Adds a basic tool for AAA IDM manipulation 96/34196/1
authorRyan Goulding <ryandgoulding@gmail.com>
Fri, 5 Feb 2016 23:13:46 +0000 (18:13 -0500)
committerRyan Goulding <ryandgoulding@gmail.com>
Fri, 5 Feb 2016 23:50:45 +0000 (23:50 +0000)
Change-Id: Ic38f2f23e4a302ecfca39ceadfe2979faec8aeba
Signed-off-by: Ryan Goulding <ryandgoulding@gmail.com>
(cherry picked from commit f6c87f3cd7eaa6ffc32625546828a2b6cd42722e)

aaa-idmlight/pom.xml
aaa-idmlight/src/main/resources/idmtool.py [new file with mode: 0755]
features/authn/pom.xml
features/authn/src/main/features/features.xml

index b9c8d1953a3b4e73f79a09dc6aa4b5015154785d..3871c6d98242b996a05039fe22bf95c958ec8cfc 100644 (file)
                             </artifacts>
                         </configuration>
                     </execution>
+                    <execution>
+                        <id>attach-artifacts-idmtool</id>
+                        <goals>
+                            <goal>attach-artifact</goal>
+                        </goals>
+                        <phase>package</phase>
+                        <configuration>
+                            <artifacts>
+                                <artifact>
+                                    <file>${project.build.directory}/classes/idmtool.py</file>
+                                    <type>py</type>
+                                    <classifier>config</classifier>
+                                </artifact>
+                            </artifacts>
+                        </configuration>
+                    </execution>
+
                 </executions>
             </plugin>
             <plugin>
