##// END OF EJS Templates
auth-crawd: py3 compat
dan -
r4350:365337eb default
parent child Browse files
Show More
@@ -1,295 +1,295 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode authentication plugin for Atlassian CROWD
23 23 """
24 24
25 25
26 26 import colander
27 27 import base64
28 28 import logging
29 29 import urllib2
30 30
31 31 from rhodecode.translation import _
32 32 from rhodecode.authentication.base import (
33 33 RhodeCodeExternalAuthPlugin, hybrid_property)
34 34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 35 from rhodecode.authentication.routes import AuthnPluginResourceBase
36 36 from rhodecode.lib.colander_utils import strip_whitespace
37 37 from rhodecode.lib.ext_json import json, formatted_json
38 38 from rhodecode.model.db import User
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42
43 43 def plugin_factory(plugin_id, *args, **kwargs):
44 44 """
45 45 Factory function that is called during plugin discovery.
46 46 It returns the plugin instance.
47 47 """
48 48 plugin = RhodeCodeAuthPlugin(plugin_id)
49 49 return plugin
50 50
51 51
52 52 class CrowdAuthnResource(AuthnPluginResourceBase):
53 53 pass
54 54
55 55
56 56 class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase):
57 57 host = colander.SchemaNode(
58 58 colander.String(),
59 59 default='127.0.0.1',
60 60 description=_('The FQDN or IP of the Atlassian CROWD Server'),
61 61 preparer=strip_whitespace,
62 62 title=_('Host'),
63 63 widget='string')
64 64 port = colander.SchemaNode(
65 65 colander.Int(),
66 66 default=8095,
67 67 description=_('The Port in use by the Atlassian CROWD Server'),
68 68 preparer=strip_whitespace,
69 69 title=_('Port'),
70 70 validator=colander.Range(min=0, max=65536),
71 71 widget='int')
72 72 app_name = colander.SchemaNode(
73 73 colander.String(),
74 74 default='',
75 75 description=_('The Application Name to authenticate to CROWD'),
76 76 preparer=strip_whitespace,
77 77 title=_('Application Name'),
78 78 widget='string')
79 79 app_password = colander.SchemaNode(
80 80 colander.String(),
81 81 default='',
82 82 description=_('The password to authenticate to CROWD'),
83 83 preparer=strip_whitespace,
84 84 title=_('Application Password'),
85 85 widget='password')
86 86 admin_groups = colander.SchemaNode(
87 87 colander.String(),
88 88 default='',
89 89 description=_('A comma separated list of group names that identify '
90 90 'users as RhodeCode Administrators'),
91 91 missing='',
92 92 preparer=strip_whitespace,
93 93 title=_('Admin Groups'),
94 94 widget='string')
95 95
96 96
97 97 class CrowdServer(object):
98 98 def __init__(self, *args, **kwargs):
99 99 """
100 100 Create a new CrowdServer object that points to IP/Address 'host',
101 101 on the given port, and using the given method (https/http). user and
102 102 passwd can be set here or with set_credentials. If unspecified,
103 103 "version" defaults to "latest".
104 104
105 105 example::
106 106
107 107 cserver = CrowdServer(host="127.0.0.1",
108 108 port="8095",
109 109 user="some_app",
110 110 passwd="some_passwd",
111 111 version="1")
112 112 """
113 113 if not "port" in kwargs:
114 114 kwargs["port"] = "8095"
115 115 self._logger = kwargs.get("logger", logging.getLogger(__name__))
116 116 self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
117 117 kwargs.get("host", "127.0.0.1"),
118 118 kwargs.get("port", "8095"))
119 119 self.set_credentials(kwargs.get("user", ""),
120 120 kwargs.get("passwd", ""))
121 121 self._version = kwargs.get("version", "latest")
122 122 self._url_list = None
123 123 self._appname = "crowd"
124 124
125 125 def set_credentials(self, user, passwd):
126 126 self.user = user
127 127 self.passwd = passwd
128 128 self._make_opener()
129 129
130 130 def _make_opener(self):
131 131 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
132 132 mgr.add_password(None, self._uri, self.user, self.passwd)
133 133 handler = urllib2.HTTPBasicAuthHandler(mgr)
134 134 self.opener = urllib2.build_opener(handler)
135 135
136 136 def _request(self, url, body=None, headers=None,
137 137 method=None, noformat=False,
138 138 empty_response_ok=False):
139 139 _headers = {"Content-type": "application/json",
140 140 "Accept": "application/json"}
141 141 if self.user and self.passwd:
142 142 authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
143 143 _headers["Authorization"] = "Basic %s" % authstring
144 144 if headers:
145 145 _headers.update(headers)
146 146 log.debug("Sent crowd: \n%s"
147 147 % (formatted_json({"url": url, "body": body,
148 148 "headers": _headers})))
149 149 request = urllib2.Request(url, body, _headers)
150 150 if method:
151 151 request.get_method = lambda: method
152 152
153 153 global msg
154 154 msg = ""
155 155 try:
156 rdoc = self.opener.open(request)
157 msg = "".join(rdoc.readlines())
156 ret_doc = self.opener.open(request)
157 msg = ret_doc.read()
158 158 if not msg and empty_response_ok:
159 rval = {}
160 rval["status"] = True
161 rval["error"] = "Response body was empty"
159 ret_val = {}
160 ret_val["status"] = True
161 ret_val["error"] = "Response body was empty"
162 162 elif not noformat:
163 rval = json.loads(msg)
164 rval["status"] = True
163 ret_val = json.loads(msg)
164 ret_val["status"] = True
165 165 else:
166 rval = "".join(rdoc.readlines())
166 ret_val = msg
167 167 except Exception as e:
168 168 if not noformat:
169 rval = {"status": False,
170 "body": body,
171 "error": str(e) + "\n" + msg}
169 ret_val = {"status": False,
170 "body": body,
171 "error": "{}\n{}".format(e, msg)}
172 172 else:
173 rval = None
174 return rval
173 ret_val = None
174 return ret_val
175 175
176 176 def user_auth(self, username, password):
177 177 """Authenticate a user against crowd. Returns brief information about
178 178 the user."""
179 179 url = ("%s/rest/usermanagement/%s/authentication?username=%s"
180 180 % (self._uri, self._version, username))
181 181 body = json.dumps({"value": password})
182 182 return self._request(url, body)
183 183
184 184 def user_groups(self, username):
185 185 """Retrieve a list of groups to which this user belongs."""
186 186 url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
187 187 % (self._uri, self._version, username))
188 188 return self._request(url)
189 189
190 190
191 191 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
192 192 uid = 'crowd'
193 193 _settings_unsafe_keys = ['app_password']
194 194
195 195 def includeme(self, config):
196 196 config.add_authn_plugin(self)
197 197 config.add_authn_resource(self.get_id(), CrowdAuthnResource(self))
198 198 config.add_view(
199 199 'rhodecode.authentication.views.AuthnPluginViewBase',
200 200 attr='settings_get',
201 201 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
202 202 request_method='GET',
203 203 route_name='auth_home',
204 204 context=CrowdAuthnResource)
205 205 config.add_view(
206 206 'rhodecode.authentication.views.AuthnPluginViewBase',
207 207 attr='settings_post',
208 208 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
209 209 request_method='POST',
210 210 route_name='auth_home',
211 211 context=CrowdAuthnResource)
212 212
213 213 def get_settings_schema(self):
214 214 return CrowdSettingsSchema()
215 215
216 216 def get_display_name(self):
217 217 return _('CROWD')
218 218
219 219 @classmethod
220 220 def docs(cls):
221 221 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-crowd.html"
222 222
223 223 @hybrid_property
224 224 def name(self):
225 225 return u"crowd"
226 226
227 227 def use_fake_password(self):
228 228 return True
229 229
230 230 def user_activation_state(self):
231 231 def_user_perms = User.get_default_user().AuthUser().permissions['global']
232 232 return 'hg.extern_activate.auto' in def_user_perms
233 233
234 234 def auth(self, userobj, username, password, settings, **kwargs):
235 235 """
236 236 Given a user object (which may be null), username, a plaintext password,
237 237 and a settings object (containing all the keys needed as listed in settings()),
238 238 authenticate this user's login attempt.
239 239
240 240 Return None on failure. On success, return a dictionary of the form:
241 241
242 242 see: RhodeCodeAuthPluginBase.auth_func_attrs
243 243 This is later validated for correctness
244 244 """
245 245 if not username or not password:
246 246 log.debug('Empty username or password skipping...')
247 247 return None
248 248
249 249 log.debug("Crowd settings: \n%s", formatted_json(settings))
250 250 server = CrowdServer(**settings)
251 251 server.set_credentials(settings["app_name"], settings["app_password"])
252 252 crowd_user = server.user_auth(username, password)
253 253 log.debug("Crowd returned: \n%s", formatted_json(crowd_user))
254 254 if not crowd_user["status"]:
255 255 return None
256 256
257 257 res = server.user_groups(crowd_user["name"])
258 258 log.debug("Crowd groups: \n%s", formatted_json(res))
259 259 crowd_user["groups"] = [x["name"] for x in res["groups"]]
260 260
261 261 # old attrs fetched from RhodeCode database
262 262 admin = getattr(userobj, 'admin', False)
263 263 active = getattr(userobj, 'active', True)
264 264 email = getattr(userobj, 'email', '')
265 265 username = getattr(userobj, 'username', username)
266 266 firstname = getattr(userobj, 'firstname', '')
267 267 lastname = getattr(userobj, 'lastname', '')
268 268 extern_type = getattr(userobj, 'extern_type', '')
269 269
270 270 user_attrs = {
271 271 'username': username,
272 272 'firstname': crowd_user["first-name"] or firstname,
273 273 'lastname': crowd_user["last-name"] or lastname,
274 274 'groups': crowd_user["groups"],
275 275 'user_group_sync': True,
276 276 'email': crowd_user["email"] or email,
277 277 'admin': admin,
278 278 'active': active,
279 279 'active_from_extern': crowd_user.get('active'),
280 280 'extern_name': crowd_user["name"],
281 281 'extern_type': extern_type,
282 282 }
283 283
284 284 # set an admin if we're in admin_groups of crowd
285 285 for group in settings["admin_groups"]:
286 286 if group in user_attrs["groups"]:
287 287 user_attrs["admin"] = True
288 288 log.debug("Final crowd user object: \n%s", formatted_json(user_attrs))
289 289 log.info('user `%s` authenticated correctly', user_attrs['username'])
290 290 return user_attrs
291 291
292 292
293 293 def includeme(config):
294 294 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid)
295 295 plugin_factory(plugin_id).includeme(config)
General Comments 0
You need to be logged in to leave comments. Login now