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