diff --git a/aaa-idmlight/src/main/resources/idmtool.py b/aaa-idmlight/src/main/resources/idmtool.py
new file mode 100755 (executable)
index 0000000..d0a31ba
--- /dev/null
@@ -0,0 +1,247 @@
+#!/usr/bin/env python
+
+#
+# Copyright (c) 2016 Brocade Communications Systems and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+#
+
+'''
+idmtool
+
+Used to manipulate ODL AAA idm on a node-per-node basis.  Assumes only one domain (sdn)
+since current support in ODL is limited.
+'''
+
+__author__ = "Ryan Goulding"
+__copyright__ = "Copyright (c) 2016 Brocade Communications Systems and others"
+__credits__ = "Ryan Goulding"
+__license__ = "EPL"
+__version__ = "1.0"
+__maintainer__ = "Ryan Goulding"
+__email__ = "ryandgoulding@gmail.com"
+__status__ = "Production"
+
+import argparse, getpass, json, requests, sys
+
+parser = argparse.ArgumentParser('idmtool')
+
+user=''
+hostname='localhost'
+protocol='http'
+port='8181'
+target_host='{}://{}:{}/'.format(protocol, hostname, port)
+
+# main program arguments
+parser.add_argument('user',help='username for BSC node', nargs=1)
+parser.add_argument('--target-host', help="target host node", nargs=1)
+
+subparsers = parser.add_subparsers(help='sub-command help')
+
+# users table related
+list_users = subparsers.add_parser('list-users', help='list all users')
+list_users.set_defaults(func=list_users)
+add_user = subparsers.add_parser('add-user', help='add a user')
+add_user.set_defaults(func=add_user)
+add_user.add_argument('newUser', help='new user name', nargs=1)
+change_password = subparsers.add_parser('change-password', help='change a password')
+change_password.set_defaults(func=change_password)
+change_password.add_argument('userid', help='change the password for a particular userid', nargs=1)
+delete_user = subparsers.add_parser('delete-user', help='delete a user')
+delete_user.add_argument('userid', help='name@sdn', nargs=1)
+delete_user.set_defaults(func=delete_user)
+
+# domains table related
+# only read is defined;  this was done on purpose since the "domain" concept
+# is mostly unsupported in ODL.
+list_domains = subparsers.add_parser('list-domains', help='list all domains')
+list_domains.set_defaults(func=list_domains)
+
+# roles table related
+list_roles = subparsers.add_parser('list-roles', help='list all roles')
+list_roles.set_defaults(func=list_roles)
+add_role = subparsers.add_parser('add-role', help='add a role')
+add_role.add_argument('role', help='role name', nargs=1)
+add_role.set_defaults(func=add_role)
+delete_role = subparsers.add_parser('delete-role', help='delete a role')
+delete_role.add_argument('roleid', help='rolename@sdn', nargs=1)
+delete_role.set_defaults(func=delete_role)
+add_grant = subparsers.add_parser('add-grant', help='add a grant')
+add_grant.set_defaults(func=add_grant)
+add_grant.add_argument('userid', help="username@sdn", nargs=1)
+add_grant.add_argument('roleid', help="role@sdn", nargs=1)
+get_grants = subparsers.add_parser('get-grants', help='get grants for userid on sdn')
+get_grants.set_defaults(func=get_grants)
+get_grants.add_argument('userid', help="username@sdn", nargs=1)
+delete_grant = subparsers.add_parser('delete-grant', help='delete a grant')
+delete_grant.add_argument('userid', help='username@sdn', nargs=1)
+delete_grant.add_argument('roleid', help='role@sdn', nargs=1)
+delete_grant.set_defaults(func=delete_grant)
+
+def process_result(r):
+    ''' Generic method to print result of a REST call '''
+    print ''
+    sc = r.status_code
+    if sc >= 200 and sc < 300:
+        print "command succeeded!"
+        try:
+            res = r.json()
+            if res is not None:
+                print '\njson:\n', json.dumps(res, indent=4, sort_keys=True)
+        except(ValueError):
+            pass
+    elif sc == 401:
+        print "Incorrect Credentials Provided"
+    elif sc == 404:
+        print "RESTconf is either not installed or not initialized yet"
+    elif sc >= 500 and sc < 600:
+        print "Internal Server Error Ocurred"
+    else:
+        print "Unknown error; HTTP status code: {}".format(sc)
+
+def get_request(user, password, url, description, outputResult=True):
+    if outputResult:
+        print description
+    try:
+        r = requests.get(url, auth=(user,password))
+        if outputResult:
+            process_result(r)
+        return r
+    except(requests.exceptions.ConnectionError):
+        if outputResult:
+            print "Unable to connect;  are you sure the controller is up?"
+            sys.exit(1)
+
+def post_request(user, password, url, description, payload, params):
+    print description
+    try:
+        r = requests.post(url, auth=(user,password), data=payload, headers=params)
+        process_result(r)
+    except(requests.exceptions.ConnectionError):
+        print "Unable to connect; are you sure the controller is up?"
+        sys.exit(1)
+
+def put_request(user, password, url, description, payload, params):
+    print description
+    try:
+        r = requests.put(url, auth=(user,password), data=payload, headers=params)
+        process_result(r)
+    except(requests.exceptions.ConnectionError):
+        print "Unable to connect; are you sure the controller is up?"
+        sys.exit(1)
+
+def delete_request(user, password, url, description, payload='', params={'Content-Type':'application/json'}):
+    print description
+    try:
+        r = requests.delete(url, auth=(user,password), data=payload, headers=params)
+        process_result(r)
+    except(requests.exceptions.ConnectionError):
+        print "Unable to connect; are you sure the controller is up?"
+        sys.exit(1)
+
+def poll_new_password():
+    new_password = getpass.getpass(prompt="Enter new password: ")
+    new_password_repeated = getpass.getpass(prompt="Re-enter password: ")
+    if new_password != new_password_repeated:
+        print "Passwords did not match;  cancelling the add_user request"
+        sys.exit(1)
+    return new_password
+
+def list_users(user, password):
+    get_request(user, password, target_host + 'auth/v1/users', 'list_users')
+
+def add_user(user, password, newUser):
+    new_password = poll_new_password()
+    description = 'add_user({})'.format(user)
+    url = target_host + 'auth/v1/users'
+    payload =  {'name':newUser, 'password':new_password, 'description':'', "domainid":"sdn", 'userid':'{}@sdn'.format(newUser), 'email':''}
+    jsonpayload = json.dumps(payload)
+    headers={'Content-Type':'application/json'}
+    post_request(user, password, url, description, jsonpayload, headers)
+
+def delete_user(user, password, userid):
+    url = target_host + 'auth/v1/users/{}'.format(userid)
+    description = 'delete_user({})'.format(userid)
+    delete_request(user, password, url, description)
+
+def change_password(user, password, existingUserId):
+    url = target_host + 'auth/v1/users/{}'.format(existingUserId)
+    r = get_request(user, password, target_host + 'auth/v1/users/{}'.format(existingUserId), 'list_users', outputResult=False)
+    try:
+        existing = r.json()
+        del existing['salt']
+        del existing['password']
+        new_password = poll_new_password()
+        existing['password'] = new_password
+        description='change_password({})'.format(existingUserId)
+        headers={'Content-Type':'application/json'}
+        url = target_host + 'auth/v1/users/{}'.format(existingUserId)
+        put_request(user, password, url, 'change_password({})'.format(user), json.dumps(existing), headers)
+    except(AttributeError):
+        print "Unable to connect;  are you sure the controller is up?"
+        sys.exit(1)
+
+def list_domains(user, password):
+    get_request(user, password, target_host + 'auth/v1/domains', 'list_domains')
+
+def list_roles(user, password):
+    get_request(user, password, target_host + 'auth/v1/roles', 'list_roles')
+
+def add_role(user, password, role):
+    url = target_host + 'auth/v1/roles'
+    description = 'add_role({})'.format(role)
+    payload = {"roleid":'{}@sdn'.format(role), 'name':role, 'description':'', 'domainid':'sdn'}
+    data = json.dumps(payload)
+    headers={'Content-Type':'application/json'}
+    post_request(user, password, url, description, data, headers)
+
+def delete_role(user, password, roleid):
+    url = target_host + 'auth/v1/roles/{}'.format(roleid)
+    description = 'delete_role({})'.format(roleid)
+    delete_request(user, password, url, description)
+
+def add_grant(user, password, userid, roleid):
+    description = 'add_grant(userid={},roleid={})'.format(userid, roleid)
+    payload = {"roleid":roleid, "userid":userid, "grantid":'{}@{}@{}'.format(userid, roleid, "sdn"), "domainid":"sdn"}
+    url = target_host + 'auth/v1/domains/sdn/users/{}/roles'.format(userid)
+    data=json.dumps(payload)
+    headers={'Content-Type':'application/json'}
+    post_request(user, password, url, description, data, headers)
+
+def get_grants(user, password, userid):
+    get_request(user, password, target_host + 'auth/v1/domains/sdn/users/{}/roles'.format(userid), 'get_grants({})'.format(userid))
+
+def delete_grant(user, password, userid, roleid):
+    url = target_host + 'auth/v1/domains/sdn/users/{}/roles/{}'.format(userid, roleid)
+    print url
+    description = 'delete_grant(userid={},roleid={})'.format(userid, roleid)
+    delete_request(user, password, url, description)
+
+args = parser.parse_args()
+command = args.func.prog.split()[1:]
+user = args.user[0]
+password = getpass.getpass()
+if "list-users" in command:
+    list_users(user,password)
+if "list-domains" in command:
+    list_domains(user,password)
+if "list-roles" in command:
+    list_roles(user,password)
+if "add-user" in command:
+    add_user(user,password, args.newUser[0])
+if "add-grant" in command:
+    add_grant(user,password, args.userid[0], args.roleid[0])
+if "get-grants" in command:
+    get_grants(user,password, args.userid[0])
+if "change-password" in command:
+    change_password(user, password, args.userid[0])
+if "delete-user" in command:
+    delete_user(user, password, args.userid[0])
+if "delete-role" in command:
+    delete_role(user, password, args.roleid[0])
+if "add-role" in command:
+    add_role(user, password, args.role[0])
+if "delete-grant" in command:
+    delete_grant(user, password, args.userid[0], args.roleid[0])
index b5999c3a79106a459667235da4c0a69f706172ba..8b9e444b24c4b47a85d81216421e3cbdd8ed2359 100644 (file)
             <type>xml</type>
             <classifier>config</classifier>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.aaa</groupId>
