##// END OF EJS Templates
authn: Set the renderer attribute when registering settings view.
johbo -
r87:71cbc422 default
parent child Browse files
Show More
@@ -1,282 +1,284 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for Atlassian CROWD
22 RhodeCode authentication plugin for Atlassian CROWD
23 """
23 """
24
24
25
25
26 import colander
26 import colander
27 import base64
27 import base64
28 import logging
28 import logging
29 import urllib2
29 import urllib2
30
30
31 from pylons.i18n.translation import lazy_ugettext as _
31 from pylons.i18n.translation import lazy_ugettext as _
32 from sqlalchemy.ext.hybrid import hybrid_property
32 from sqlalchemy.ext.hybrid import hybrid_property
33
33
34 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.routes import AuthnPluginResourceBase
36 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.lib.colander_utils import strip_whitespace
37 from rhodecode.lib.colander_utils import strip_whitespace
38 from rhodecode.lib.ext_json import json, formatted_json
38 from rhodecode.lib.ext_json import json, formatted_json
39 from rhodecode.model.db import User
39 from rhodecode.model.db import User
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 def plugin_factory(plugin_id, *args, **kwds):
44 def plugin_factory(plugin_id, *args, **kwds):
45 """
45 """
46 Factory function that is called during plugin discovery.
46 Factory function that is called during plugin discovery.
47 It returns the plugin instance.
47 It returns the plugin instance.
48 """
48 """
49 plugin = RhodeCodeAuthPlugin(plugin_id)
49 plugin = RhodeCodeAuthPlugin(plugin_id)
50 return plugin
50 return plugin
51
51
52
52
53 class CrowdAuthnResource(AuthnPluginResourceBase):
53 class CrowdAuthnResource(AuthnPluginResourceBase):
54 pass
54 pass
55
55
56
56
57 class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase):
57 class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase):
58 host = colander.SchemaNode(
58 host = colander.SchemaNode(
59 colander.String(),
59 colander.String(),
60 default='127.0.0.1',
60 default='127.0.0.1',
61 description=_('The FQDN or IP of the Atlassian CROWD Server'),
61 description=_('The FQDN or IP of the Atlassian CROWD Server'),
62 preparer=strip_whitespace,
62 preparer=strip_whitespace,
63 title=_('Host'),
63 title=_('Host'),
64 widget='string')
64 widget='string')
65 port = colander.SchemaNode(
65 port = colander.SchemaNode(
66 colander.Int(),
66 colander.Int(),
67 default=8095,
67 default=8095,
68 description=_('The Port in use by the Atlassian CROWD Server'),
68 description=_('The Port in use by the Atlassian CROWD Server'),
69 preparer=strip_whitespace,
69 preparer=strip_whitespace,
70 title=_('Port'),
70 title=_('Port'),
71 validator=colander.Range(min=0, max=65536),
71 validator=colander.Range(min=0, max=65536),
72 widget='int')
72 widget='int')
73 app_name = colander.SchemaNode(
73 app_name = colander.SchemaNode(
74 colander.String(),
74 colander.String(),
75 default='',
75 default='',
76 description=_('The Application Name to authenticate to CROWD'),
76 description=_('The Application Name to authenticate to CROWD'),
77 preparer=strip_whitespace,
77 preparer=strip_whitespace,
78 title=_('Application Name'),
78 title=_('Application Name'),
79 widget='string')
79 widget='string')
80 app_password = colander.SchemaNode(
80 app_password = colander.SchemaNode(
81 colander.String(),
81 colander.String(),
82 default='',
82 default='',
83 description=_('The password to authenticate to CROWD'),
83 description=_('The password to authenticate to CROWD'),
84 preparer=strip_whitespace,
84 preparer=strip_whitespace,
85 title=_('Application Password'),
85 title=_('Application Password'),
86 widget='password')
86 widget='password')
87 admin_groups = colander.SchemaNode(
87 admin_groups = colander.SchemaNode(
88 colander.String(),
88 colander.String(),
89 default='',
89 default='',
90 description=_('A comma separated list of group names that identify '
90 description=_('A comma separated list of group names that identify '
91 'users as RhodeCode Administrators'),
91 'users as RhodeCode Administrators'),
92 missing='',
92 missing='',
93 preparer=strip_whitespace,
93 preparer=strip_whitespace,
94 title=_('Admin Groups'),
94 title=_('Admin Groups'),
95 widget='string')
95 widget='string')
96
96
97
97
98 class CrowdServer(object):
98 class CrowdServer(object):
99 def __init__(self, *args, **kwargs):
99 def __init__(self, *args, **kwargs):
100 """
100 """
101 Create a new CrowdServer object that points to IP/Address 'host',
101 Create a new CrowdServer object that points to IP/Address 'host',
102 on the given port, and using the given method (https/http). user and
102 on the given port, and using the given method (https/http). user and
103 passwd can be set here or with set_credentials. If unspecified,
103 passwd can be set here or with set_credentials. If unspecified,
104 "version" defaults to "latest".
104 "version" defaults to "latest".
105
105
106 example::
106 example::
107
107
108 cserver = CrowdServer(host="127.0.0.1",
108 cserver = CrowdServer(host="127.0.0.1",
109 port="8095",
109 port="8095",
110 user="some_app",
110 user="some_app",
111 passwd="some_passwd",
111 passwd="some_passwd",
112 version="1")
112 version="1")
113 """
113 """
114 if not "port" in kwargs:
114 if not "port" in kwargs:
115 kwargs["port"] = "8095"
115 kwargs["port"] = "8095"
116 self._logger = kwargs.get("logger", logging.getLogger(__name__))
116 self._logger = kwargs.get("logger", logging.getLogger(__name__))
117 self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
117 self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
118 kwargs.get("host", "127.0.0.1"),
118 kwargs.get("host", "127.0.0.1"),
119 kwargs.get("port", "8095"))
119 kwargs.get("port", "8095"))
120 self.set_credentials(kwargs.get("user", ""),
120 self.set_credentials(kwargs.get("user", ""),
121 kwargs.get("passwd", ""))
121 kwargs.get("passwd", ""))
122 self._version = kwargs.get("version", "latest")
122 self._version = kwargs.get("version", "latest")
123 self._url_list = None
123 self._url_list = None
124 self._appname = "crowd"
124 self._appname = "crowd"
125
125
126 def set_credentials(self, user, passwd):
126 def set_credentials(self, user, passwd):
127 self.user = user
127 self.user = user
128 self.passwd = passwd
128 self.passwd = passwd
129 self._make_opener()
129 self._make_opener()
130
130
131 def _make_opener(self):
131 def _make_opener(self):
132 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
132 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
133 mgr.add_password(None, self._uri, self.user, self.passwd)
133 mgr.add_password(None, self._uri, self.user, self.passwd)
134 handler = urllib2.HTTPBasicAuthHandler(mgr)
134 handler = urllib2.HTTPBasicAuthHandler(mgr)
135 self.opener = urllib2.build_opener(handler)
135 self.opener = urllib2.build_opener(handler)
136
136
137 def _request(self, url, body=None, headers=None,
137 def _request(self, url, body=None, headers=None,
138 method=None, noformat=False,
138 method=None, noformat=False,
139 empty_response_ok=False):
139 empty_response_ok=False):
140 _headers = {"Content-type": "application/json",
140 _headers = {"Content-type": "application/json",
141 "Accept": "application/json"}
141 "Accept": "application/json"}
142 if self.user and self.passwd:
142 if self.user and self.passwd:
143 authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
143 authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
144 _headers["Authorization"] = "Basic %s" % authstring
144 _headers["Authorization"] = "Basic %s" % authstring
145 if headers:
145 if headers:
146 _headers.update(headers)
146 _headers.update(headers)
147 log.debug("Sent crowd: \n%s"
147 log.debug("Sent crowd: \n%s"
148 % (formatted_json({"url": url, "body": body,
148 % (formatted_json({"url": url, "body": body,
149 "headers": _headers})))
149 "headers": _headers})))
150 request = urllib2.Request(url, body, _headers)
150 request = urllib2.Request(url, body, _headers)
151 if method:
151 if method:
152 request.get_method = lambda: method
152 request.get_method = lambda: method
153
153
154 global msg
154 global msg
155 msg = ""
155 msg = ""
156 try:
156 try:
157 rdoc = self.opener.open(request)
157 rdoc = self.opener.open(request)
158 msg = "".join(rdoc.readlines())
158 msg = "".join(rdoc.readlines())
159 if not msg and empty_response_ok:
159 if not msg and empty_response_ok:
160 rval = {}
160 rval = {}
161 rval["status"] = True
161 rval["status"] = True
162 rval["error"] = "Response body was empty"
162 rval["error"] = "Response body was empty"
163 elif not noformat:
163 elif not noformat:
164 rval = json.loads(msg)
164 rval = json.loads(msg)
165 rval["status"] = True
165 rval["status"] = True
166 else:
166 else:
167 rval = "".join(rdoc.readlines())
167 rval = "".join(rdoc.readlines())
168 except Exception as e:
168 except Exception as e:
169 if not noformat:
169 if not noformat:
170 rval = {"status": False,
170 rval = {"status": False,
171 "body": body,
171 "body": body,
172 "error": str(e) + "\n" + msg}
172 "error": str(e) + "\n" + msg}
173 else:
173 else:
174 rval = None
174 rval = None
175 return rval
175 return rval
176
176
177 def user_auth(self, username, password):
177 def user_auth(self, username, password):
178 """Authenticate a user against crowd. Returns brief information about
178 """Authenticate a user against crowd. Returns brief information about
179 the user."""
179 the user."""
180 url = ("%s/rest/usermanagement/%s/authentication?username=%s"
180 url = ("%s/rest/usermanagement/%s/authentication?username=%s"
181 % (self._uri, self._version, username))
181 % (self._uri, self._version, username))
182 body = json.dumps({"value": password})
182 body = json.dumps({"value": password})
183 return self._request(url, body)
183 return self._request(url, body)
184
184
185 def user_groups(self, username):
185 def user_groups(self, username):
186 """Retrieve a list of groups to which this user belongs."""
186 """Retrieve a list of groups to which this user belongs."""
187 url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
187 url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
188 % (self._uri, self._version, username))
188 % (self._uri, self._version, username))
189 return self._request(url)
189 return self._request(url)
190
190
191
191
192 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
192 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
193
193
194 def includeme(self, config):
194 def includeme(self, config):
195 config.add_authn_plugin(self)
195 config.add_authn_plugin(self)
196 config.add_authn_resource(self.get_id(), CrowdAuthnResource(self))
196 config.add_authn_resource(self.get_id(), CrowdAuthnResource(self))
197 config.add_view(
197 config.add_view(
198 'rhodecode.authentication.views.AuthnPluginViewBase',
198 'rhodecode.authentication.views.AuthnPluginViewBase',
199 attr='settings_get',
199 attr='settings_get',
200 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
200 request_method='GET',
201 request_method='GET',
201 route_name='auth_home',
202 route_name='auth_home',
202 context=CrowdAuthnResource)
203 context=CrowdAuthnResource)
203 config.add_view(
204 config.add_view(
204 'rhodecode.authentication.views.AuthnPluginViewBase',
205 'rhodecode.authentication.views.AuthnPluginViewBase',
205 attr='settings_post',
206 attr='settings_post',
207 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
206 request_method='POST',
208 request_method='POST',
207 route_name='auth_home',
209 route_name='auth_home',
208 context=CrowdAuthnResource)
210 context=CrowdAuthnResource)
209
211
210 def get_settings_schema(self):
212 def get_settings_schema(self):
211 return CrowdSettingsSchema()
213 return CrowdSettingsSchema()
212
214
213 def get_display_name(self):
215 def get_display_name(self):
214 return _('CROWD')
216 return _('CROWD')
215
217
216 @hybrid_property
218 @hybrid_property
217 def name(self):
219 def name(self):
218 return "crowd"
220 return "crowd"
219
221
220 def use_fake_password(self):
222 def use_fake_password(self):
221 return True
223 return True
222
224
223 def user_activation_state(self):
225 def user_activation_state(self):
224 def_user_perms = User.get_default_user().AuthUser.permissions['global']
226 def_user_perms = User.get_default_user().AuthUser.permissions['global']
225 return 'hg.extern_activate.auto' in def_user_perms
227 return 'hg.extern_activate.auto' in def_user_perms
226
228
227 def auth(self, userobj, username, password, settings, **kwargs):
229 def auth(self, userobj, username, password, settings, **kwargs):
228 """
230 """
229 Given a user object (which may be null), username, a plaintext password,
231 Given a user object (which may be null), username, a plaintext password,
230 and a settings object (containing all the keys needed as listed in settings()),
232 and a settings object (containing all the keys needed as listed in settings()),
231 authenticate this user's login attempt.
233 authenticate this user's login attempt.
232
234
233 Return None on failure. On success, return a dictionary of the form:
235 Return None on failure. On success, return a dictionary of the form:
234
236
235 see: RhodeCodeAuthPluginBase.auth_func_attrs
237 see: RhodeCodeAuthPluginBase.auth_func_attrs
236 This is later validated for correctness
238 This is later validated for correctness
237 """
239 """
238 if not username or not password:
240 if not username or not password:
239 log.debug('Empty username or password skipping...')
241 log.debug('Empty username or password skipping...')
240 return None
242 return None
241
243
242 log.debug("Crowd settings: \n%s" % (formatted_json(settings)))
244 log.debug("Crowd settings: \n%s" % (formatted_json(settings)))
243 server = CrowdServer(**settings)
245 server = CrowdServer(**settings)
244 server.set_credentials(settings["app_name"], settings["app_password"])
246 server.set_credentials(settings["app_name"], settings["app_password"])
245 crowd_user = server.user_auth(username, password)
247 crowd_user = server.user_auth(username, password)
246 log.debug("Crowd returned: \n%s" % (formatted_json(crowd_user)))
248 log.debug("Crowd returned: \n%s" % (formatted_json(crowd_user)))
247 if not crowd_user["status"]:
249 if not crowd_user["status"]:
248 return None
250 return None
249
251
250 res = server.user_groups(crowd_user["name"])
252 res = server.user_groups(crowd_user["name"])
251 log.debug("Crowd groups: \n%s" % (formatted_json(res)))
253 log.debug("Crowd groups: \n%s" % (formatted_json(res)))
252 crowd_user["groups"] = [x["name"] for x in res["groups"]]
254 crowd_user["groups"] = [x["name"] for x in res["groups"]]
253
255
254 # old attrs fetched from RhodeCode database
256 # old attrs fetched from RhodeCode database
255 admin = getattr(userobj, 'admin', False)
257 admin = getattr(userobj, 'admin', False)
256 active = getattr(userobj, 'active', True)
258 active = getattr(userobj, 'active', True)
257 email = getattr(userobj, 'email', '')
259 email = getattr(userobj, 'email', '')
258 username = getattr(userobj, 'username', username)
260 username = getattr(userobj, 'username', username)
259 firstname = getattr(userobj, 'firstname', '')
261 firstname = getattr(userobj, 'firstname', '')
260 lastname = getattr(userobj, 'lastname', '')
262 lastname = getattr(userobj, 'lastname', '')
261 extern_type = getattr(userobj, 'extern_type', '')
263 extern_type = getattr(userobj, 'extern_type', '')
262
264
263 user_attrs = {
265 user_attrs = {
264 'username': username,
266 'username': username,
265 'firstname': crowd_user["first-name"] or firstname,
267 'firstname': crowd_user["first-name"] or firstname,
266 'lastname': crowd_user["last-name"] or lastname,
268 'lastname': crowd_user["last-name"] or lastname,
267 'groups': crowd_user["groups"],
269 'groups': crowd_user["groups"],
268 'email': crowd_user["email"] or email,
270 'email': crowd_user["email"] or email,
269 'admin': admin,
271 'admin': admin,
270 'active': active,
272 'active': active,
271 'active_from_extern': crowd_user.get('active'),
273 'active_from_extern': crowd_user.get('active'),
272 'extern_name': crowd_user["name"],
274 'extern_name': crowd_user["name"],
273 'extern_type': extern_type,
275 'extern_type': extern_type,
274 }
276 }
275
277
276 # set an admin if we're in admin_groups of crowd
278 # set an admin if we're in admin_groups of crowd
277 for group in settings["admin_groups"]:
279 for group in settings["admin_groups"]:
278 if group in user_attrs["groups"]:
280 if group in user_attrs["groups"]:
279 user_attrs["admin"] = True
281 user_attrs["admin"] = True
280 log.debug("Final crowd user object: \n%s" % (formatted_json(user_attrs)))
282 log.debug("Final crowd user object: \n%s" % (formatted_json(user_attrs)))
281 log.info('user %s authenticated correctly' % user_attrs['username'])
283 log.info('user %s authenticated correctly' % user_attrs['username'])
282 return user_attrs
284 return user_attrs
@@ -1,223 +1,225 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22 import logging
22 import logging
23
23
24 from sqlalchemy.ext.hybrid import hybrid_property
24 from sqlalchemy.ext.hybrid import hybrid_property
25
25
26 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
26 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
28 from rhodecode.authentication.routes import AuthnPluginResourceBase
28 from rhodecode.authentication.routes import AuthnPluginResourceBase
29 from rhodecode.lib.colander_utils import strip_whitespace
29 from rhodecode.lib.colander_utils import strip_whitespace
30 from rhodecode.lib.utils2 import str2bool, safe_unicode
30 from rhodecode.lib.utils2 import str2bool, safe_unicode
31 from rhodecode.model.db import User
31 from rhodecode.model.db import User
32 from rhodecode.translation import _
32 from rhodecode.translation import _
33
33
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 def plugin_factory(plugin_id, *args, **kwds):
38 def plugin_factory(plugin_id, *args, **kwds):
39 """
39 """
40 Factory function that is called during plugin discovery.
40 Factory function that is called during plugin discovery.
41 It returns the plugin instance.
41 It returns the plugin instance.
42 """
42 """
43 plugin = RhodeCodeAuthPlugin(plugin_id)
43 plugin = RhodeCodeAuthPlugin(plugin_id)
44 return plugin
44 return plugin
45
45
46
46
47 class HeadersAuthnResource(AuthnPluginResourceBase):
47 class HeadersAuthnResource(AuthnPluginResourceBase):
48 pass
48 pass
49
49
50
50
51 class HeadersSettingsSchema(AuthnPluginSettingsSchemaBase):
51 class HeadersSettingsSchema(AuthnPluginSettingsSchemaBase):
52 header = colander.SchemaNode(
52 header = colander.SchemaNode(
53 colander.String(),
53 colander.String(),
54 default='REMOTE_USER',
54 default='REMOTE_USER',
55 description=_('Header to extract the user from'),
55 description=_('Header to extract the user from'),
56 preparer=strip_whitespace,
56 preparer=strip_whitespace,
57 title=_('Header'),
57 title=_('Header'),
58 widget='string')
58 widget='string')
59 fallback_header = colander.SchemaNode(
59 fallback_header = colander.SchemaNode(
60 colander.String(),
60 colander.String(),
61 default='HTTP_X_FORWARDED_USER',
61 default='HTTP_X_FORWARDED_USER',
62 description=_('Header to extract the user from when main one fails'),
62 description=_('Header to extract the user from when main one fails'),
63 preparer=strip_whitespace,
63 preparer=strip_whitespace,
64 title=_('Fallback header'),
64 title=_('Fallback header'),
65 widget='string')
65 widget='string')
66 clean_username = colander.SchemaNode(
66 clean_username = colander.SchemaNode(
67 colander.Boolean(),
67 colander.Boolean(),
68 default=True,
68 default=True,
69 description=_('Perform cleaning of user, if passed user has @ in '
69 description=_('Perform cleaning of user, if passed user has @ in '
70 'username then first part before @ is taken. '
70 'username then first part before @ is taken. '
71 'If there\'s \\ in the username only the part after '
71 'If there\'s \\ in the username only the part after '
72 ' \\ is taken'),
72 ' \\ is taken'),
73 missing=False,
73 missing=False,
74 title=_('Clean username'),
74 title=_('Clean username'),
75 widget='bool')
75 widget='bool')
76
76
77
77
78 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
78 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
79
79
80 def includeme(self, config):
80 def includeme(self, config):
81 config.add_authn_plugin(self)
81 config.add_authn_plugin(self)
82 config.add_authn_resource(self.get_id(), HeadersAuthnResource(self))
82 config.add_authn_resource(self.get_id(), HeadersAuthnResource(self))
83 config.add_view(
83 config.add_view(
84 'rhodecode.authentication.views.AuthnPluginViewBase',
84 'rhodecode.authentication.views.AuthnPluginViewBase',
85 attr='settings_get',
85 attr='settings_get',
86 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
86 request_method='GET',
87 request_method='GET',
87 route_name='auth_home',
88 route_name='auth_home',
88 context=HeadersAuthnResource)
89 context=HeadersAuthnResource)
89 config.add_view(
90 config.add_view(
90 'rhodecode.authentication.views.AuthnPluginViewBase',
91 'rhodecode.authentication.views.AuthnPluginViewBase',
91 attr='settings_post',
92 attr='settings_post',
93 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
92 request_method='POST',
94 request_method='POST',
93 route_name='auth_home',
95 route_name='auth_home',
94 context=HeadersAuthnResource)
96 context=HeadersAuthnResource)
95
97
96 def get_display_name(self):
98 def get_display_name(self):
97 return _('Headers')
99 return _('Headers')
98
100
99 def get_settings_schema(self):
101 def get_settings_schema(self):
100 return HeadersSettingsSchema()
102 return HeadersSettingsSchema()
101
103
102 @hybrid_property
104 @hybrid_property
103 def name(self):
105 def name(self):
104 return 'headers'
106 return 'headers'
105
107
106 @hybrid_property
108 @hybrid_property
107 def is_container_auth(self):
109 def is_container_auth(self):
108 return True
110 return True
109
111
110 def use_fake_password(self):
112 def use_fake_password(self):
111 return True
113 return True
112
114
113 def user_activation_state(self):
115 def user_activation_state(self):
114 def_user_perms = User.get_default_user().AuthUser.permissions['global']
116 def_user_perms = User.get_default_user().AuthUser.permissions['global']
115 return 'hg.extern_activate.auto' in def_user_perms
117 return 'hg.extern_activate.auto' in def_user_perms
116
118
117 def _clean_username(self, username):
119 def _clean_username(self, username):
118 # Removing realm and domain from username
120 # Removing realm and domain from username
119 username = username.split('@')[0]
121 username = username.split('@')[0]
120 username = username.rsplit('\\')[-1]
122 username = username.rsplit('\\')[-1]
121 return username
123 return username
122
124
123 def _get_username(self, environ, settings):
125 def _get_username(self, environ, settings):
124 username = None
126 username = None
125 environ = environ or {}
127 environ = environ or {}
126 if not environ:
128 if not environ:
127 log.debug('got empty environ: %s' % environ)
129 log.debug('got empty environ: %s' % environ)
128
130
129 settings = settings or {}
131 settings = settings or {}
130 if settings.get('header'):
132 if settings.get('header'):
131 header = settings.get('header')
133 header = settings.get('header')
132 username = environ.get(header)
134 username = environ.get(header)
133 log.debug('extracted %s:%s' % (header, username))
135 log.debug('extracted %s:%s' % (header, username))
134
136
135 # fallback mode
137 # fallback mode
136 if not username and settings.get('fallback_header'):
138 if not username and settings.get('fallback_header'):
137 header = settings.get('fallback_header')
139 header = settings.get('fallback_header')
138 username = environ.get(header)
140 username = environ.get(header)
139 log.debug('extracted %s:%s' % (header, username))
141 log.debug('extracted %s:%s' % (header, username))
140
142
141 if username and str2bool(settings.get('clean_username')):
143 if username and str2bool(settings.get('clean_username')):
142 log.debug('Received username `%s` from headers' % username)
144 log.debug('Received username `%s` from headers' % username)
143 username = self._clean_username(username)
145 username = self._clean_username(username)
144 log.debug('New cleanup user is:%s' % username)
146 log.debug('New cleanup user is:%s' % username)
145 return username
147 return username
146
148
147 def get_user(self, username=None, **kwargs):
149 def get_user(self, username=None, **kwargs):
148 """
150 """
149 Helper method for user fetching in plugins, by default it's using
151 Helper method for user fetching in plugins, by default it's using
150 simple fetch by username, but this method can be custimized in plugins
152 simple fetch by username, but this method can be custimized in plugins
151 eg. headers auth plugin to fetch user by environ params
153 eg. headers auth plugin to fetch user by environ params
152 :param username: username if given to fetch
154 :param username: username if given to fetch
153 :param kwargs: extra arguments needed for user fetching.
155 :param kwargs: extra arguments needed for user fetching.
154 """
156 """
155 environ = kwargs.get('environ') or {}
157 environ = kwargs.get('environ') or {}
156 settings = kwargs.get('settings') or {}
158 settings = kwargs.get('settings') or {}
157 username = self._get_username(environ, settings)
159 username = self._get_username(environ, settings)
158 # we got the username, so use default method now
160 # we got the username, so use default method now
159 return super(RhodeCodeAuthPlugin, self).get_user(username)
161 return super(RhodeCodeAuthPlugin, self).get_user(username)
160
162
161 def auth(self, userobj, username, password, settings, **kwargs):
163 def auth(self, userobj, username, password, settings, **kwargs):
162 """
164 """
163 Get's the headers_auth username (or email). It tries to get username
165 Get's the headers_auth username (or email). It tries to get username
164 from REMOTE_USER if this plugin is enabled, if that fails
166 from REMOTE_USER if this plugin is enabled, if that fails
165 it tries to get username from HTTP_X_FORWARDED_USER if fallback header
167 it tries to get username from HTTP_X_FORWARDED_USER if fallback header
166 is set. clean_username extracts the username from this data if it's
168 is set. clean_username extracts the username from this data if it's
167 having @ in it.
169 having @ in it.
168 Return None on failure. On success, return a dictionary of the form:
170 Return None on failure. On success, return a dictionary of the form:
169
171
170 see: RhodeCodeAuthPluginBase.auth_func_attrs
172 see: RhodeCodeAuthPluginBase.auth_func_attrs
171
173
172 :param userobj:
174 :param userobj:
173 :param username:
175 :param username:
174 :param password:
176 :param password:
175 :param settings:
177 :param settings:
176 :param kwargs:
178 :param kwargs:
177 """
179 """
178 environ = kwargs.get('environ')
180 environ = kwargs.get('environ')
179 if not environ:
181 if not environ:
180 log.debug('Empty environ data skipping...')
182 log.debug('Empty environ data skipping...')
181 return None
183 return None
182
184
183 if not userobj:
185 if not userobj:
184 userobj = self.get_user('', environ=environ, settings=settings)
186 userobj = self.get_user('', environ=environ, settings=settings)
185
187
186 # we don't care passed username/password for headers auth plugins.
188 # we don't care passed username/password for headers auth plugins.
187 # only way to log in is using environ
189 # only way to log in is using environ
188 username = None
190 username = None
189 if userobj:
191 if userobj:
190 username = getattr(userobj, 'username')
192 username = getattr(userobj, 'username')
191
193
192 if not username:
194 if not username:
193 # we don't have any objects in DB user doesn't exist extract
195 # we don't have any objects in DB user doesn't exist extract
194 # username from environ based on the settings
196 # username from environ based on the settings
195 username = self._get_username(environ, settings)
197 username = self._get_username(environ, settings)
196
198
197 # if cannot fetch username, it's a no-go for this plugin to proceed
199 # if cannot fetch username, it's a no-go for this plugin to proceed
198 if not username:
200 if not username:
199 return None
201 return None
200
202
201 # old attrs fetched from RhodeCode database
203 # old attrs fetched from RhodeCode database
202 admin = getattr(userobj, 'admin', False)
204 admin = getattr(userobj, 'admin', False)
203 active = getattr(userobj, 'active', True)
205 active = getattr(userobj, 'active', True)
204 email = getattr(userobj, 'email', '')
206 email = getattr(userobj, 'email', '')
205 firstname = getattr(userobj, 'firstname', '')
207 firstname = getattr(userobj, 'firstname', '')
206 lastname = getattr(userobj, 'lastname', '')
208 lastname = getattr(userobj, 'lastname', '')
207 extern_type = getattr(userobj, 'extern_type', '')
209 extern_type = getattr(userobj, 'extern_type', '')
208
210
209 user_attrs = {
211 user_attrs = {
210 'username': username,
212 'username': username,
211 'firstname': safe_unicode(firstname or username),
213 'firstname': safe_unicode(firstname or username),
212 'lastname': safe_unicode(lastname or ''),
214 'lastname': safe_unicode(lastname or ''),
213 'groups': [],
215 'groups': [],
214 'email': email or '',
216 'email': email or '',
215 'admin': admin or False,
217 'admin': admin or False,
216 'active': active,
218 'active': active,
217 'active_from_extern': True,
219 'active_from_extern': True,
218 'extern_name': username,
220 'extern_name': username,
219 'extern_type': extern_type,
221 'extern_type': extern_type,
220 }
222 }
221
223
222 log.info('user `%s` authenticated correctly' % user_attrs['username'])
224 log.info('user `%s` authenticated correctly' % user_attrs['username'])
223 return user_attrs
225 return user_attrs
@@ -1,165 +1,167 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for Jasig CAS
22 RhodeCode authentication plugin for Jasig CAS
23 http://www.jasig.org/cas
23 http://www.jasig.org/cas
24 """
24 """
25
25
26
26
27 import colander
27 import colander
28 import logging
28 import logging
29 import rhodecode
29 import rhodecode
30 import urllib
30 import urllib
31 import urllib2
31 import urllib2
32
32
33 from pylons.i18n.translation import lazy_ugettext as _
33 from pylons.i18n.translation import lazy_ugettext as _
34 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.ext.hybrid import hybrid_property
35
35
36 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
36 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 from rhodecode.authentication.routes import AuthnPluginResourceBase
38 from rhodecode.authentication.routes import AuthnPluginResourceBase
39 from rhodecode.lib.colander_utils import strip_whitespace
39 from rhodecode.lib.colander_utils import strip_whitespace
40 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.lib.utils2 import safe_unicode
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 def plugin_factory(plugin_id, *args, **kwds):
46 def plugin_factory(plugin_id, *args, **kwds):
47 """
47 """
48 Factory function that is called during plugin discovery.
48 Factory function that is called during plugin discovery.
49 It returns the plugin instance.
49 It returns the plugin instance.
50 """
50 """
51 plugin = RhodeCodeAuthPlugin(plugin_id)
51 plugin = RhodeCodeAuthPlugin(plugin_id)
52 return plugin
52 return plugin
53
53
54
54
55 class JasigCasAuthnResource(AuthnPluginResourceBase):
55 class JasigCasAuthnResource(AuthnPluginResourceBase):
56 pass
56 pass
57
57
58
58
59 class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase):
59 class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase):
60 service_url = colander.SchemaNode(
60 service_url = colander.SchemaNode(
61 colander.String(),
61 colander.String(),
62 default='https://domain.com/cas/v1/tickets',
62 default='https://domain.com/cas/v1/tickets',
63 description=_('The url of the Jasig CAS REST service'),
63 description=_('The url of the Jasig CAS REST service'),
64 preparer=strip_whitespace,
64 preparer=strip_whitespace,
65 title=_('URL'),
65 title=_('URL'),
66 widget='string')
66 widget='string')
67
67
68
68
69 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
69 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
70
70
71 def includeme(self, config):
71 def includeme(self, config):
72 config.add_authn_plugin(self)
72 config.add_authn_plugin(self)
73 config.add_authn_resource(self.get_id(), JasigCasAuthnResource(self))
73 config.add_authn_resource(self.get_id(), JasigCasAuthnResource(self))
74 config.add_view(
74 config.add_view(
75 'rhodecode.authentication.views.AuthnPluginViewBase',
75 'rhodecode.authentication.views.AuthnPluginViewBase',
76 attr='settings_get',
76 attr='settings_get',
77 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
77 request_method='GET',
78 request_method='GET',
78 route_name='auth_home',
79 route_name='auth_home',
79 context=JasigCasAuthnResource)
80 context=JasigCasAuthnResource)
80 config.add_view(
81 config.add_view(
81 'rhodecode.authentication.views.AuthnPluginViewBase',
82 'rhodecode.authentication.views.AuthnPluginViewBase',
82 attr='settings_post',
83 attr='settings_post',
84 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
83 request_method='POST',
85 request_method='POST',
84 route_name='auth_home',
86 route_name='auth_home',
85 context=JasigCasAuthnResource)
87 context=JasigCasAuthnResource)
86
88
87 def get_settings_schema(self):
89 def get_settings_schema(self):
88 return JasigCasSettingsSchema()
90 return JasigCasSettingsSchema()
89
91
90 def get_display_name(self):
92 def get_display_name(self):
91 return _('Jasig-CAS')
93 return _('Jasig-CAS')
92
94
93 @hybrid_property
95 @hybrid_property
94 def name(self):
96 def name(self):
95 return "jasig-cas"
97 return "jasig-cas"
96
98
97 @hybrid_property
99 @hybrid_property
98 def is_container_auth(self):
100 def is_container_auth(self):
99 return True
101 return True
100
102
101 def use_fake_password(self):
103 def use_fake_password(self):
102 return True
104 return True
103
105
104 def user_activation_state(self):
106 def user_activation_state(self):
105 def_user_perms = User.get_default_user().AuthUser.permissions['global']
107 def_user_perms = User.get_default_user().AuthUser.permissions['global']
106 return 'hg.extern_activate.auto' in def_user_perms
108 return 'hg.extern_activate.auto' in def_user_perms
107
109
108 def auth(self, userobj, username, password, settings, **kwargs):
110 def auth(self, userobj, username, password, settings, **kwargs):
109 """
111 """
110 Given a user object (which may be null), username, a plaintext password,
112 Given a user object (which may be null), username, a plaintext password,
111 and a settings object (containing all the keys needed as listed in settings()),
113 and a settings object (containing all the keys needed as listed in settings()),
112 authenticate this user's login attempt.
114 authenticate this user's login attempt.
113
115
114 Return None on failure. On success, return a dictionary of the form:
116 Return None on failure. On success, return a dictionary of the form:
115
117
116 see: RhodeCodeAuthPluginBase.auth_func_attrs
118 see: RhodeCodeAuthPluginBase.auth_func_attrs
117 This is later validated for correctness
119 This is later validated for correctness
118 """
120 """
119 if not username or not password:
121 if not username or not password:
120 log.debug('Empty username or password skipping...')
122 log.debug('Empty username or password skipping...')
121 return None
123 return None
122
124
123 log.debug("Jasig CAS settings: %s", settings)
125 log.debug("Jasig CAS settings: %s", settings)
124 params = urllib.urlencode({'username': username, 'password': password})
126 params = urllib.urlencode({'username': username, 'password': password})
125 headers = {"Content-type": "application/x-www-form-urlencoded",
127 headers = {"Content-type": "application/x-www-form-urlencoded",
126 "Accept": "text/plain",
128 "Accept": "text/plain",
127 "User-Agent": "RhodeCode-auth-%s" % rhodecode.__version__}
129 "User-Agent": "RhodeCode-auth-%s" % rhodecode.__version__}
128 url = settings["service_url"]
130 url = settings["service_url"]
129
131
130 log.debug("Sent Jasig CAS: \n%s",
132 log.debug("Sent Jasig CAS: \n%s",
131 {"url": url, "body": params, "headers": headers})
133 {"url": url, "body": params, "headers": headers})
132 request = urllib2.Request(url, params, headers)
134 request = urllib2.Request(url, params, headers)
133 try:
135 try:
134 response = urllib2.urlopen(request)
136 response = urllib2.urlopen(request)
135 except urllib2.HTTPError as e:
137 except urllib2.HTTPError as e:
136 log.debug("HTTPError when requesting Jasig CAS (status code: %d)" % e.code)
138 log.debug("HTTPError when requesting Jasig CAS (status code: %d)" % e.code)
137 return None
139 return None
138 except urllib2.URLError as e:
140 except urllib2.URLError as e:
139 log.debug("URLError when requesting Jasig CAS url: %s " % url)
141 log.debug("URLError when requesting Jasig CAS url: %s " % url)
140 return None
142 return None
141
143
142 # old attrs fetched from RhodeCode database
144 # old attrs fetched from RhodeCode database
143 admin = getattr(userobj, 'admin', False)
145 admin = getattr(userobj, 'admin', False)
144 active = getattr(userobj, 'active', True)
146 active = getattr(userobj, 'active', True)
145 email = getattr(userobj, 'email', '')
147 email = getattr(userobj, 'email', '')
146 username = getattr(userobj, 'username', username)
148 username = getattr(userobj, 'username', username)
147 firstname = getattr(userobj, 'firstname', '')
149 firstname = getattr(userobj, 'firstname', '')
148 lastname = getattr(userobj, 'lastname', '')
150 lastname = getattr(userobj, 'lastname', '')
149 extern_type = getattr(userobj, 'extern_type', '')
151 extern_type = getattr(userobj, 'extern_type', '')
150
152
151 user_attrs = {
153 user_attrs = {
152 'username': username,
154 'username': username,
153 'firstname': safe_unicode(firstname or username),
155 'firstname': safe_unicode(firstname or username),
154 'lastname': safe_unicode(lastname or ''),
156 'lastname': safe_unicode(lastname or ''),
155 'groups': [],
157 'groups': [],
156 'email': email or '',
158 'email': email or '',
157 'admin': admin or False,
159 'admin': admin or False,
158 'active': active,
160 'active': active,
159 'active_from_extern': True,
161 'active_from_extern': True,
160 'extern_name': username,
162 'extern_name': username,
161 'extern_type': extern_type,
163 'extern_type': extern_type,
162 }
164 }
163
165
164 log.info('user %s authenticated correctly' % user_attrs['username'])
166 log.info('user %s authenticated correctly' % user_attrs['username'])
165 return user_attrs
167 return user_attrs
@@ -1,458 +1,460 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for LDAP
22 RhodeCode authentication plugin for LDAP
23 """
23 """
24
24
25
25
26 import colander
26 import colander
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from pylons.i18n.translation import lazy_ugettext as _
30 from pylons.i18n.translation import lazy_ugettext as _
31 from sqlalchemy.ext.hybrid import hybrid_property
31 from sqlalchemy.ext.hybrid import hybrid_property
32
32
33 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
33 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 from rhodecode.authentication.routes import AuthnPluginResourceBase
35 from rhodecode.authentication.routes import AuthnPluginResourceBase
36 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.lib.colander_utils import strip_whitespace
37 from rhodecode.lib.exceptions import (
37 from rhodecode.lib.exceptions import (
38 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
38 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
39 )
39 )
40 from rhodecode.lib.utils2 import safe_unicode, safe_str
40 from rhodecode.lib.utils2 import safe_unicode, safe_str
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42 from rhodecode.model.validators import Missing
42 from rhodecode.model.validators import Missing
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46 try:
46 try:
47 import ldap
47 import ldap
48 except ImportError:
48 except ImportError:
49 # means that python-ldap is not installed
49 # means that python-ldap is not installed
50 ldap = Missing()
50 ldap = Missing()
51
51
52
52
53 def plugin_factory(plugin_id, *args, **kwds):
53 def plugin_factory(plugin_id, *args, **kwds):
54 """
54 """
55 Factory function that is called during plugin discovery.
55 Factory function that is called during plugin discovery.
56 It returns the plugin instance.
56 It returns the plugin instance.
57 """
57 """
58 plugin = RhodeCodeAuthPlugin(plugin_id)
58 plugin = RhodeCodeAuthPlugin(plugin_id)
59 return plugin
59 return plugin
60
60
61
61
62 class LdapAuthnResource(AuthnPluginResourceBase):
62 class LdapAuthnResource(AuthnPluginResourceBase):
63 pass
63 pass
64
64
65
65
66 class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
66 class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
67 tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS']
67 tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS']
68 tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD']
68 tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD']
69 search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE']
69 search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE']
70
70
71 host = colander.SchemaNode(
71 host = colander.SchemaNode(
72 colander.String(),
72 colander.String(),
73 default='',
73 default='',
74 description=_('Host of the LDAP Server'),
74 description=_('Host of the LDAP Server'),
75 preparer=strip_whitespace,
75 preparer=strip_whitespace,
76 title=_('LDAP Host'),
76 title=_('LDAP Host'),
77 widget='string')
77 widget='string')
78 port = colander.SchemaNode(
78 port = colander.SchemaNode(
79 colander.Int(),
79 colander.Int(),
80 default=389,
80 default=389,
81 description=_('Port that the LDAP server is listening on'),
81 description=_('Port that the LDAP server is listening on'),
82 preparer=strip_whitespace,
82 preparer=strip_whitespace,
83 title=_('Port'),
83 title=_('Port'),
84 validator=colander.Range(min=0, max=65536),
84 validator=colander.Range(min=0, max=65536),
85 widget='int')
85 widget='int')
86 dn_user = colander.SchemaNode(
86 dn_user = colander.SchemaNode(
87 colander.String(),
87 colander.String(),
88 default='',
88 default='',
89 description=_('User to connect to LDAP'),
89 description=_('User to connect to LDAP'),
90 missing='',
90 missing='',
91 preparer=strip_whitespace,
91 preparer=strip_whitespace,
92 title=_('Account'),
92 title=_('Account'),
93 widget='string')
93 widget='string')
94 dn_pass = colander.SchemaNode(
94 dn_pass = colander.SchemaNode(
95 colander.String(),
95 colander.String(),
96 default='',
96 default='',
97 description=_('Password to connect to LDAP'),
97 description=_('Password to connect to LDAP'),
98 missing='',
98 missing='',
99 preparer=strip_whitespace,
99 preparer=strip_whitespace,
100 title=_('Password'),
100 title=_('Password'),
101 widget='password')
101 widget='password')
102 tls_kind = colander.SchemaNode(
102 tls_kind = colander.SchemaNode(
103 colander.String(),
103 colander.String(),
104 default=tls_kind_choices[0],
104 default=tls_kind_choices[0],
105 description=_('TLS Type'),
105 description=_('TLS Type'),
106 title=_('Connection Security'),
106 title=_('Connection Security'),
107 validator=colander.OneOf(tls_kind_choices),
107 validator=colander.OneOf(tls_kind_choices),
108 widget='select')
108 widget='select')
109 tls_reqcert = colander.SchemaNode(
109 tls_reqcert = colander.SchemaNode(
110 colander.String(),
110 colander.String(),
111 default=tls_reqcert_choices[0],
111 default=tls_reqcert_choices[0],
112 description=_('Require Cert over TLS?'),
112 description=_('Require Cert over TLS?'),
113 title=_('Certificate Checks'),
113 title=_('Certificate Checks'),
114 validator=colander.OneOf(tls_reqcert_choices),
114 validator=colander.OneOf(tls_reqcert_choices),
115 widget='select')
115 widget='select')
116 base_dn = colander.SchemaNode(
116 base_dn = colander.SchemaNode(
117 colander.String(),
117 colander.String(),
118 default='',
118 default='',
119 description=_('Base DN to search (e.g., dc=mydomain,dc=com)'),
119 description=_('Base DN to search (e.g., dc=mydomain,dc=com)'),
120 missing='',
120 missing='',
121 preparer=strip_whitespace,
121 preparer=strip_whitespace,
122 title=_('Base DN'),
122 title=_('Base DN'),
123 widget='string')
123 widget='string')
124 filter = colander.SchemaNode(
124 filter = colander.SchemaNode(
125 colander.String(),
125 colander.String(),
126 default='',
126 default='',
127 description=_('Filter to narrow results (e.g., ou=Users, etc)'),
127 description=_('Filter to narrow results (e.g., ou=Users, etc)'),
128 missing='',
128 missing='',
129 preparer=strip_whitespace,
129 preparer=strip_whitespace,
130 title=_('LDAP Search Filter'),
130 title=_('LDAP Search Filter'),
131 widget='string')
131 widget='string')
132 search_scope = colander.SchemaNode(
132 search_scope = colander.SchemaNode(
133 colander.String(),
133 colander.String(),
134 default=search_scope_choices[0],
134 default=search_scope_choices[0],
135 description=_('How deep to search LDAP'),
135 description=_('How deep to search LDAP'),
136 title=_('LDAP Search Scope'),
136 title=_('LDAP Search Scope'),
137 validator=colander.OneOf(search_scope_choices),
137 validator=colander.OneOf(search_scope_choices),
138 widget='select')
138 widget='select')
139 attr_login = colander.SchemaNode(
139 attr_login = colander.SchemaNode(
140 colander.String(),
140 colander.String(),
141 default='',
141 default='',
142 description=_('LDAP Attribute to map to user name'),
142 description=_('LDAP Attribute to map to user name'),
143 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
143 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
144 preparer=strip_whitespace,
144 preparer=strip_whitespace,
145 title=_('Login Attribute'),
145 title=_('Login Attribute'),
146 widget='string')
146 widget='string')
147 attr_firstname = colander.SchemaNode(
147 attr_firstname = colander.SchemaNode(
148 colander.String(),
148 colander.String(),
149 default='',
149 default='',
150 description=_('LDAP Attribute to map to first name'),
150 description=_('LDAP Attribute to map to first name'),
151 missing='',
151 missing='',
152 preparer=strip_whitespace,
152 preparer=strip_whitespace,
153 title=_('First Name Attribute'),
153 title=_('First Name Attribute'),
154 widget='string')
154 widget='string')
155 attr_lastname = colander.SchemaNode(
155 attr_lastname = colander.SchemaNode(
156 colander.String(),
156 colander.String(),
157 default='',
157 default='',
158 description=_('LDAP Attribute to map to last name'),
158 description=_('LDAP Attribute to map to last name'),
159 missing='',
159 missing='',
160 preparer=strip_whitespace,
160 preparer=strip_whitespace,
161 title=_('Last Name Attribute'),
161 title=_('Last Name Attribute'),
162 widget='string')
162 widget='string')
163 attr_email = colander.SchemaNode(
163 attr_email = colander.SchemaNode(
164 colander.String(),
164 colander.String(),
165 default='',
165 default='',
166 description=_('LDAP Attribute to map to email address'),
166 description=_('LDAP Attribute to map to email address'),
167 missing='',
167 missing='',
168 preparer=strip_whitespace,
168 preparer=strip_whitespace,
169 title=_('Email Attribute'),
169 title=_('Email Attribute'),
170 widget='string')
170 widget='string')
171
171
172
172
173 class AuthLdap(object):
173 class AuthLdap(object):
174
174
175 def _build_servers(self):
175 def _build_servers(self):
176 return ', '.join(
176 return ', '.join(
177 ["{}://{}:{}".format(
177 ["{}://{}:{}".format(
178 self.ldap_server_type, host.strip(), self.LDAP_SERVER_PORT)
178 self.ldap_server_type, host.strip(), self.LDAP_SERVER_PORT)
179 for host in self.SERVER_ADDRESSES])
179 for host in self.SERVER_ADDRESSES])
180
180
181 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
181 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
182 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
182 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
183 search_scope='SUBTREE', attr_login='uid',
183 search_scope='SUBTREE', attr_login='uid',
184 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))'):
184 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))'):
185 if isinstance(ldap, Missing):
185 if isinstance(ldap, Missing):
186 raise LdapImportError("Missing or incompatible ldap library")
186 raise LdapImportError("Missing or incompatible ldap library")
187
187
188 self.ldap_version = ldap_version
188 self.ldap_version = ldap_version
189 self.ldap_server_type = 'ldap'
189 self.ldap_server_type = 'ldap'
190
190
191 self.TLS_KIND = tls_kind
191 self.TLS_KIND = tls_kind
192
192
193 if self.TLS_KIND == 'LDAPS':
193 if self.TLS_KIND == 'LDAPS':
194 port = port or 689
194 port = port or 689
195 self.ldap_server_type += 's'
195 self.ldap_server_type += 's'
196
196
197 OPT_X_TLS_DEMAND = 2
197 OPT_X_TLS_DEMAND = 2
198 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
198 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
199 OPT_X_TLS_DEMAND)
199 OPT_X_TLS_DEMAND)
200 # split server into list
200 # split server into list
201 self.SERVER_ADDRESSES = server.split(',')
201 self.SERVER_ADDRESSES = server.split(',')
202 self.LDAP_SERVER_PORT = port
202 self.LDAP_SERVER_PORT = port
203
203
204 # USE FOR READ ONLY BIND TO LDAP SERVER
204 # USE FOR READ ONLY BIND TO LDAP SERVER
205 self.attr_login = attr_login
205 self.attr_login = attr_login
206
206
207 self.LDAP_BIND_DN = safe_str(bind_dn)
207 self.LDAP_BIND_DN = safe_str(bind_dn)
208 self.LDAP_BIND_PASS = safe_str(bind_pass)
208 self.LDAP_BIND_PASS = safe_str(bind_pass)
209 self.LDAP_SERVER = self._build_servers()
209 self.LDAP_SERVER = self._build_servers()
210 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
210 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
211 self.BASE_DN = safe_str(base_dn)
211 self.BASE_DN = safe_str(base_dn)
212 self.LDAP_FILTER = safe_str(ldap_filter)
212 self.LDAP_FILTER = safe_str(ldap_filter)
213
213
214 def _get_ldap_server(self):
214 def _get_ldap_server(self):
215 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
215 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
216 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
216 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
217 '/etc/openldap/cacerts')
217 '/etc/openldap/cacerts')
218 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
218 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
219 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
219 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
220 ldap.set_option(ldap.OPT_TIMEOUT, 20)
220 ldap.set_option(ldap.OPT_TIMEOUT, 20)
221 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
221 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
222 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
222 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
223 if self.TLS_KIND != 'PLAIN':
223 if self.TLS_KIND != 'PLAIN':
224 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
224 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
225 server = ldap.initialize(self.LDAP_SERVER)
225 server = ldap.initialize(self.LDAP_SERVER)
226 if self.ldap_version == 2:
226 if self.ldap_version == 2:
227 server.protocol = ldap.VERSION2
227 server.protocol = ldap.VERSION2
228 else:
228 else:
229 server.protocol = ldap.VERSION3
229 server.protocol = ldap.VERSION3
230
230
231 if self.TLS_KIND == 'START_TLS':
231 if self.TLS_KIND == 'START_TLS':
232 server.start_tls_s()
232 server.start_tls_s()
233
233
234 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
234 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
235 log.debug('Trying simple_bind with password and given DN: %s',
235 log.debug('Trying simple_bind with password and given DN: %s',
236 self.LDAP_BIND_DN)
236 self.LDAP_BIND_DN)
237 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
237 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
238
238
239 return server
239 return server
240
240
241 def get_uid(self, username):
241 def get_uid(self, username):
242 from rhodecode.lib.helpers import chop_at
242 from rhodecode.lib.helpers import chop_at
243 uid = username
243 uid = username
244 for server_addr in self.SERVER_ADDRESSES:
244 for server_addr in self.SERVER_ADDRESSES:
245 uid = chop_at(username, "@%s" % server_addr)
245 uid = chop_at(username, "@%s" % server_addr)
246 return uid
246 return uid
247
247
248 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
248 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
249 try:
249 try:
250 log.debug('Trying simple bind with %s', dn)
250 log.debug('Trying simple bind with %s', dn)
251 server.simple_bind_s(dn, safe_str(password))
251 server.simple_bind_s(dn, safe_str(password))
252 user = server.search_ext_s(
252 user = server.search_ext_s(
253 dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0]
253 dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0]
254 _, attrs = user
254 _, attrs = user
255 return attrs
255 return attrs
256
256
257 except ldap.INVALID_CREDENTIALS:
257 except ldap.INVALID_CREDENTIALS:
258 log.debug(
258 log.debug(
259 "LDAP rejected password for user '%s': %s, org_exc:",
259 "LDAP rejected password for user '%s': %s, org_exc:",
260 username, dn, exc_info=True)
260 username, dn, exc_info=True)
261
261
262 def authenticate_ldap(self, username, password):
262 def authenticate_ldap(self, username, password):
263 """
263 """
264 Authenticate a user via LDAP and return his/her LDAP properties.
264 Authenticate a user via LDAP and return his/her LDAP properties.
265
265
266 Raises AuthenticationError if the credentials are rejected, or
266 Raises AuthenticationError if the credentials are rejected, or
267 EnvironmentError if the LDAP server can't be reached.
267 EnvironmentError if the LDAP server can't be reached.
268
268
269 :param username: username
269 :param username: username
270 :param password: password
270 :param password: password
271 """
271 """
272
272
273 uid = self.get_uid(username)
273 uid = self.get_uid(username)
274
274
275 if not password:
275 if not password:
276 msg = "Authenticating user %s with blank password not allowed"
276 msg = "Authenticating user %s with blank password not allowed"
277 log.warning(msg, username)
277 log.warning(msg, username)
278 raise LdapPasswordError(msg)
278 raise LdapPasswordError(msg)
279 if "," in username:
279 if "," in username:
280 raise LdapUsernameError("invalid character in username: ,")
280 raise LdapUsernameError("invalid character in username: ,")
281 try:
281 try:
282 server = self._get_ldap_server()
282 server = self._get_ldap_server()
283 filter_ = '(&%s(%s=%s))' % (
283 filter_ = '(&%s(%s=%s))' % (
284 self.LDAP_FILTER, self.attr_login, username)
284 self.LDAP_FILTER, self.attr_login, username)
285 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
285 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
286 filter_, self.LDAP_SERVER)
286 filter_, self.LDAP_SERVER)
287 lobjects = server.search_ext_s(
287 lobjects = server.search_ext_s(
288 self.BASE_DN, self.SEARCH_SCOPE, filter_)
288 self.BASE_DN, self.SEARCH_SCOPE, filter_)
289
289
290 if not lobjects:
290 if not lobjects:
291 raise ldap.NO_SUCH_OBJECT()
291 raise ldap.NO_SUCH_OBJECT()
292
292
293 for (dn, _attrs) in lobjects:
293 for (dn, _attrs) in lobjects:
294 if dn is None:
294 if dn is None:
295 continue
295 continue
296
296
297 user_attrs = self.fetch_attrs_from_simple_bind(
297 user_attrs = self.fetch_attrs_from_simple_bind(
298 server, dn, username, password)
298 server, dn, username, password)
299 if user_attrs:
299 if user_attrs:
300 break
300 break
301
301
302 else:
302 else:
303 log.debug("No matching LDAP objects for authentication "
303 log.debug("No matching LDAP objects for authentication "
304 "of '%s' (%s)", uid, username)
304 "of '%s' (%s)", uid, username)
305 raise LdapPasswordError('Failed to authenticate user '
305 raise LdapPasswordError('Failed to authenticate user '
306 'with given password')
306 'with given password')
307
307
308 except ldap.NO_SUCH_OBJECT:
308 except ldap.NO_SUCH_OBJECT:
309 log.debug("LDAP says no such user '%s' (%s), org_exc:",
309 log.debug("LDAP says no such user '%s' (%s), org_exc:",
310 uid, username, exc_info=True)
310 uid, username, exc_info=True)
311 raise LdapUsernameError()
311 raise LdapUsernameError()
312 except ldap.SERVER_DOWN:
312 except ldap.SERVER_DOWN:
313 org_exc = traceback.format_exc()
313 org_exc = traceback.format_exc()
314 raise LdapConnectionError(
314 raise LdapConnectionError(
315 "LDAP can't access authentication "
315 "LDAP can't access authentication "
316 "server, org_exc:%s" % org_exc)
316 "server, org_exc:%s" % org_exc)
317
317
318 return dn, user_attrs
318 return dn, user_attrs
319
319
320
320
321 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
321 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
322 # used to define dynamic binding in the
322 # used to define dynamic binding in the
323 DYNAMIC_BIND_VAR = '$login'
323 DYNAMIC_BIND_VAR = '$login'
324
324
325 def includeme(self, config):
325 def includeme(self, config):
326 config.add_authn_plugin(self)
326 config.add_authn_plugin(self)
327 config.add_authn_resource(self.get_id(), LdapAuthnResource(self))
327 config.add_authn_resource(self.get_id(), LdapAuthnResource(self))
328 config.add_view(
328 config.add_view(
329 'rhodecode.authentication.views.AuthnPluginViewBase',
329 'rhodecode.authentication.views.AuthnPluginViewBase',
330 attr='settings_get',
330 attr='settings_get',
331 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
331 request_method='GET',
332 request_method='GET',
332 route_name='auth_home',
333 route_name='auth_home',
333 context=LdapAuthnResource)
334 context=LdapAuthnResource)
334 config.add_view(
335 config.add_view(
335 'rhodecode.authentication.views.AuthnPluginViewBase',
336 'rhodecode.authentication.views.AuthnPluginViewBase',
336 attr='settings_post',
337 attr='settings_post',
338 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
337 request_method='POST',
339 request_method='POST',
338 route_name='auth_home',
340 route_name='auth_home',
339 context=LdapAuthnResource)
341 context=LdapAuthnResource)
340
342
341 def get_settings_schema(self):
343 def get_settings_schema(self):
342 return LdapSettingsSchema()
344 return LdapSettingsSchema()
343
345
344 def get_display_name(self):
346 def get_display_name(self):
345 return _('LDAP')
347 return _('LDAP')
346
348
347 @hybrid_property
349 @hybrid_property
348 def name(self):
350 def name(self):
349 return "ldap"
351 return "ldap"
350
352
351 def use_fake_password(self):
353 def use_fake_password(self):
352 return True
354 return True
353
355
354 def user_activation_state(self):
356 def user_activation_state(self):
355 def_user_perms = User.get_default_user().AuthUser.permissions['global']
357 def_user_perms = User.get_default_user().AuthUser.permissions['global']
356 return 'hg.extern_activate.auto' in def_user_perms
358 return 'hg.extern_activate.auto' in def_user_perms
357
359
358 def try_dynamic_binding(self, username, password, current_args):
360 def try_dynamic_binding(self, username, password, current_args):
359 """
361 """
360 Detects marker inside our original bind, and uses dynamic auth if
362 Detects marker inside our original bind, and uses dynamic auth if
361 present
363 present
362 """
364 """
363
365
364 org_bind = current_args['bind_dn']
366 org_bind = current_args['bind_dn']
365 passwd = current_args['bind_pass']
367 passwd = current_args['bind_pass']
366
368
367 def has_bind_marker(username):
369 def has_bind_marker(username):
368 if self.DYNAMIC_BIND_VAR in username:
370 if self.DYNAMIC_BIND_VAR in username:
369 return True
371 return True
370
372
371 # we only passed in user with "special" variable
373 # we only passed in user with "special" variable
372 if org_bind and has_bind_marker(org_bind) and not passwd:
374 if org_bind and has_bind_marker(org_bind) and not passwd:
373 log.debug('Using dynamic user/password binding for ldap '
375 log.debug('Using dynamic user/password binding for ldap '
374 'authentication. Replacing `%s` with username',
376 'authentication. Replacing `%s` with username',
375 self.DYNAMIC_BIND_VAR)
377 self.DYNAMIC_BIND_VAR)
376 current_args['bind_dn'] = org_bind.replace(
378 current_args['bind_dn'] = org_bind.replace(
377 self.DYNAMIC_BIND_VAR, username)
379 self.DYNAMIC_BIND_VAR, username)
378 current_args['bind_pass'] = password
380 current_args['bind_pass'] = password
379
381
380 return current_args
382 return current_args
381
383
382 def auth(self, userobj, username, password, settings, **kwargs):
384 def auth(self, userobj, username, password, settings, **kwargs):
383 """
385 """
384 Given a user object (which may be null), username, a plaintext password,
386 Given a user object (which may be null), username, a plaintext password,
385 and a settings object (containing all the keys needed as listed in
387 and a settings object (containing all the keys needed as listed in
386 settings()), authenticate this user's login attempt.
388 settings()), authenticate this user's login attempt.
387
389
388 Return None on failure. On success, return a dictionary of the form:
390 Return None on failure. On success, return a dictionary of the form:
389
391
390 see: RhodeCodeAuthPluginBase.auth_func_attrs
392 see: RhodeCodeAuthPluginBase.auth_func_attrs
391 This is later validated for correctness
393 This is later validated for correctness
392 """
394 """
393
395
394 if not username or not password:
396 if not username or not password:
395 log.debug('Empty username or password skipping...')
397 log.debug('Empty username or password skipping...')
396 return None
398 return None
397
399
398 ldap_args = {
400 ldap_args = {
399 'server': settings.get('host', ''),
401 'server': settings.get('host', ''),
400 'base_dn': settings.get('base_dn', ''),
402 'base_dn': settings.get('base_dn', ''),
401 'port': settings.get('port'),
403 'port': settings.get('port'),
402 'bind_dn': settings.get('dn_user'),
404 'bind_dn': settings.get('dn_user'),
403 'bind_pass': settings.get('dn_pass'),
405 'bind_pass': settings.get('dn_pass'),
404 'tls_kind': settings.get('tls_kind'),
406 'tls_kind': settings.get('tls_kind'),
405 'tls_reqcert': settings.get('tls_reqcert'),
407 'tls_reqcert': settings.get('tls_reqcert'),
406 'search_scope': settings.get('search_scope'),
408 'search_scope': settings.get('search_scope'),
407 'attr_login': settings.get('attr_login'),
409 'attr_login': settings.get('attr_login'),
408 'ldap_version': 3,
410 'ldap_version': 3,
409 'ldap_filter': settings.get('filter'),
411 'ldap_filter': settings.get('filter'),
410 }
412 }
411
413
412 ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
414 ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
413
415
414 log.debug('Checking for ldap authentication.')
416 log.debug('Checking for ldap authentication.')
415
417
416 try:
418 try:
417 aldap = AuthLdap(**ldap_args)
419 aldap = AuthLdap(**ldap_args)
418 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
420 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
419 log.debug('Got ldap DN response %s', user_dn)
421 log.debug('Got ldap DN response %s', user_dn)
420
422
421 def get_ldap_attr(k):
423 def get_ldap_attr(k):
422 return ldap_attrs.get(settings.get(k), [''])[0]
424 return ldap_attrs.get(settings.get(k), [''])[0]
423
425
424 # old attrs fetched from RhodeCode database
426 # old attrs fetched from RhodeCode database
425 admin = getattr(userobj, 'admin', False)
427 admin = getattr(userobj, 'admin', False)
426 active = getattr(userobj, 'active', True)
428 active = getattr(userobj, 'active', True)
427 email = getattr(userobj, 'email', '')
429 email = getattr(userobj, 'email', '')
428 username = getattr(userobj, 'username', username)
430 username = getattr(userobj, 'username', username)
429 firstname = getattr(userobj, 'firstname', '')
431 firstname = getattr(userobj, 'firstname', '')
430 lastname = getattr(userobj, 'lastname', '')
432 lastname = getattr(userobj, 'lastname', '')
431 extern_type = getattr(userobj, 'extern_type', '')
433 extern_type = getattr(userobj, 'extern_type', '')
432
434
433 groups = []
435 groups = []
434 user_attrs = {
436 user_attrs = {
435 'username': username,
437 'username': username,
436 'firstname': safe_unicode(
438 'firstname': safe_unicode(
437 get_ldap_attr('attr_firstname') or firstname),
439 get_ldap_attr('attr_firstname') or firstname),
438 'lastname': safe_unicode(
440 'lastname': safe_unicode(
439 get_ldap_attr('attr_lastname') or lastname),
441 get_ldap_attr('attr_lastname') or lastname),
440 'groups': groups,
442 'groups': groups,
441 'email': get_ldap_attr('attr_email' or email),
443 'email': get_ldap_attr('attr_email' or email),
442 'admin': admin,
444 'admin': admin,
443 'active': active,
445 'active': active,
444 "active_from_extern": None,
446 "active_from_extern": None,
445 'extern_name': user_dn,
447 'extern_name': user_dn,
446 'extern_type': extern_type,
448 'extern_type': extern_type,
447 }
449 }
448 log.debug('ldap user: %s', user_attrs)
450 log.debug('ldap user: %s', user_attrs)
449 log.info('user %s authenticated correctly', user_attrs['username'])
451 log.info('user %s authenticated correctly', user_attrs['username'])
450
452
451 return user_attrs
453 return user_attrs
452
454
453 except (LdapUsernameError, LdapPasswordError, LdapImportError):
455 except (LdapUsernameError, LdapPasswordError, LdapImportError):
454 log.exception("LDAP related exception")
456 log.exception("LDAP related exception")
455 return None
457 return None
456 except (Exception,):
458 except (Exception,):
457 log.exception("Other exception")
459 log.exception("Other exception")
458 return None
460 return None
@@ -1,158 +1,160 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 """
20 """
21 RhodeCode authentication library for PAM
21 RhodeCode authentication library for PAM
22 """
22 """
23
23
24 import colander
24 import colander
25 import grp
25 import grp
26 import logging
26 import logging
27 import pam
27 import pam
28 import pwd
28 import pwd
29 import re
29 import re
30 import socket
30 import socket
31
31
32 from pylons.i18n.translation import lazy_ugettext as _
32 from pylons.i18n.translation import lazy_ugettext as _
33 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.ext.hybrid import hybrid_property
34
34
35 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.authentication.routes import AuthnPluginResourceBase
38 from rhodecode.lib.colander_utils import strip_whitespace
38 from rhodecode.lib.colander_utils import strip_whitespace
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 def plugin_factory(plugin_id, *args, **kwds):
43 def plugin_factory(plugin_id, *args, **kwds):
44 """
44 """
45 Factory function that is called during plugin discovery.
45 Factory function that is called during plugin discovery.
46 It returns the plugin instance.
46 It returns the plugin instance.
47 """
47 """
48 plugin = RhodeCodeAuthPlugin(plugin_id)
48 plugin = RhodeCodeAuthPlugin(plugin_id)
49 return plugin
49 return plugin
50
50
51
51
52 class PamAuthnResource(AuthnPluginResourceBase):
52 class PamAuthnResource(AuthnPluginResourceBase):
53 pass
53 pass
54
54
55
55
56 class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
56 class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
57 service = colander.SchemaNode(
57 service = colander.SchemaNode(
58 colander.String(),
58 colander.String(),
59 default='login',
59 default='login',
60 description=_('PAM service name to use for authentication.'),
60 description=_('PAM service name to use for authentication.'),
61 preparer=strip_whitespace,
61 preparer=strip_whitespace,
62 title=_('PAM service name'),
62 title=_('PAM service name'),
63 widget='string')
63 widget='string')
64 gecos = colander.SchemaNode(
64 gecos = colander.SchemaNode(
65 colander.String(),
65 colander.String(),
66 default='(?P<last_name>.+),\s*(?P<first_name>\w+)',
66 default='(?P<last_name>.+),\s*(?P<first_name>\w+)',
67 description=_('Regular expression for extracting user name/email etc. '
67 description=_('Regular expression for extracting user name/email etc. '
68 'from Unix userinfo.'),
68 'from Unix userinfo.'),
69 preparer=strip_whitespace,
69 preparer=strip_whitespace,
70 title=_('Gecos Regex'),
70 title=_('Gecos Regex'),
71 widget='string')
71 widget='string')
72
72
73
73
74 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
74 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
75 # PAM authentication can be slow. Repository operations involve a lot of
75 # PAM authentication can be slow. Repository operations involve a lot of
76 # auth calls. Little caching helps speedup push/pull operations significantly
76 # auth calls. Little caching helps speedup push/pull operations significantly
77 AUTH_CACHE_TTL = 4
77 AUTH_CACHE_TTL = 4
78
78
79 def includeme(self, config):
79 def includeme(self, config):
80 config.add_authn_plugin(self)
80 config.add_authn_plugin(self)
81 config.add_authn_resource(self.get_id(), PamAuthnResource(self))
81 config.add_authn_resource(self.get_id(), PamAuthnResource(self))
82 config.add_view(
82 config.add_view(
83 'rhodecode.authentication.views.AuthnPluginViewBase',
83 'rhodecode.authentication.views.AuthnPluginViewBase',
84 attr='settings_get',
84 attr='settings_get',
85 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
85 request_method='GET',
86 request_method='GET',
86 route_name='auth_home',
87 route_name='auth_home',
87 context=PamAuthnResource)
88 context=PamAuthnResource)
88 config.add_view(
89 config.add_view(
89 'rhodecode.authentication.views.AuthnPluginViewBase',
90 'rhodecode.authentication.views.AuthnPluginViewBase',
90 attr='settings_post',
91 attr='settings_post',
92 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
91 request_method='POST',
93 request_method='POST',
92 route_name='auth_home',
94 route_name='auth_home',
93 context=PamAuthnResource)
95 context=PamAuthnResource)
94
96
95 def get_display_name(self):
97 def get_display_name(self):
96 return _('PAM')
98 return _('PAM')
97
99
98 @hybrid_property
100 @hybrid_property
99 def name(self):
101 def name(self):
100 return "pam"
102 return "pam"
101
103
102 def get_settings_schema(self):
104 def get_settings_schema(self):
103 return PamSettingsSchema()
105 return PamSettingsSchema()
104
106
105 def use_fake_password(self):
107 def use_fake_password(self):
106 return True
108 return True
107
109
108 def auth(self, userobj, username, password, settings, **kwargs):
110 def auth(self, userobj, username, password, settings, **kwargs):
109 if not username or not password:
111 if not username or not password:
110 log.debug('Empty username or password skipping...')
112 log.debug('Empty username or password skipping...')
111 return None
113 return None
112
114
113 auth_result = pam.authenticate(username, password, settings["service"])
115 auth_result = pam.authenticate(username, password, settings["service"])
114
116
115 if not auth_result:
117 if not auth_result:
116 log.error("PAM was unable to authenticate user: %s" % (username, ))
118 log.error("PAM was unable to authenticate user: %s" % (username, ))
117 return None
119 return None
118
120
119 log.debug('Got PAM response %s' % (auth_result, ))
121 log.debug('Got PAM response %s' % (auth_result, ))
120
122
121 # old attrs fetched from RhodeCode database
123 # old attrs fetched from RhodeCode database
122 default_email = "%s@%s" % (username, socket.gethostname())
124 default_email = "%s@%s" % (username, socket.gethostname())
123 admin = getattr(userobj, 'admin', False)
125 admin = getattr(userobj, 'admin', False)
124 active = getattr(userobj, 'active', True)
126 active = getattr(userobj, 'active', True)
125 email = getattr(userobj, 'email', '') or default_email
127 email = getattr(userobj, 'email', '') or default_email
126 username = getattr(userobj, 'username', username)
128 username = getattr(userobj, 'username', username)
127 firstname = getattr(userobj, 'firstname', '')
129 firstname = getattr(userobj, 'firstname', '')
128 lastname = getattr(userobj, 'lastname', '')
130 lastname = getattr(userobj, 'lastname', '')
129 extern_type = getattr(userobj, 'extern_type', '')
131 extern_type = getattr(userobj, 'extern_type', '')
130
132
131 user_attrs = {
133 user_attrs = {
132 'username': username,
134 'username': username,
133 'firstname': firstname,
135 'firstname': firstname,
134 'lastname': lastname,
136 'lastname': lastname,
135 'groups': [g.gr_name for g in grp.getgrall()
137 'groups': [g.gr_name for g in grp.getgrall()
136 if username in g.gr_mem],
138 if username in g.gr_mem],
137 'email': email,
139 'email': email,
138 'admin': admin,
140 'admin': admin,
139 'active': active,
141 'active': active,
140 'active_from_extern': None,
142 'active_from_extern': None,
141 'extern_name': username,
143 'extern_name': username,
142 'extern_type': extern_type,
144 'extern_type': extern_type,
143 }
145 }
144
146
145 try:
147 try:
146 user_data = pwd.getpwnam(username)
148 user_data = pwd.getpwnam(username)
147 regex = settings["gecos"]
149 regex = settings["gecos"]
148 match = re.search(regex, user_data.pw_gecos)
150 match = re.search(regex, user_data.pw_gecos)
149 if match:
151 if match:
150 user_attrs["firstname"] = match.group('first_name')
152 user_attrs["firstname"] = match.group('first_name')
151 user_attrs["lastname"] = match.group('last_name')
153 user_attrs["lastname"] = match.group('last_name')
152 except Exception:
154 except Exception:
153 log.warning("Cannot extract additional info for PAM user")
155 log.warning("Cannot extract additional info for PAM user")
154 pass
156 pass
155
157
156 log.debug("pamuser: %s", user_attrs)
158 log.debug("pamuser: %s", user_attrs)
157 log.info('user %s authenticated correctly' % user_attrs['username'])
159 log.info('user %s authenticated correctly' % user_attrs['username'])
158 return user_attrs
160 return user_attrs
@@ -1,139 +1,141 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for built in internal auth
22 RhodeCode authentication plugin for built in internal auth
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from pylons.i18n.translation import lazy_ugettext as _
27 from pylons.i18n.translation import lazy_ugettext as _
28 from sqlalchemy.ext.hybrid import hybrid_property
28 from sqlalchemy.ext.hybrid import hybrid_property
29
29
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
32 from rhodecode.lib.utils2 import safe_str
32 from rhodecode.lib.utils2 import safe_str
33 from rhodecode.model.db import User
33 from rhodecode.model.db import User
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 def plugin_factory(plugin_id, *args, **kwds):
38 def plugin_factory(plugin_id, *args, **kwds):
39 plugin = RhodeCodeAuthPlugin(plugin_id)
39 plugin = RhodeCodeAuthPlugin(plugin_id)
40 return plugin
40 return plugin
41
41
42
42
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
44 pass
44 pass
45
45
46
46
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
48
48
49 def includeme(self, config):
49 def includeme(self, config):
50 config.add_authn_plugin(self)
50 config.add_authn_plugin(self)
51 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
51 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
52 config.add_view(
52 config.add_view(
53 'rhodecode.authentication.views.AuthnPluginViewBase',
53 'rhodecode.authentication.views.AuthnPluginViewBase',
54 attr='settings_get',
54 attr='settings_get',
55 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
55 request_method='GET',
56 request_method='GET',
56 route_name='auth_home',
57 route_name='auth_home',
57 context=RhodecodeAuthnResource)
58 context=RhodecodeAuthnResource)
58 config.add_view(
59 config.add_view(
59 'rhodecode.authentication.views.AuthnPluginViewBase',
60 'rhodecode.authentication.views.AuthnPluginViewBase',
60 attr='settings_post',
61 attr='settings_post',
62 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
61 request_method='POST',
63 request_method='POST',
62 route_name='auth_home',
64 route_name='auth_home',
63 context=RhodecodeAuthnResource)
65 context=RhodecodeAuthnResource)
64
66
65 def get_display_name(self):
67 def get_display_name(self):
66 return _('Rhodecode')
68 return _('Rhodecode')
67
69
68 @hybrid_property
70 @hybrid_property
69 def name(self):
71 def name(self):
70 return "rhodecode"
72 return "rhodecode"
71
73
72 def user_activation_state(self):
74 def user_activation_state(self):
73 def_user_perms = User.get_default_user().AuthUser.permissions['global']
75 def_user_perms = User.get_default_user().AuthUser.permissions['global']
74 return 'hg.register.auto_activate' in def_user_perms
76 return 'hg.register.auto_activate' in def_user_perms
75
77
76 def allows_authentication_from(
78 def allows_authentication_from(
77 self, user, allows_non_existing_user=True,
79 self, user, allows_non_existing_user=True,
78 allowed_auth_plugins=None, allowed_auth_sources=None):
80 allowed_auth_plugins=None, allowed_auth_sources=None):
79 """
81 """
80 Custom method for this auth that doesn't accept non existing users.
82 Custom method for this auth that doesn't accept non existing users.
81 We know that user exists in our database.
83 We know that user exists in our database.
82 """
84 """
83 allows_non_existing_user = False
85 allows_non_existing_user = False
84 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
86 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
85 user, allows_non_existing_user=allows_non_existing_user)
87 user, allows_non_existing_user=allows_non_existing_user)
86
88
87 def auth(self, userobj, username, password, settings, **kwargs):
89 def auth(self, userobj, username, password, settings, **kwargs):
88 if not userobj:
90 if not userobj:
89 log.debug('userobj was:%s skipping' % (userobj, ))
91 log.debug('userobj was:%s skipping' % (userobj, ))
90 return None
92 return None
91 if userobj.extern_type != self.name:
93 if userobj.extern_type != self.name:
92 log.warning(
94 log.warning(
93 "userobj:%s extern_type mismatch got:`%s` expected:`%s`" %
95 "userobj:%s extern_type mismatch got:`%s` expected:`%s`" %
94 (userobj, userobj.extern_type, self.name))
96 (userobj, userobj.extern_type, self.name))
95 return None
97 return None
96
98
97 user_attrs = {
99 user_attrs = {
98 "username": userobj.username,
100 "username": userobj.username,
99 "firstname": userobj.firstname,
101 "firstname": userobj.firstname,
100 "lastname": userobj.lastname,
102 "lastname": userobj.lastname,
101 "groups": [],
103 "groups": [],
102 "email": userobj.email,
104 "email": userobj.email,
103 "admin": userobj.admin,
105 "admin": userobj.admin,
104 "active": userobj.active,
106 "active": userobj.active,
105 "active_from_extern": userobj.active,
107 "active_from_extern": userobj.active,
106 "extern_name": userobj.user_id,
108 "extern_name": userobj.user_id,
107 "extern_type": userobj.extern_type,
109 "extern_type": userobj.extern_type,
108 }
110 }
109
111
110 log.debug("User attributes:%s" % (user_attrs, ))
112 log.debug("User attributes:%s" % (user_attrs, ))
111 if userobj.active:
113 if userobj.active:
112 from rhodecode.lib import auth
114 from rhodecode.lib import auth
113 crypto_backend = auth.crypto_backend()
115 crypto_backend = auth.crypto_backend()
114 password_encoded = safe_str(password)
116 password_encoded = safe_str(password)
115 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
117 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
116 password_encoded, userobj.password)
118 password_encoded, userobj.password)
117
119
118 if password_match and new_hash:
120 if password_match and new_hash:
119 log.debug('user %s properly authenticated, but '
121 log.debug('user %s properly authenticated, but '
120 'requires hash change to bcrypt', userobj)
122 'requires hash change to bcrypt', userobj)
121 # if password match, and we use OLD deprecated hash,
123 # if password match, and we use OLD deprecated hash,
122 # we should migrate this user hash password to the new hash
124 # we should migrate this user hash password to the new hash
123 # we store the new returned by hash_check_with_upgrade function
125 # we store the new returned by hash_check_with_upgrade function
124 user_attrs['_hash_migrate'] = new_hash
126 user_attrs['_hash_migrate'] = new_hash
125
127
126 if userobj.username == User.DEFAULT_USER and userobj.active:
128 if userobj.username == User.DEFAULT_USER and userobj.active:
127 log.info(
129 log.info(
128 'user %s authenticated correctly as anonymous user', userobj)
130 'user %s authenticated correctly as anonymous user', userobj)
129 return user_attrs
131 return user_attrs
130
132
131 elif userobj.username == username and password_match:
133 elif userobj.username == username and password_match:
132 log.info('user %s authenticated correctly', userobj)
134 log.info('user %s authenticated correctly', userobj)
133 return user_attrs
135 return user_attrs
134 log.info("user %s had a bad password when "
136 log.info("user %s had a bad password when "
135 "authenticating on this plugin", userobj)
137 "authenticating on this plugin", userobj)
136 return None
138 return None
137 else:
139 else:
138 log.warning('user %s tried auth but is disabled', userobj)
140 log.warning('user %s tried auth but is disabled', userobj)
139 return None
141 return None
General Comments 0
You need to be logged in to leave comments. Login now