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