+            <artifactId>aaa-idmlight</artifactId>
+            <version>${project.version}</version>
+            <type>py</type>
+            <classifier>config</classifier>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.aaa</groupId>
             <artifactId>aaa-authn-federation</artifactId>
index 461d035763e567e2baacd503c275e9b27945a8c7..2bc183bc1aae8bcbf4d981c2669b1a5deb424493 100644 (file)
@@ -63,6 +63,8 @@
         <!-- IDMLight -->
         <bundle>mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}</bundle>
         <configfile finalname="etc/opendaylight/karaf/08-aaa-idmlight-config.xml">mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/xml/config</configfile>
+        <configfile finalname="etc/idmtool">mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/py/config</configfile>
+
         <bundle>mvn:com.fasterxml.jackson.core/jackson-core/{{VERSION}}</bundle>
         <bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/{{VERSION}}</bundle>
         <bundle>mvn:com.fasterxml.jackson.core/jackson-databind/{{VERSION}}</bundle>
         <!-- IDMLight -->
         <bundle>mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}</bundle>
         <configfile finalname="etc/opendaylight/karaf/08-aaa-idmlight-config.xml">mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/xml/config</configfile>
+        <configfile finalname="etc/idmtool">mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/py/config</configfile>
+
         <bundle>mvn:com.fasterxml.jackson.core/jackson-core/{{VERSION}}</bundle>
         <bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/{{VERSION}}</bundle>
         <bundle>mvn:com.fasterxml.jackson.core/jackson-databind/{{VERSION}}</bundle>