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