This commit is contained in:
Peter Bengtsson 2018-05-10 11:24:47 -04:00
Родитель 2ae42f468b
Коммит f47e9d714b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: EEDB25B3B0643BFB
3 изменённых файлов: 12 добавлений и 25 удалений

Просмотреть файл

@ -344,8 +344,8 @@ in other Django projects.
URL where we sent access tokens received as an authorization bearer token. URL where we sent access tokens received as an authorization bearer token.
This URL needs to match the OIDC domain used by the client to authenticate. This URL needs to match the OIDC domain used by the client to authenticate.
The endpoint URL is actually extracted from reading ``/.well-known/openid-configuration`` The value for this setting is usually listed in
on the configured OIDC provider domain. ``/.well-known/openid-configuration`` on the OIDC provider.
Gunicorn settings Gunicorn settings
----------------- -----------------

Просмотреть файл

@ -139,17 +139,12 @@ class OIDCAccessTokenAuthorizationMiddleware:
an authentication backend, there is no 'Set-Cookie' header returned in the an authentication backend, there is no 'Set-Cookie' header returned in the
response. response.
It's admittedly confusing to mix the terms authentication and authorization.
Arguably the client has already *authenticated* and basically converted her
password for an access token. Here in the context of Normandy we kill two
birds with one stone: check that access token and use the user profile that
comes out to sign the user into the rest of the Django request.
The flow is as follows: The flow is as follows:
1. Client trades email/username and password with the OIDC for an access token. 1. (outside Normandy) Client trades email/username and password with the
OIDC provider for an access token.
2. The access token is sent to us here. 2. The access token is sent to us here.
3. We send it to the OIDC provider in return for a user profile 3. We send it to the OIDC provider in return for a user profile.
4. We extract the email from the user profile (and first- and last name) 4. We extract the email from the user profile (and first- and last name)
5. Turn the email into a Django User model instance. 5. Turn the email into a Django User model instance.
6. If possible, extracts the first- and last name and updates the User instance persistently. 6. If possible, extracts the first- and last name and updates the User instance persistently.
@ -164,7 +159,6 @@ class OIDCAccessTokenAuthorizationMiddleware:
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response
self.oidc_user_endpoint = settings.OIDC_USER_ENDPOINT
self.UserModel = get_user_model() self.UserModel = get_user_model()
def __call__(self, request): def __call__(self, request):
@ -193,7 +187,7 @@ class OIDCAccessTokenAuthorizationMiddleware:
access_token = matched.group(1) access_token = matched.group(1)
user_profile = self._fetch_oidc_email(access_token) user_profile = self._fetch_oidc_user_profile(access_token)
email = user_profile.get('email') email = user_profile.get('email')
if not email: if not email:
# This would happen if someone has requested an access token # This would happen if someone has requested an access token
@ -237,23 +231,18 @@ class OIDCAccessTokenAuthorizationMiddleware:
user.last_name = user_profile['family_name'].strip() user.last_name = user_profile['family_name'].strip()
user.save() user.save()
# We *could* do something like this too:
# if user_profile.get('picture'):
# pf = user.get_profile()
# pf.picture = user_profile['picture']
# pf.save()
# Now, let's "become" this user for the rest of the request. # Now, let's "become" this user for the rest of the request.
request.user = user request.user = user
user_logged_in.send(sender=user.__class__, request=request, user=user) user_logged_in.send(sender=user.__class__, request=request, user=user)
@backoff.on_exception( @backoff.on_exception(
backoff.expo, backoff.constant,
requests.exceptions.RequestException, requests.exceptions.RequestException,
max_tries=5, max_tries=5,
) )
def _fetch_oidc_email(self, access_token): def _fetch_oidc_user_profile(self, access_token):
response = requests.get(self.oidc_user_endpoint, headers={ url = settings.OIDC_USER_ENDPOINT
response = requests.get(url, headers={
'Authorization': 'Bearer {0}'.format(access_token) 'Authorization': 'Bearer {0}'.format(access_token)
}) })
if response.status_code == 200: if response.status_code == 200:
@ -262,9 +251,7 @@ class OIDCAccessTokenAuthorizationMiddleware:
# The OIDC provider did not like the access token. # The OIDC provider did not like the access token.
raise PermissionDenied('Unauthorized access token') raise PermissionDenied('Unauthorized access token')
if response.status_code >= 500: if response.status_code >= 500:
raise requests.exceptions.RequestException( raise requests.exceptions.RequestException(f'{response.status_code} on {url}')
f'{response.status_code} on {self.oidc_user_endpoint}'
)
# This could happen if, for some reason, we're not configured to be # This could happen if, for some reason, we're not configured to be
# allowed to talk to the OIDC endpoint. # allowed to talk to the OIDC endpoint.

Просмотреть файл

@ -194,7 +194,7 @@ class OIDC:
# https://$OIDC_DOMAIN/.well-known/openid-configuration and extract the # https://$OIDC_DOMAIN/.well-known/openid-configuration and extract the
# 'userinfo_endoint' value from that during startup or something. # 'userinfo_endoint' value from that during startup or something.
# As of May 2018, the likelyhood of this URL changing and the fact that it's # As of May 2018, the likelyhood of this URL changing and the fact that it's
# the only URL we need, let's just make the setting thee URL that we need # the only URL we need, let's just make the setting the URL that we need
# for being able to authorization by access token. # for being able to authorization by access token.
OIDC_USER_ENDPOINT = values.URLValue('https://auth.mozilla.auth0.com/userinfo') OIDC_USER_ENDPOINT = values.URLValue('https://auth.mozilla.auth0.com/userinfo')