Added a ``login`` method to the ``repoze.who.api.API`` object.
This method is convenience for application-driven login code, which would
otherwise need to use private methods of the API, and reach doen into its
plugins.
| | |
| | | repoze.who Changelog |
| | | ==================== |
| | | |
| | | After 2.0a3 (unreleased) |
| | | ------------------------ |
| | | |
| | | - Added a ``login`` method to the ``repoze.who.api.API`` object, as a |
| | | convenience for application-driven login code. |
| | | |
| | | 2.0a3 (2010-09030) |
| | | ------------------ |
| | | |
| | |
| | | who_api = context.who_api_factory(request.environ) |
| | | |
| | | |
| | | .. _writing_custom_login_view: |
| | | |
| | | Writing a Custom Login View |
| | | --------------------------- |
| | | |
| | | :class:`repoze.who.api.API` provides a helper method to assist developers |
| | | who want to control the details of the login view. The following |
| | | BFG example illustrates how this API might be used: |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | def login_view(context, request): |
| | | message = '' |
| | | |
| | | who_api = get_api(request.environ) |
| | | if 'form.login' in request.POST: |
| | | creds = {} |
| | | creds['login'] = request.POST['login'] |
| | | creds['password'] = request.POST['password'] |
| | | authenticated, headers = who_api.login(creds) |
| | | if authenticated: |
| | | return HTTPFound(location='/', headers=headers) |
| | | |
| | | message = 'Invalid login.' |
| | | else: |
| | | # Forcefully forget any existing credentials. |
| | | _, headers = who_api.login({}) |
| | | |
| | | request.response_headerlist = headers |
| | | if 'REMOTE_USER' in request.environ: |
| | | del request.environ['REMOTE_USER'] |
| | | |
| | | return {'message': message} |
| | | |
| | | This application is written as a "hybrid": the :mod:`repoze.who` middleware |
| | | injects the API object into the WSGI enviornment on each request. |
| | | |
| | | - In line 4, this application extracts the API object from the environ |
| | | using :func:`repoze.who.api:get_api`. |
| | | |
| | | - Lines 6 - 8 fabricate a set of credentials, based on the values the |
| | | user entered in the form. |
| | | |
| | | - In line 9, the application asks the API to authenticate those credentials, |
| | | returning an identity and a set of respones headers. |
| | | |
| | | - Lines 10 and 11 handle the case of successful authentication: in this |
| | | case, the application redirects to the site root, setting the headers |
| | | returned by the API object, which will "remember" the user across requests. |
| | | |
| | | - Line 13 is reached on failed login. In this case, the headers returned |
| | | in line 9 will be "forget" headers, clearing any existing cookies or other |
| | | tokens. |
| | | |
| | | - Lines 14 - 16 perform a "fake" login, in order to get the "forget" headers. |
| | | |
| | | - Line 18 sets the "forget" headers to clear any authenticated user for |
| | | subsequent requests. |
| | | |
| | | - Lines 19 - 20 clear any authenticated user for the current request. |
| | | |
| | | - Line 22 returns any message about a failed login to the rendering template. |
| | | |
| | | |
| | | .. _interfaces: |
| | | |
| | | Interfaces |
| | |
| | | (self.interface_registry, |
| | | self.name_registry) = make_registries(identifiers, authenticators, |
| | | challengers, mdproviders) |
| | | self.identifiers = identifiers |
| | | self.authenticators = authenticators |
| | | self.challengers = challengers |
| | | self.mdproviders = mdproviders |
| | |
| | | classification = self.classification = (request_classifier and |
| | | request_classifier(environ)) |
| | | logger and logger.info('request classification: %s' % classification) |
| | | |
| | | |
| | | def authenticate(self): |
| | | |
| | | ids = self._identify() |
| | |
| | | % (identifier, headers)) |
| | | return headers |
| | | |
| | | def login(self, credentials, identifier_name=None): |
| | | """ See IAPI. |
| | | """ |
| | | if identifier_name is not None: |
| | | identifier = self.name_registry[identifier_name] |
| | | else: |
| | | identifier = self.identifiers[0][1] |
| | | # Pretend that the given identifier extracted the identity. |
| | | authenticated = self._authenticate([(identifier, credentials)]) |
| | | if authenticated: |
| | | # and therefore can remember it |
| | | rank, plugin, identifier, identity, userid = authenticated[0] |
| | | headers = identifier.remember(self.environ, identity) |
| | | return identity, headers |
| | | else: |
| | | # or forget it |
| | | headers = identifier.forget(self.environ, None) |
| | | return None, headers |
| | | |
| | | def _identify(self): |
| | | """ See IAPI. |
| | | """ |
| | |
| | | o If 'identity' is not passed, use the identity in the environment. |
| | | """ |
| | | |
| | | def login(credentials, identifier_name=None): |
| | | """ -> (identity, headers) |
| | | |
| | | o This is an API for browser-based application login forms. |
| | | |
| | | o If 'identifier_name' is passed, use it to look up the identifier; |
| | | othewise, use the first configured identifier. |
| | | |
| | | o Attempt to authenticate 'credentials' as though the identifier |
| | | had extracted them. |
| | | |
| | | o On success, 'identity' will be authenticated mapping, and 'headers' |
| | | will be "remember" headers. |
| | | |
| | | o On failure, 'identity' will be None, and response_headers will be |
| | | "forget" headers. |
| | | """ |
| | | |
| | | |
| | | class IPlugin(Interface): |
| | | pass |
| | |
| | | self.failUnless(logger._info[1].endswith(repr(HEADERS))) |
| | | self.assertEqual(len(logger._debug), 0) |
| | | |
| | | def test_login_w_identifier_name_hit(self): |
| | | REMEMBER_HEADERS = [('Foo', 'Bar'), ('Baz', 'Qux')] |
| | | FORGET_HEADERS = [('Spam', 'Blah')] |
| | | class _Identifier: |
| | | def identify(self, environ): |
| | | pass |
| | | def remember(self, environ, identity): |
| | | return REMEMBER_HEADERS |
| | | def forget(self, environ, identity): |
| | | return FORGET_HEADERS |
| | | class _BogusIdentifier: |
| | | def identify(self, environ): |
| | | pass |
| | | def remember(self, environ, identity): |
| | | pass |
| | | def forget(self, environ, identity): |
| | | pass |
| | | authenticator = DummyAuthenticator('chrisid') |
| | | environ = self._makeEnviron() |
| | | identifiers = [('bogus', _BogusIdentifier()), |
| | | ('valid', _Identifier()), |
| | | ] |
| | | api = self._makeOne(identifiers=identifiers, |
| | | authenticators=[('authentic', authenticator)], |
| | | environ=environ) |
| | | identity, headers = api.login({'login': 'chrisid'}, 'valid') |
| | | self.assertEqual(identity['repoze.who.userid'], 'chrisid') |
| | | self.assertEqual(headers, REMEMBER_HEADERS) |
| | | |
| | | def test_login_wo_identifier_name_hit(self): |
| | | REMEMBER_HEADERS = [('Foo', 'Bar'), ('Baz', 'Qux')] |
| | | FORGET_HEADERS = [('Spam', 'Blah')] |
| | | class _Identifier: |
| | | def identify(self, environ): |
| | | pass |
| | | def remember(self, environ, identity): |
| | | return REMEMBER_HEADERS |
| | | def forget(self, environ, identity): |
| | | return FORGET_HEADERS |
| | | class _BogusIdentifier: |
| | | def identify(self, environ): |
| | | pass |
| | | def remember(self, environ, identity): |
| | | pass |
| | | def forget(self, environ, identity): |
| | | pass |
| | | authenticator = DummyAuthenticator('chrisid') |
| | | environ = self._makeEnviron() |
| | | identifiers = [('valid', _Identifier()), |
| | | ('bogus', _BogusIdentifier()), |
| | | ] |
| | | api = self._makeOne(identifiers=identifiers, |
| | | authenticators=[('authentic', authenticator)], |
| | | environ=environ) |
| | | identity, headers = api.login({'login': 'chrisid'}) |
| | | self.failUnless(identity) |
| | | self.assertEqual(headers, REMEMBER_HEADERS) |
| | | |
| | | def test_login_w_identifier_name_miss(self): |
| | | REMEMBER_HEADERS = [('Foo', 'Bar'), ('Baz', 'Qux')] |
| | | FORGET_HEADERS = [('Spam', 'Blah')] |
| | | class _Identifier: |
| | | def identify(self, environ): |
| | | pass |
| | | def remember(self, environ, identity): |
| | | return REMEMBER_HEADERS |
| | | def forget(self, environ, identity): |
| | | return FORGET_HEADERS |
| | | class _BogusIdentifier: |
| | | def identify(self, environ): |
| | | pass |
| | | def remember(self, environ, identity): |
| | | pass |
| | | def forget(self, environ, identity): |
| | | pass |
| | | authenticator = DummyFailAuthenticator() |
| | | environ = self._makeEnviron() |
| | | identifiers = [('bogus', _BogusIdentifier()), |
| | | ('valid', _Identifier()), |
| | | ] |
| | | api = self._makeOne(identifiers=identifiers, |
| | | authenticators=[('authentic', authenticator)], |
| | | environ=environ) |
| | | identity, headers = api.login({'login': 'notchrisid'}, 'valid') |
| | | self.assertEqual(identity, None) |
| | | self.assertEqual(headers, FORGET_HEADERS) |
| | | |
| | | def test_login_wo_identifier_name_miss(self): |
| | | REMEMBER_HEADERS = [('Foo', 'Bar'), ('Baz', 'Qux')] |
| | | FORGET_HEADERS = [('Spam', 'Blah')] |
| | | class _Identifier: |
| | | def identify(self, environ): |
| | | pass |
| | | def remember(self, environ, identity): |
| | | return REMEMBER_HEADERS |
| | | def forget(self, environ, identity): |
| | | return FORGET_HEADERS |
| | | class _BogusIdentifier: |
| | | def identify(self, environ): |
| | | pass |
| | | def remember(self, environ, identity): |
| | | pass |
| | | def forget(self, environ, identity): |
| | | pass |
| | | authenticator = DummyFailAuthenticator() |
| | | environ = self._makeEnviron() |
| | | identifiers = [('valid', _Identifier()), |
| | | ('bogus', _BogusIdentifier()), |
| | | ] |
| | | api = self._makeOne(identifiers=identifiers, |
| | | authenticators=[('authentic', authenticator)], |
| | | environ=environ) |
| | | identity, headers = api.login({'login': 'notchrisid'}) |
| | | self.assertEqual(identity, None) |
| | | self.assertEqual(headers, FORGET_HEADERS) |
| | | |
| | | def test__identify_success(self): |
| | | environ = self._makeEnviron() |
| | | credentials = {'login':'chris', 'password':'password'} |