##// END OF EJS Templates
added linaro ldap sync script...
marcink -
r3556:4358b1b9 beta
parent child Browse files
Show More
@@ -0,0 +1,11 b''
1 [default]
2 api_url = http://your.rhodecode.server:5000/_admin/api
3 api_user = admin
4 api_key = XXXXXXXXXXXX
5
6 ldap_uri = ldap://your.ldap.server:389
7 ldap_user = cn=rhodecode,ou=binders,dc=linaro,dc=org
8 ldap_key = XXXXXXXXX
9 base_dn = dc=linaro,dc=org
10
11 sync_users = True No newline at end of file
@@ -0,0 +1,237 b''
1 # This program is free software: you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation, either version 3 of the License, or
4 # (at your option) any later version.
5 #
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU General Public License for more details.
10 #
11 # You should have received a copy of the GNU General Public License
12 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13
14 import ldap
15 import urllib2
16 import uuid
17 import json
18
19 from ConfigParser import ConfigParser
20
21 config = ConfigParser()
22 config.read('ldap_sync.conf')
23
24
25 class InvalidResponseIDError(Exception):
26 """ Request and response don't have the same UUID. """
27
28
29 class RhodecodeResponseError(Exception):
30 """ Response has an error, something went wrong with request execution. """
31
32
33 class UserAlreadyInGroupError(Exception):
34 """ User is already a member of the target group. """
35
36
37 class UserNotInGroupError(Exception):
38 """ User is not a member of the target group. """
39
40
41 class RhodecodeAPI():
42
43 def __init__(self, url, key):
44 self.url = url
45 self.key = key
46
47 def get_api_data(self, uid, method, args):
48 """Prepare dict for API post."""
49 return {
50 "id": uid,
51 "api_key": self.key,
52 "method": method,
53 "args": args
54 }
55
56 def rhodecode_api_post(self, method, args):
57 """Send a generic API post to Rhodecode.
58
59 This will generate the UUID for validation check after the
60 response is returned. Handle errors and get the result back.
61 """
62 uid = str(uuid.uuid1())
63 data = self.get_api_data(uid, method, args)
64
65 data = json.dumps(data)
66 headers = {'content-type': 'text/plain'}
67 req = urllib2.Request(self.url, data, headers)
68
69 response = urllib2.urlopen(req)
70 response = json.load(response)
71
72 if uid != response["id"]:
73 raise InvalidResponseIDError("UUID does not match.")
74
75 if response["error"] != None:
76 raise RhodecodeResponseError(response["error"])
77
78 return response["result"]
79
80 def create_group(self, name, active=True):
81 """Create the Rhodecode user group."""
82 args = {
83 "group_name": name,
84 "active": str(active)
85 }
86 self.rhodecode_api_post("create_users_group", args)
87
88 def add_membership(self, group, username):
89 """Add specific user to a group."""
90 args = {
91 "usersgroupid": group,
92 "userid": username
93 }
94 result = self.rhodecode_api_post("add_user_to_users_group", args)
95 if not result["success"]:
96 raise UserAlreadyInGroupError("User %s already in group %s." %
97 (username, group))
98
99 def remove_membership(self, group, username):
100 """Remove specific user from a group."""
101 args = {
102 "usersgroupid": group,
103 "userid": username
104 }
105 result = self.rhodecode_api_post("remove_user_from_users_group", args)
106 if not result["success"]:
107 raise UserNotInGroupError("User %s not in group %s." %
108 (username, group))
109
110 def get_group_members(self, name):
111 """Get the list of member usernames from a user group."""
112 args = {"usersgroupid": name}
113 members = self.rhodecode_api_post("get_users_group", args)['members']
114 member_list = []
115 for member in members:
116 member_list.append(member["username"])
117 return member_list
118
119 def get_group(self, name):
120 """Return group info."""
121 args = {"usersgroupid": name}
122 return self.rhodecode_api_post("get_users_group", args)
123
124 def get_user(self, username):
125 """Return user info."""
126 args = {"userid": username}
127 return self.rhodecode_api_post("get_user", args)
128
129
130 class LdapClient():
131
132 def __init__(self, uri, user, key, base_dn):
133 self.client = ldap.initialize(uri, trace_level=0)
134 self.client.set_option(ldap.OPT_REFERRALS, 0)
135 self.client.simple_bind(user, key)
136 self.base_dn = base_dn
137
138 def __del__(self):
139 self.client.unbind()
140
141 def get_groups(self):
142 """Get all the groups in form of dict {group_name: group_info,...}."""
143 searchFilter = "objectClass=groupOfUniqueNames"
144 result = self.client.search_s(self.base_dn, ldap.SCOPE_SUBTREE,
145 searchFilter)
146
147 groups = {}
148 for group in result:
149 groups[group[1]['cn'][0]] = group[1]
150
151 return groups
152
153 def get_group_users(self, groups, group):
154 """Returns all the users belonging to a single group.
155
156 Based on the list of groups and memberships, returns all the
157 users belonging to a single group, searching recursively.
158 """
159 users = []
160 for member in groups[group]["uniqueMember"]:
161 member = self.parse_member_string(member)
162 if member[0] == "uid":
163 users.append(member[1])
164 elif member[0] == "cn":
165 users += self.get_group_users(groups, member[1])
166
167 return users
168
169 def parse_member_string(self, member):
170 """Parses the member string and returns a touple of type and name.
171
172 Unique member can be either user or group. Users will have 'uid' as
173 prefix while groups will have 'cn'.
174 """
175 member = member.split(",")[0]
176 return member.split('=')
177
178
179 class LdapSync(object):
180
181 def __init__(self):
182 self.ldap_client = LdapClient(config.get("default", "ldap_uri"),
183 config.get("default", "ldap_user"),
184 config.get("default", "ldap_key"),
185 config.get("default", "base_dn"))
186 self.rhodocode_api = RhodecodeAPI(config.get("default", "api_url"),
187 config.get("default", "api_key"))
188
189 def update_groups_from_ldap(self):
190 """Add all the groups from LDAP to Rhodecode."""
191 added = existing = 0
192 groups = self.ldap_client.get_groups()
193 for group in groups:
194 try:
195 self.rhodecode_api.create_group(group)
196 added += 1
197 except Exception:
198 existing += 1
199
200 return added, existing
201
202 def update_memberships_from_ldap(self, group):
203 """Update memberships in rhodecode based on the LDAP groups."""
204 groups = self.ldap_client.get_groups()
205 group_users = self.ldap_client.get_group_users(groups, group)
206
207 # Delete memberships first from each group which are not part
208 # of the group any more.
209 rhodecode_members = self.rhodecode_api.get_group_members(group)
210 for rhodecode_member in rhodecode_members:
211 if rhodecode_member not in group_users:
212 try:
213 self.rhodocode_api.remove_membership(group,
214 rhodecode_member)
215 except UserNotInGroupError:
216 pass
217
218 # Add memberships.
219 for member in group_users:
220 try:
221 self.rhodecode_api.add_membership(group, member)
222 except UserAlreadyInGroupError:
223 # TODO: handle somehow maybe..
224 pass
225
226
227 if __name__ == '__main__':
228 sync = LdapSync()
229 print sync.update_groups_from_ldap()
230
231 for gr in sync.ldap_client.get_groups():
232 # TODO: exception when user does not exist during add membership...
233 # How should we handle this.. Either sync users as well at this step,
234 # or just ignore those who don't exist. If we want the second case,
235 # we need to find a way to recognize the right exception (we always get
236 # RhodecodeResponseError with no error code so maybe by return msg (?)
237 sync.update_memberships_from_ldap(gr)
General Comments 0
You need to be logged in to leave comments. Login now