Skip to content

Commit

Permalink
Merge pull request #194 from jezdez/verify-claims
Browse files Browse the repository at this point in the history
Add verify_claims method for advanced authenticatio checks (e.g. LDAP groups).
  • Loading branch information
akatsoulas committed Nov 9, 2017
2 parents 1dae28a + 63da4a5 commit 68c03c6
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 1 deletion.
25 changes: 24 additions & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ data from the claims:
class MyOIDCAB(OIDCAuthenticationBackend):
def create_user(self, claims):
user = super(OIDCAuthenticationBackend, self).create_user(claims)
user = super(MyOIDCAB, self).create_user(claims)
user.first_name = claim.get('given_name', '')
user.last_name = claim.get('family_name', '')
Expand Down Expand Up @@ -340,6 +340,29 @@ You might want to do this if you want to control user creation because your
system requires additional process to allow people to use it.


Advanced user verification based on their claims
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In case you need to check additional values in the user's claims to decide
if the authentication should happen at all (included creating new users
if ``OIDC_CREATE_USER`` is ``True``), then you should subclass the
:py:class:`mozilla_django_oidc.auth.OIDCAuthenticationBackend` class and
override the `verify_claims` method. It should return either ``True`` or
``False`` to either continue or stop the whole authentication process.

.. code-block:: python
class MyOIDCAB(OIDCAuthenticationBackend):
def verify_claims(self, claims):
verified = super(MyOIDCAB, self).verify_claims(claims)
is_admin = 'admin' in claims.get('group', [])
return verified and is_admin
.. seealso::

https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims


Troubleshooting
---------------

Expand Down
9 changes: 9 additions & 0 deletions mozilla_django_oidc/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ def filter_users_by_claims(self, claims):
return self.UserModel.objects.none()
return self.UserModel.objects.filter(email__iexact=email)

def verify_claims(self, claims):
"""Verify the provided claims to decide if authentication should be allowed."""
return True

def create_user(self, claims):
"""Return object for a newly created user account."""
# bluntly stolen from django-browserid
Expand Down Expand Up @@ -186,6 +190,11 @@ def authenticate(self, **kwargs):
user_info = user_response.json()
email = user_info.get('email')

claims_verified = self.verify_claims(user_info)
if not claims_verified:
LOGGER.debug('Login failed: Claims verification for %s failed.', email)
return None

# email based filtering
users = self.filter_users_by_claims(user_info)

Expand Down
46 changes: 46 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,52 @@ def test_successful_authentication_existing_user_upper_case(self, token_mock, re
self.assertEqual(auth_request.session.get('oidc_id_token'), 'id_token')
self.assertEqual(auth_request.session.get('oidc_access_token'), 'access_granted')

@patch('mozilla_django_oidc.auth.requests')
@patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
@patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_claims')
def test_failed_authentication_verify_claims(self, claims_mock, token_mock, request_mock):
"""Test successful authentication for existing user."""
auth_request = RequestFactory().get('/foo', {'code': 'foo',
'state': 'bar'})
auth_request.session = {}

User.objects.create_user(username='a_username',
email='email@example.com')
token_mock.return_value = True
claims_mock.return_value = False
get_json_mock = Mock()
claims_response = {
'nickname': 'a_username',
'email': 'email@example.com'
}
get_json_mock.json.return_value = claims_response
request_mock.get.return_value = get_json_mock
post_json_mock = Mock()
post_json_mock.json.return_value = {
'id_token': 'id_token',
'access_token': 'access_granted'
}
request_mock.post.return_value = post_json_mock

post_data = {
'client_id': 'example_id',
'client_secret': 'client_secret',
'grant_type': 'authorization_code',
'code': 'foo',
'redirect_uri': 'http://testserver/callback/'
}
self.assertIsNone(self.backend.authenticate(request=auth_request))
token_mock.assert_called_once_with('id_token', nonce=None)
claims_mock.assert_called_once_with(claims_response)
request_mock.post.assert_called_once_with('https://server.example.com/token',
data=post_data,
verify=True)
request_mock.get.assert_called_once_with(
'https://server.example.com/user',
headers={'Authorization': 'Bearer access_granted'},
verify=True
)

@patch.object(settings, 'OIDC_USERNAME_ALGO')
@patch('mozilla_django_oidc.auth.requests')
@patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
Expand Down

0 comments on commit 68c03c6

Please sign in to comment.