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