Files
@ ffd45b185016
Branch filter:
Location: kallithea/rhodecode/bin/ldap_sync.py - annotation
ffd45b185016
8.4 KiB
text/x-python
Imported some of the GPLv3'd changes from RhodeCode v2.2.5.
This imports changes between changesets 21af6c4eab3d and 6177597791c2 in
RhodeCode's original repository, including only changes to Python files and HTML.
RhodeCode clearly licensed its changes to these files under GPLv3
in their /LICENSE file, which states the following:
The Python code and integrated HTML are licensed under the GPLv3 license.
(See:
https://code.rhodecode.com/rhodecode/files/v2.2.5/LICENSE
or
http://web.archive.org/web/20140512193334/https://code.rhodecode.com/rhodecode/files/f3b123159901f15426d18e3dc395e8369f70ebe0/LICENSE
for an online copy of that LICENSE file)
Conservancy reviewed these changes and confirmed that they can be licensed as
a whole to the Kallithea project under GPLv3-only.
While some of the contents committed herein are clearly licensed
GPLv3-or-later, on the whole we must assume the are GPLv3-only, since the
statement above from RhodeCode indicates that they intend GPLv3-only as their
license, per GPLv3§14 and other relevant sections of GPLv3.
This imports changes between changesets 21af6c4eab3d and 6177597791c2 in
RhodeCode's original repository, including only changes to Python files and HTML.
RhodeCode clearly licensed its changes to these files under GPLv3
in their /LICENSE file, which states the following:
The Python code and integrated HTML are licensed under the GPLv3 license.
(See:
https://code.rhodecode.com/rhodecode/files/v2.2.5/LICENSE
or
http://web.archive.org/web/20140512193334/https://code.rhodecode.com/rhodecode/files/f3b123159901f15426d18e3dc395e8369f70ebe0/LICENSE
for an online copy of that LICENSE file)
Conservancy reviewed these changes and confirmed that they can be licensed as
a whole to the Kallithea project under GPLv3-only.
While some of the contents committed herein are clearly licensed
GPLv3-or-later, on the whole we must assume the are GPLv3-only, since the
statement above from RhodeCode indicates that they intend GPLv3-only as their
license, per GPLv3§14 and other relevant sections of GPLv3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d ffd45b185016 ffd45b185016 ffd45b185016 ffd45b185016 ffd45b185016 ffd45b185016 ffd45b185016 ffd45b185016 ffd45b185016 ffd45b185016 ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d f37d7514e7ab f37d7514e7ab f37d7514e7ab f37d7514e7ab f37d7514e7ab f37d7514e7ab f37d7514e7ab f37d7514e7ab 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d b84c83b651de 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d ffd45b185016 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d 4358b1b9307d | # -*- coding: utf-8 -*-
# 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/>.
"""
rhodecode.bin.__init__
~~~~~~~~~~~~~~~~~~~~~~
LDAP sync script
:created_on: Mar 06, 2013
:author: marcink
:copyright: (c) 2013 RhodeCode GmbH.
:license: GPLv3, see LICENSE for more details.
"""
import ldap
import urllib2
import uuid
try:
from rhodecode.lib.compat import json
except ImportError:
try:
import simplejson as json
except ImportError:
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(object):
def __init__(self, url, key):
self.url = url
self.key = key
def get_api_data(self, uid, method, args):
"""Prepare dict for API post."""
return {
"id": uid,
"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.
"""
uid = str(uuid.uuid1())
data = self.get_api_data(uid, 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 uid != response["id"]:
raise InvalidResponseIDError("UUID does not match.")
if response["error"] is not 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_user_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_user_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_user_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_user_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_user_group", args)
def get_user(self, username):
"""Return user info."""
args = {"userid": username}
return self.rhodecode_api_post("get_user", args)
class LdapClient(object):
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(object):
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.rhodocode_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_repo_group(group)
added += 1
except Exception:
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()
for gr in sync.ldap_client.get_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(gr)
|