aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStevan Radaković <stevan.radakovic@linaro.org>2013-03-01 10:09:05 +0100
committerStevan Radaković <stevan.radakovic@linaro.org>2013-03-01 10:09:05 +0100
commit01174e45feff1023b1d299655dc547ddfc9cbfc2 (patch)
tree2275096283d8beacf56fdd9cefc4837344004dc1
parent83e7c03ba90a4120a47bdf93fb160701f12d2c59 (diff)
Add ldap_sync files.
-rw-r--r--scripts/ldap_sync.conf11
-rw-r--r--scripts/ldap_sync.py237
2 files changed, 248 insertions, 0 deletions
diff --git a/scripts/ldap_sync.conf b/scripts/ldap_sync.conf
new file mode 100644
index 0000000..8e54019
--- /dev/null
+++ b/scripts/ldap_sync.conf
@@ -0,0 +1,11 @@
+[default]
+api_url = http://your.rhodecode.server:5000/_admin/api
+api_user = admin
+api_key = XXXXXXXXXXXX
+
+ldap_uri = ldap://your.ldap.server:389
+ldap_user = cn=rhodecode,ou=binders,dc=linaro,dc=org
+ldap_key = XXXXXXXXX
+base_dn = dc=linaro,dc=org
+
+sync_users = True
diff --git a/scripts/ldap_sync.py b/scripts/ldap_sync.py
new file mode 100644
index 0000000..71c1479
--- /dev/null
+++ b/scripts/ldap_sync.py
@@ -0,0 +1,237 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import ldap
+import urllib2
+import uuid
+import json
+
+from ConfigParser import ConfigParser
+
+config = ConfigParser()
+config.read('ldap_sync.conf')
+
+
+class InvalidResponseIDError(Exception):
+ ''' Request and response don't have the same UUID. '''
+
+class RhodecodeResponseError(Exception):
+ ''' Response has an error, something went wrong with request execution. '''
+
+class UserAlreadyInGroupError(Exception):
+ ''' User is already a member of the target group. '''
+
+class UserNotInGroupError(Exception):
+ ''' User is not a member of the target group. '''
+
+
+class RhodecodeAPI():
+
+ def __init__(self, url, key):
+ self.url = url
+ self.key = key
+
+ def get_api_data(self, id, method, args):
+ """Prepare dict for API post."""
+ return {
+ "id": id,
+ "api_key": self.key,
+ "method": method,
+ "args": args
+ }
+
+ def rhodecode_api_post(self, method, args):
+ """Send a generic API post to Rhodecode.
+
+ This will generate the UUID for validation check after the
+ response is returned. Handle errors and get the result back.
+ """
+ id = str(uuid.uuid1())
+ data = self.get_api_data(id, method, args)
+
+ data = json.dumps(data)
+ headers = {'content-type': 'text/plain'}
+ req = urllib2.Request(self.url, data, headers)
+
+ response = urllib2.urlopen(req)
+ response = json.load(response)
+
+ if id != response["id"]:
+ raise InvalidResponseIDError("UUID does not match.")
+
+ if response["error"] != None:
+ raise RhodecodeResponseError(response["error"])
+
+ return response["result"]
+
+ def create_group(self, name, active=True):
+ """Create the Rhodecode user group."""
+ args = {
+ "group_name": name,
+ "active": str(active)
+ }
+ self.rhodecode_api_post("create_users_group", args)
+
+ def add_membership(self, group, username):
+ """Add specific user to a group."""
+ args = {
+ "usersgroupid": group,
+ "userid": username
+ }
+ result = self.rhodecode_api_post("add_user_to_users_group", args)
+ if not result["success"]:
+ raise UserAlreadyInGroupError("User %s already in group %s." %
+ (username, group))
+
+ def remove_membership(self, group, username):
+ """Remove specific user from a group."""
+ args = {
+ "usersgroupid": group,
+ "userid": username
+ }
+ result = self.rhodecode_api_post("remove_user_from_users_group", args)
+ if not result["success"]:
+ raise UserNotInGroupError("User %s not in group %s." %
+ (username, group))
+
+ def get_group_members(self, name):
+ """Get the list of member usernames from a user group."""
+ args = {"usersgroupid": name}
+ members = self.rhodecode_api_post("get_users_group", args)['members']
+ member_list = []
+ for member in members:
+ member_list.append(member["username"])
+ return member_list
+
+ def get_group(self, name):
+ """Return group info."""
+ args = {"usersgroupid": name}
+ return self.rhodecode_api_post("get_users_group", args)
+
+ def get_user(self, username):
+ """Return user info."""
+ args = {"userid": username}
+ return self.rhodecode_api_post("get_user", args)
+
+
+class LdapClient():
+
+ def __init__(self, uri, user, key, base_dn):
+ self.client = ldap.initialize(uri, trace_level=0)
+ self.client.set_option(ldap.OPT_REFERRALS, 0)
+ self.client.simple_bind(user, key)
+ self.base_dn = base_dn
+
+ def __del__(self):
+ self.client.unbind()
+
+ def get_groups(self):
+ """Get all the groups in form of dict {group_name: group_info,...}."""
+ searchFilter = "objectClass=groupOfUniqueNames"
+ result = self.client.search_s(self.base_dn, ldap.SCOPE_SUBTREE,
+ searchFilter)
+
+ groups = {}
+ for group in result:
+ groups[group[1]['cn'][0]] = group[1]
+
+ return groups
+
+ def get_group_users(self, groups, group):
+ """Returns all the users belonging to a single group.
+
+ Based on the list of groups and memberships, returns all the
+ users belonging to a single group, searching recursively.
+ """
+ users = []
+ for member in groups[group]["uniqueMember"]:
+ member = self.parse_member_string(member)
+ if member[0] == "uid":
+ users.append(member[1])
+ elif member[0] == "cn":
+ users += self.get_group_users(groups, member[1])
+
+ return users
+
+ def parse_member_string(self, member):
+ """Parses the member string and returns a touple of type and name.
+
+ Unique member can be either user or group. Users will have 'uid' as
+ prefix while groups will have 'cn'.
+ """
+ member = member.split(",")[0]
+ return member.split('=')
+
+class LdapSync():
+
+ def __init__(self):
+ self.ldap_client = LdapClient(config.get("default", "ldap_uri"),
+ config.get("default", "ldap_user"),
+ config.get("default", "ldap_key"),
+ config.get("default", "base_dn"))
+ self.rhodecode_api = RhodecodeAPI(config.get("default", "api_url"),
+ config.get("default", "api_key"))
+
+ def update_groups_from_ldap(self):
+ """Add all the groups from LDAP to Rhodecode."""
+ added = existing = 0
+ groups = self.ldap_client.get_groups()
+ for group in groups:
+ try:
+ self.rhodecode_api.create_group(group)
+ added += 1
+ except:
+ existing += 1
+
+ return added, existing
+
+ def update_memberships_from_ldap(self, group):
+ """Update memberships in rhodecode based on the LDAP groups."""
+ groups = self.ldap_client.get_groups()
+ group_users = self.ldap_client.get_group_users(groups, group)
+
+ # Delete memberships first from each group which are not part
+ # of the group any more.
+ rhodecode_members = self.rhodecode_api.get_group_members(group)
+ for rhodecode_member in rhodecode_members:
+ if rhodecode_member not in group_users:
+ try:
+ self.rhodocode_api.remove_membership(group,
+ rhodecode_member)
+ except UserNotInGroupError:
+ pass
+
+ # Add memberships.
+ for member in group_users:
+ try:
+ self.rhodecode_api.add_membership(group, member)
+ except UserAlreadyInGroupError:
+ # TODO: handle somehow maybe..
+ pass
+
+
+if __name__ == '__main__':
+
+ sync = LdapSync()
+
+ print sync.update_groups_from_ldap()
+
+ groups = sync.ldap_client.get_groups()
+ for group in groups:
+ # TODO: exception when user does not exist during add membership...
+ # How should we handle this.. Either sync users as well at this step,
+ # or just ignore those who don't exist. If we want the second case,
+ # we need to find a way to recognize the right exception (we always get
+ # RhodecodeResponseError with no error code so maybe by return msg (?)
+ sync.update_memberships_from_ldap(group)
+