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