Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify user_can_authenticate when authenticating user #379

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ History
pending
=======

Backwards-incompatible changes:

* ``OIDCAuthenticationBackend.authenticate`` now calls ``user_can_authenticate``.

Minor:

* Make `get_or_create_user` compatible with custom scope configuration
by moving scope specific code to `describe_user_by_claims`

Expand Down
5 changes: 3 additions & 2 deletions mozilla_django_oidc/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,15 +319,16 @@ def get_or_create_user(self, access_token, id_token, payload):
users = self.filter_users_by_claims(user_info)

if len(users) == 1:
return self.update_user(users[0], user_info)
user = self.update_user(users[0], user_info)
return user if self.user_can_authenticate(user) else None
elif len(users) > 1:
# In the rare case that two user accounts have the same email address,
# bail. Randomly selecting one seems really wrong.
msg = 'Multiple users returned'
raise SuspiciousOperation(msg)
elif self.get_settings('OIDC_CREATE_USER', True):
user = self.create_user(user_info)
return user
return user if self.user_can_authenticate(user) else None
else:
LOGGER.debug('Login failed: No user with %s found, and '
'OIDC_CREATE_USER is False',
Expand Down
104 changes: 104 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,54 @@ 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')
def test_failed_authentication_inactive_user(self, 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 protected]',
is_active=False)
token_mock.return_value = True
get_json_mock = Mock()
get_json_mock.json.return_value = {
'nickname': 'a_username',
'email': '[email protected]'
}
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)
request_mock.post.assert_called_once_with('https://server.example.com/token',
data=post_data,
auth=None,
verify=True,
timeout=None,
proxies=None)
request_mock.get.assert_called_once_with(
'https://server.example.com/user',
headers={'Authorization': 'Bearer access_granted'},
verify=True,
timeout=None,
proxies=None
)

@patch('mozilla_django_oidc.auth.requests')
@patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
@patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_claims')
Expand Down Expand Up @@ -468,6 +516,62 @@ def test_successful_authentication_new_user(self, token_mock, request_mock, algo
proxies=None,
)

@patch.object(settings, 'OIDC_USERNAME_ALGO')
@patch('mozilla_django_oidc.auth.requests')
@patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
@patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.user_can_authenticate')
def test_failed_authentication_new_user_cannot_authenticate(
self, can_authenticate_mock, token_mock, request_mock, algo_mock
):
"""Test successful authentication and user creation."""
auth_request = RequestFactory().get('/foo', {'code': 'foo',
'state': 'bar'})
auth_request.session = {}

can_authenticate_mock.return_value = False
algo_mock.return_value = 'username_algo'
token_mock.return_value = True
get_json_mock = Mock()
get_json_mock.json.return_value = {
'nickname': 'a_username',
'email': '[email protected]'
}
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.assertEqual(User.objects.all().count(), 0)
self.assertIsNone(self.backend.authenticate(request=auth_request))
self.assertEqual(User.objects.all().count(), 1)
user = User.objects.all()[0]
self.assertEqual(user.email, '[email protected]')
self.assertEqual(user.username, 'username_algo')

token_mock.assert_called_once_with('id_token', nonce=None)
request_mock.post.assert_called_once_with('https://server.example.com/token',
data=post_data,
auth=None,
verify=True,
timeout=None,
proxies=None)
request_mock.get.assert_called_once_with(
'https://server.example.com/user',
headers={'Authorization': 'Bearer access_granted'},
verify=True,
timeout=None,
proxies=None,
)

@override_settings(OIDC_TOKEN_USE_BASIC_AUTH=True)
@override_settings(OIDC_STORE_ACCESS_TOKEN=True)
@override_settings(OIDC_STORE_ID_TOKEN=True)
Expand Down