##// END OF EJS Templates
auth: Remove AuthomaticBase class....
johbo -
r22:06f54f5c default
parent child Browse files
Show More
@@ -1,739 +1,624 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 Authentication modules
23 23 """
24 24
25 25 import logging
26 26 import time
27 27 import traceback
28 28
29 29 from authomatic import Authomatic
30 30 from authomatic.adapters import WebObAdapter
31 31 from authomatic.providers import oauth2, oauth1
32 32 from pylons import url
33 33 from pylons.controllers.util import Response
34 34 from pylons.i18n.translation import _
35 35 from pyramid.threadlocal import get_current_registry
36 36 from sqlalchemy.ext.hybrid import hybrid_property
37 37
38 38 import rhodecode.lib.helpers as h
39 39 from rhodecode.authentication.interface import IAuthnPluginRegistry
40 40 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
41 41 from rhodecode.lib import caches
42 42 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
43 43 from rhodecode.lib.utils2 import md5_safe, safe_int
44 44 from rhodecode.lib.utils2 import safe_str
45 45 from rhodecode.model.db import User, ExternalIdentity
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.settings import SettingsModel
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.user_group import UserGroupModel
50 50
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54 # auth types that authenticate() function can receive
55 55 VCS_TYPE = 'vcs'
56 56 HTTP_TYPE = 'http'
57 57
58 58
59 59 class LazyFormencode(object):
60 60 def __init__(self, formencode_obj, *args, **kwargs):
61 61 self.formencode_obj = formencode_obj
62 62 self.args = args
63 63 self.kwargs = kwargs
64 64
65 65 def __call__(self, *args, **kwargs):
66 66 from inspect import isfunction
67 67 formencode_obj = self.formencode_obj
68 68 if isfunction(formencode_obj):
69 69 # case we wrap validators into functions
70 70 formencode_obj = self.formencode_obj(*args, **kwargs)
71 71 return formencode_obj(*self.args, **self.kwargs)
72 72
73 73
74 74 class RhodeCodeAuthPluginBase(object):
75 75 # cache the authentication request for N amount of seconds. Some kind
76 76 # of authentication methods are very heavy and it's very efficient to cache
77 77 # the result of a call. If it's set to None (default) cache is off
78 78 AUTH_CACHE_TTL = None
79 79 AUTH_CACHE = {}
80 80
81 81 auth_func_attrs = {
82 82 "username": "unique username",
83 83 "firstname": "first name",
84 84 "lastname": "last name",
85 85 "email": "email address",
86 86 "groups": '["list", "of", "groups"]',
87 87 "extern_name": "name in external source of record",
88 88 "extern_type": "type of external source of record",
89 89 "admin": 'True|False defines if user should be RhodeCode super admin',
90 90 "active":
91 91 'True|False defines active state of user internally for RhodeCode',
92 92 "active_from_extern":
93 93 "True|False\None, active state from the external auth, "
94 94 "None means use definition from RhodeCode extern_type active value"
95 95 }
96 96 # set on authenticate() method and via set_auth_type func.
97 97 auth_type = None
98 98
99 99 # List of setting names to store encrypted. Plugins may override this list
100 100 # to store settings encrypted.
101 101 _settings_encrypted = []
102 102
103 103 # Mapping of python to DB settings model types. Plugins may override or
104 104 # extend this mapping.
105 105 _settings_type_map = {
106 106 str: 'str',
107 107 int: 'int',
108 108 unicode: 'unicode',
109 109 bool: 'bool',
110 110 list: 'list',
111 111 }
112 112
113 113 def __init__(self, plugin_id):
114 114 self._plugin_id = plugin_id
115 115
116 116 def _get_setting_full_name(self, name):
117 117 """
118 118 Return the full setting name used for storing values in the database.
119 119 """
120 120 # TODO: johbo: Using the name here is problematic. It would be good to
121 121 # introduce either new models in the database to hold Plugin and
122 122 # PluginSetting or to use the plugin id here.
123 123 return 'auth_{}_{}'.format(self.name, name)
124 124
125 125 def _get_setting_type(self, name, value):
126 126 """
127 127 Get the type as used by the SettingsModel accordingly to type of passed
128 128 value. Optionally the suffix `.encrypted` is appended to instruct
129 129 SettingsModel to store it encrypted.
130 130 """
131 131 type_ = self._settings_type_map.get(type(value), 'unicode')
132 132 if name in self._settings_encrypted:
133 133 type_ = '{}.encrypted'.format(type_)
134 134 return type_
135 135
136 136 def is_enabled(self):
137 137 """
138 138 Returns true if this plugin is enabled. An enabled plugin can be
139 139 configured in the admin interface but it is not consulted during
140 140 authentication.
141 141 """
142 142 auth_plugins = SettingsModel().get_auth_plugins()
143 143 return self.get_id() in auth_plugins
144 144
145 145 def is_active(self):
146 146 """
147 147 Returns true if the plugin is activated. An activated plugin is
148 148 consulted during authentication, assumed it is also enabled.
149 149 """
150 150 return self.get_setting_by_name('enabled')
151 151
152 152 def get_id(self):
153 153 """
154 154 Returns the plugin id.
155 155 """
156 156 return self._plugin_id
157 157
158 158 def get_display_name(self):
159 159 """
160 160 Returns a translation string for displaying purposes.
161 161 """
162 162 raise NotImplementedError('Not implemented in base class')
163 163
164 164 def get_settings_schema(self):
165 165 """
166 166 Returns a colander schema, representing the plugin settings.
167 167 """
168 168 return AuthnPluginSettingsSchemaBase()
169 169
170 170 def get_setting_by_name(self, name):
171 171 """
172 172 Returns a plugin setting by name.
173 173 """
174 174 full_name = self._get_setting_full_name(name)
175 175 db_setting = SettingsModel().get_setting_by_name(full_name)
176 176 return db_setting.app_settings_value if db_setting else None
177 177
178 178 def create_or_update_setting(self, name, value):
179 179 """
180 180 Create or update a setting for this plugin in the persistent storage.
181 181 """
182 182 full_name = self._get_setting_full_name(name)
183 183 type_ = self._get_setting_type(name, value)
184 184 db_setting = SettingsModel().create_or_update_setting(
185 185 full_name, value, type_)
186 186 return db_setting.app_settings_value
187 187
188 188 def get_settings(self):
189 189 """
190 190 Returns the plugin settings as dictionary.
191 191 """
192 192 settings = {}
193 193 for node in self.get_settings_schema():
194 194 settings[node.name] = self.get_setting_by_name(node.name)
195 195 return settings
196 196
197 197 @property
198 198 def validators(self):
199 199 """
200 200 Exposes RhodeCode validators modules
201 201 """
202 202 # this is a hack to overcome issues with pylons threadlocals and
203 203 # translator object _() not beein registered properly.
204 204 class LazyCaller(object):
205 205 def __init__(self, name):
206 206 self.validator_name = name
207 207
208 208 def __call__(self, *args, **kwargs):
209 209 from rhodecode.model import validators as v
210 210 obj = getattr(v, self.validator_name)
211 211 # log.debug('Initializing lazy formencode object: %s', obj)
212 212 return LazyFormencode(obj, *args, **kwargs)
213 213
214 214 class ProxyGet(object):
215 215 def __getattribute__(self, name):
216 216 return LazyCaller(name)
217 217
218 218 return ProxyGet()
219 219
220 220 @hybrid_property
221 221 def name(self):
222 222 """
223 223 Returns the name of this authentication plugin.
224 224
225 225 :returns: string
226 226 """
227 227 raise NotImplementedError("Not implemented in base class")
228 228
229 229 @hybrid_property
230 230 def is_container_auth(self):
231 231 """
232 232 Returns bool if this module uses container auth.
233 233
234 234 This property will trigger an automatic call to authenticate on
235 235 a visit to the website or during a push/pull.
236 236
237 237 :returns: bool
238 238 """
239 239 return False
240 240
241 241 @hybrid_property
242 242 def allows_creating_users(self):
243 243 """
244 244 Defines if Plugin allows users to be created on-the-fly when
245 245 authentication is called. Controls how external plugins should behave
246 246 in terms if they are allowed to create new users, or not. Base plugins
247 247 should not be allowed to, but External ones should be !
248 248
249 249 :return: bool
250 250 """
251 251 return False
252 252
253 253 def set_auth_type(self, auth_type):
254 254 self.auth_type = auth_type
255 255
256 256 def allows_authentication_from(
257 257 self, user, allows_non_existing_user=True,
258 258 allowed_auth_plugins=None, allowed_auth_sources=None):
259 259 """
260 260 Checks if this authentication module should accept a request for
261 261 the current user.
262 262
263 263 :param user: user object fetched using plugin's get_user() method.
264 264 :param allows_non_existing_user: if True, don't allow the
265 265 user to be empty, meaning not existing in our database
266 266 :param allowed_auth_plugins: if provided, users extern_type will be
267 267 checked against a list of provided extern types, which are plugin
268 268 auth_names in the end
269 269 :param allowed_auth_sources: authentication type allowed,
270 270 `http` or `vcs` default is both.
271 271 defines if plugin will accept only http authentication vcs
272 272 authentication(git/hg) or both
273 273 :returns: boolean
274 274 """
275 275 if not user and not allows_non_existing_user:
276 276 log.debug('User is empty but plugin does not allow empty users,'
277 277 'not allowed to authenticate')
278 278 return False
279 279
280 280 expected_auth_plugins = allowed_auth_plugins or [self.name]
281 281 if user and (user.extern_type and
282 282 user.extern_type not in expected_auth_plugins):
283 283 log.debug(
284 284 'User `%s` is bound to `%s` auth type. Plugin allows only '
285 285 '%s, skipping', user, user.extern_type, expected_auth_plugins)
286 286
287 287 return False
288 288
289 289 # by default accept both
290 290 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
291 291 if self.auth_type not in expected_auth_from:
292 292 log.debug('Current auth source is %s but plugin only allows %s',
293 293 self.auth_type, expected_auth_from)
294 294 return False
295 295
296 296 return True
297 297
298 298 def get_user(self, username=None, **kwargs):
299 299 """
300 300 Helper method for user fetching in plugins, by default it's using
301 301 simple fetch by username, but this method can be custimized in plugins
302 302 eg. container auth plugin to fetch user by environ params
303 303
304 304 :param username: username if given to fetch from database
305 305 :param kwargs: extra arguments needed for user fetching.
306 306 """
307 307 user = None
308 308 log.debug(
309 309 'Trying to fetch user `%s` from RhodeCode database', username)
310 310 if username:
311 311 user = User.get_by_username(username)
312 312 if not user:
313 313 log.debug('User not found, fallback to fetch user in '
314 314 'case insensitive mode')
315 315 user = User.get_by_username(username, case_insensitive=True)
316 316 else:
317 317 log.debug('provided username:`%s` is empty skipping...', username)
318 318 if not user:
319 319 log.debug('User `%s` not found in database', username)
320 320 return user
321 321
322 322 def user_activation_state(self):
323 323 """
324 324 Defines user activation state when creating new users
325 325
326 326 :returns: boolean
327 327 """
328 328 raise NotImplementedError("Not implemented in base class")
329 329
330 330 def auth(self, userobj, username, passwd, settings, **kwargs):
331 331 """
332 332 Given a user object (which may be null), username, a plaintext
333 333 password, and a settings object (containing all the keys needed as
334 334 listed in settings()), authenticate this user's login attempt.
335 335
336 336 Return None on failure. On success, return a dictionary of the form:
337 337
338 338 see: RhodeCodeAuthPluginBase.auth_func_attrs
339 339 This is later validated for correctness
340 340 """
341 341 raise NotImplementedError("not implemented in base class")
342 342
343 343 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
344 344 """
345 345 Wrapper to call self.auth() that validates call on it
346 346
347 347 :param userobj: userobj
348 348 :param username: username
349 349 :param passwd: plaintext password
350 350 :param settings: plugin settings
351 351 """
352 352 auth = self.auth(userobj, username, passwd, settings, **kwargs)
353 353 if auth:
354 354 # check if hash should be migrated ?
355 355 new_hash = auth.get('_hash_migrate')
356 356 if new_hash:
357 357 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
358 358 return self._validate_auth_return(auth)
359 359 return auth
360 360
361 361 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
362 362 new_hash_cypher = _RhodeCodeCryptoBCrypt()
363 363 # extra checks, so make sure new hash is correct.
364 364 password_encoded = safe_str(password)
365 365 if new_hash and new_hash_cypher.hash_check(
366 366 password_encoded, new_hash):
367 367 cur_user = User.get_by_username(username)
368 368 cur_user.password = new_hash
369 369 Session().add(cur_user)
370 370 Session().flush()
371 371 log.info('Migrated user %s hash to bcrypt', cur_user)
372 372
373 373 def _validate_auth_return(self, ret):
374 374 if not isinstance(ret, dict):
375 375 raise Exception('returned value from auth must be a dict')
376 376 for k in self.auth_func_attrs:
377 377 if k not in ret:
378 378 raise Exception('Missing %s attribute from returned data' % k)
379 379 return ret
380 380
381 381
382 382 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
383 383
384 384 @hybrid_property
385 385 def allows_creating_users(self):
386 386 return True
387 387
388 388 def use_fake_password(self):
389 389 """
390 390 Return a boolean that indicates whether or not we should set the user's
391 391 password to a random value when it is authenticated by this plugin.
392 392 If your plugin provides authentication, then you will generally
393 393 want this.
394 394
395 395 :returns: boolean
396 396 """
397 397 raise NotImplementedError("Not implemented in base class")
398 398
399 399 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
400 400 # at this point _authenticate calls plugin's `auth()` function
401 401 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
402 402 userobj, username, passwd, settings, **kwargs)
403 403 if auth:
404 404 # maybe plugin will clean the username ?
405 405 # we should use the return value
406 406 username = auth['username']
407 407
408 408 # if external source tells us that user is not active, we should
409 409 # skip rest of the process. This can prevent from creating users in
410 410 # RhodeCode when using external authentication, but if it's
411 411 # inactive user we shouldn't create that user anyway
412 412 if auth['active_from_extern'] is False:
413 413 log.warning(
414 414 "User %s authenticated against %s, but is inactive",
415 415 username, self.__module__)
416 416 return None
417 417
418 418 cur_user = User.get_by_username(username, case_insensitive=True)
419 419 is_user_existing = cur_user is not None
420 420
421 421 if is_user_existing:
422 422 log.debug('Syncing user `%s` from '
423 423 '`%s` plugin', username, self.name)
424 424 else:
425 425 log.debug('Creating non existing user `%s` from '
426 426 '`%s` plugin', username, self.name)
427 427
428 428 if self.allows_creating_users:
429 429 log.debug('Plugin `%s` allows to '
430 430 'create new users', self.name)
431 431 else:
432 432 log.debug('Plugin `%s` does not allow to '
433 433 'create new users', self.name)
434 434
435 435 user_parameters = {
436 436 'username': username,
437 437 'email': auth["email"],
438 438 'firstname': auth["firstname"],
439 439 'lastname': auth["lastname"],
440 440 'active': auth["active"],
441 441 'admin': auth["admin"],
442 442 'extern_name': auth["extern_name"],
443 443 'extern_type': self.name,
444 444 'plugin': self,
445 445 'allow_to_create_user': self.allows_creating_users,
446 446 }
447 447
448 448 if not is_user_existing:
449 449 if self.use_fake_password():
450 450 # Randomize the PW because we don't need it, but don't want
451 451 # them blank either
452 452 passwd = PasswordGenerator().gen_password(length=16)
453 453 user_parameters['password'] = passwd
454 454 else:
455 455 # Since the password is required by create_or_update method of
456 456 # UserModel, we need to set it explicitly.
457 457 # The create_or_update method is smart and recognises the
458 458 # password hashes as well.
459 459 user_parameters['password'] = cur_user.password
460 460
461 461 # we either create or update users, we also pass the flag
462 462 # that controls if this method can actually do that.
463 463 # raises NotAllowedToCreateUserError if it cannot, and we try to.
464 464 user = UserModel().create_or_update(**user_parameters)
465 465 Session().flush()
466 466 # enforce user is just in given groups, all of them has to be ones
467 467 # created from plugins. We store this info in _group_data JSON
468 468 # field
469 469 try:
470 470 groups = auth['groups'] or []
471 471 UserGroupModel().enforce_groups(user, groups, self.name)
472 472 except Exception:
473 473 # for any reason group syncing fails, we should
474 474 # proceed with login
475 475 log.error(traceback.format_exc())
476 476 Session().commit()
477 477 return auth
478 478
479 479
480 class AuthomaticBase(RhodeCodeExternalAuthPlugin):
481
482 # TODO: Think about how to create and store this secret string.
483 # We need the secret for the authomatic library. It needs to be the same
484 # across requests.
485 def _get_authomatic_secret(self, length=40):
486 secret = self.get_setting_by_name('secret')
487 if secret is None or secret == 'None' or secret == '':
488 from Crypto import Random, Hash
489 secret_bytes = Random.new().read(length)
490 secret_hash = Hash.SHA256.new()
491 secret_hash.update(secret_bytes)
492 secret = secret_hash.hexdigest()
493 self.create_or_update_setting('secret', secret)
494 Session.commit()
495 secret = self.get_setting_by_name('secret')
496 return secret
497
498 def get_authomatic(self):
499 scope = []
500 if self.name == 'bitbucket':
501 provider_class = oauth1.Bitbucket
502 scope = ['account', 'email', 'repository', 'issue', 'issue:write']
503 elif self.name == 'github':
504 provider_class = oauth2.GitHub
505 scope = ['repo', 'public_repo', 'user:email']
506 elif self.name == 'google':
507 provider_class = oauth2.Google
508 scope = ['profile', 'email']
509 elif self.name == 'twitter':
510 provider_class = oauth1.Twitter
511
512 authomatic_conf = {
513 self.name: {
514 'class_': provider_class,
515 'consumer_key': self.get_setting_by_name('consumer_key'),
516 'consumer_secret': self.get_setting_by_name('consumer_secret'),
517 'scope': scope,
518 'access_headers': {'User-Agent': 'TestAppAgent'},
519 }
520 }
521 secret = self._get_authomatic_secret()
522 return Authomatic(config=authomatic_conf,
523 secret=secret)
524
525 def get_provider_result(self, request):
526 """
527 Provides `authomatic.core.LoginResult` for provider and request
528
529 :param provider_name:
530 :param request:
531 :param config:
532 :return:
533 """
534 response = Response()
535 adapter = WebObAdapter(request, response)
536 authomatic_inst = self.get_authomatic()
537 return authomatic_inst.login(adapter, self.name), response
538
539 def handle_social_data(self, session, user_id, social_data):
540 """
541 Updates user tokens in database whenever necessary
542 :param request:
543 :param user:
544 :param social_data:
545 :return:
546 """
547 if not self.is_active():
548 h.flash(_('This provider is currently disabled'),
549 category='warning')
550 return False
551
552 social_data = social_data
553 update_identity = False
554
555 existing_row = ExternalIdentity.by_external_id_and_provider(
556 social_data['user']['id'],
557 social_data['credentials.provider']
558 )
559
560 if existing_row:
561 Session().delete(existing_row)
562 update_identity = True
563
564 if not existing_row or update_identity:
565 if not update_identity:
566 h.flash(_('Your external identity is now '
567 'connected with your account'), category='success')
568
569 if not social_data['user']['id']:
570 h.flash(_('No external user id found? Perhaps permissions'
571 'for authentication are set incorrectly'),
572 category='error')
573 return False
574
575 ex_identity = ExternalIdentity()
576 ex_identity.external_id = social_data['user']['id']
577 ex_identity.external_username = social_data['user']['user_name']
578 ex_identity.provider_name = social_data['credentials.provider']
579 ex_identity.access_token = social_data['credentials.token']
580 ex_identity.token_secret = social_data['credentials.token_secret']
581 ex_identity.alt_token = social_data['credentials.refresh_token']
582 ex_identity.local_user_id = user_id
583 Session().add(ex_identity)
584 session.pop('rhodecode.social_auth', None)
585 return ex_identity
586
587 def callback_url(self):
588 try:
589 return url('social_auth', provider_name=self.name, qualified=True)
590 except TypeError:
591 pass
592 return ''
593
594
595 480 def loadplugin(plugin_id):
596 481 """
597 482 Loads and returns an instantiated authentication plugin.
598 483 Returns the RhodeCodeAuthPluginBase subclass on success,
599 484 raises exceptions on failure.
600 485
601 486 raises:
602 487 KeyError -- if no plugin available with given name
603 488 TypeError -- if the RhodeCodeAuthPlugin is not a subclass of
604 489 ours RhodeCodeAuthPluginBase
605 490 """
606 491 # TODO: Disusing pyramids thread locals to retrieve the registry.
607 492 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
608 493 plugin = authn_registry.get_plugin(plugin_id)
609 494 if plugin is None:
610 495 log.error('Authentication plugin not found: "%s"', plugin_id)
611 496 return plugin
612 497
613 498
614 499 def get_auth_cache_manager(custom_ttl=None):
615 500 return caches.get_cache_manager(
616 501 'auth_plugins', 'rhodecode.authentication', custom_ttl)
617 502
618 503
619 504 def authenticate(username, password, environ=None, auth_type=None,
620 505 skip_missing=False):
621 506 """
622 507 Authentication function used for access control,
623 508 It tries to authenticate based on enabled authentication modules.
624 509
625 510 :param username: username can be empty for container auth
626 511 :param password: password can be empty for container auth
627 512 :param environ: environ headers passed for container auth
628 513 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
629 514 :param skip_missing: ignores plugins that are in db but not in environment
630 515 :returns: None if auth failed, plugin_user dict if auth is correct
631 516 """
632 517 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
633 518 raise ValueError('auth type must be on of http, vcs got "%s" instead'
634 519 % auth_type)
635 520 container_only = environ and not (username and password)
636 521 auth_plugins = SettingsModel().get_auth_plugins()
637 522 for plugin_id in auth_plugins:
638 523 plugin = loadplugin(plugin_id)
639 524
640 525 if plugin is None:
641 526 log.warning('Authentication plugin missing: "{}"'.format(
642 527 plugin_id))
643 528 continue
644 529
645 530 if not plugin.is_active():
646 531 log.info('Authentication plugin is inactive: "{}"'.format(
647 532 plugin_id))
648 533 continue
649 534
650 535 plugin.set_auth_type(auth_type)
651 536 user = plugin.get_user(username)
652 537 display_user = user.username if user else username
653 538
654 539 if container_only and not plugin.is_container_auth:
655 540 log.debug('Auth type is for container only and plugin `%s` is not '
656 541 'container plugin, skipping...', plugin_id)
657 542 continue
658 543
659 544 # load plugin settings from RhodeCode database
660 545 plugin_settings = plugin.get_settings()
661 546 log.debug('Plugin settings:%s', plugin_settings)
662 547
663 548 log.debug('Trying authentication using ** %s **', plugin_id)
664 549 # use plugin's method of user extraction.
665 550 user = plugin.get_user(username, environ=environ,
666 551 settings=plugin_settings)
667 552 display_user = user.username if user else username
668 553 log.debug('Plugin %s extracted user is `%s`', plugin_id, display_user)
669 554
670 555 if not plugin.allows_authentication_from(user):
671 556 log.debug('Plugin %s does not accept user `%s` for authentication',
672 557 plugin_id, display_user)
673 558 continue
674 559 else:
675 560 log.debug('Plugin %s accepted user `%s` for authentication',
676 561 plugin_id, display_user)
677 562
678 563 log.info('Authenticating user `%s` using %s plugin',
679 564 display_user, plugin_id)
680 565
681 566 _cache_ttl = 0
682 567
683 568 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
684 569 # plugin cache set inside is more important than the settings value
685 570 _cache_ttl = plugin.AUTH_CACHE_TTL
686 571 elif plugin_settings.get('auth_cache_ttl'):
687 572 _cache_ttl = safe_int(plugin_settings.get('auth_cache_ttl'), 0)
688 573
689 574 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
690 575
691 576 # get instance of cache manager configured for a namespace
692 577 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
693 578
694 579 log.debug('Cache for plugin `%s` active: %s', plugin_id,
695 580 plugin_cache_active)
696 581
697 582 # for environ based password can be empty, but then the validation is
698 583 # on the server that fills in the env data needed for authentication
699 584 _password_hash = md5_safe(plugin.name + username + (password or ''))
700 585
701 586 # _authenticate is a wrapper for .auth() method of plugin.
702 587 # it checks if .auth() sends proper data.
703 588 # For RhodeCodeExternalAuthPlugin it also maps users to
704 589 # Database and maps the attributes returned from .auth()
705 590 # to RhodeCode database. If this function returns data
706 591 # then auth is correct.
707 592 start = time.time()
708 593 log.debug('Running plugin `%s` _authenticate method',
709 594 plugin_id)
710 595
711 596 def auth_func():
712 597 """
713 598 This function is used internally in Cache of Beaker to calculate
714 599 Results
715 600 """
716 601 return plugin._authenticate(
717 602 user, username, password, plugin_settings,
718 603 environ=environ or {})
719 604
720 605 if plugin_cache_active:
721 606 plugin_user = cache_manager.get(
722 607 _password_hash, createfunc=auth_func)
723 608 else:
724 609 plugin_user = auth_func()
725 610
726 611 auth_time = time.time() - start
727 612 log.debug('Authentication for plugin `%s` completed in %.3fs, '
728 613 'expiration time of fetched cache %.1fs.',
729 614 plugin_id, auth_time, _cache_ttl)
730 615
731 616 log.debug('PLUGIN USER DATA: %s', plugin_user)
732 617
733 618 if plugin_user:
734 619 log.debug('Plugin returned proper authentication data')
735 620 return plugin_user
736 621 # we failed to Auth because .auth() method didn't return proper user
737 622 log.debug("User `%s` failed to authenticate against %s",
738 623 display_user, plugin_id)
739 624 return None
General Comments 0
You need to be logged in to leave comments. Login now