idp_oidc: serve user profile information in UserInfo endpoint (#69416)
This commit is contained in:
parent
3e7ae285a7
commit
25a7c66673
|
@ -492,6 +492,7 @@ def authorize_for_client(request, client, redirect_uri):
|
|||
scopes=' '.join(scopes),
|
||||
session_key=request.session.session_key,
|
||||
expired=expired,
|
||||
profile=profile,
|
||||
)
|
||||
acr = '0'
|
||||
if nonce is not None and last_auth.get('nonce') == nonce:
|
||||
|
@ -634,6 +635,7 @@ def idtoken_from_user_credential(request):
|
|||
)
|
||||
username = request.POST.get('username')
|
||||
scope = request.POST.get('scope')
|
||||
profile_id = request.POST.get('profile')
|
||||
|
||||
# scope is ignored, we used the configured scope
|
||||
|
||||
|
@ -695,11 +697,28 @@ def idtoken_from_user_credential(request):
|
|||
expires_in = datetime.timedelta(seconds=app_settings.ACCESS_TOKEN_DURATION)
|
||||
else:
|
||||
expires_in = client.access_token_duration
|
||||
|
||||
profile = None
|
||||
if profile_id:
|
||||
if not client.activate_user_profiles:
|
||||
raise AccessDenied(
|
||||
_('User profile requested yet client does not manage profiles.'), client=client
|
||||
)
|
||||
try:
|
||||
profile = Profile.objects.get(id=profile_id, user=user)
|
||||
except Profile.DoesNotExist:
|
||||
raise AccessDenied(_('Invalid profile'), client=client)
|
||||
|
||||
access_token = models.OIDCAccessToken.objects.create(
|
||||
client=client, user=user, scopes=' '.join(scopes), session_key='', expired=iat + expires_in
|
||||
client=client,
|
||||
user=user,
|
||||
scopes=' '.join(scopes),
|
||||
session_key='',
|
||||
expired=iat + expires_in,
|
||||
profile=profile,
|
||||
)
|
||||
# make id_token
|
||||
id_token = utils.create_user_info(request, client, user, scopes, id_token=True)
|
||||
id_token = utils.create_user_info(request, client, user, scopes, profile=profile, id_token=True)
|
||||
exp = iat + idtoken_duration(client)
|
||||
id_token.update(
|
||||
{
|
||||
|
@ -748,6 +767,7 @@ def tokens_from_authz_code(request):
|
|||
scopes=oidc_code.scopes,
|
||||
session_key=oidc_code.session_key,
|
||||
expired=expired,
|
||||
profile=oidc_code.profile,
|
||||
)
|
||||
start = now()
|
||||
acr = '0'
|
||||
|
@ -835,7 +855,11 @@ def user_info(request, *args, **kwargs):
|
|||
try:
|
||||
access_token = authenticate_access_token(request)
|
||||
user_info = utils.create_user_info(
|
||||
request, access_token.client, access_token.user, access_token.scope_set()
|
||||
request,
|
||||
access_token.client,
|
||||
access_token.user,
|
||||
access_token.scope_set(),
|
||||
profile=access_token.profile,
|
||||
)
|
||||
return JsonResponse(user_info)
|
||||
except OIDCException as e:
|
||||
|
|
|
@ -30,11 +30,19 @@ from jwcrypto.jwt import JWT
|
|||
|
||||
from authentic2.custom_user.models import Profile, ProfileType
|
||||
from authentic2.utils.misc import make_url
|
||||
from authentic2_idp_oidc.models import OIDCAccessToken, OIDCCode
|
||||
from authentic2_idp_oidc.utils import get_jwkset, make_pairwise_sub, make_sub, reverse_pairwise_sub
|
||||
from authentic2_idp_oidc.models import OIDCAccessToken, OIDCClient, OIDCCode
|
||||
from authentic2_idp_oidc.utils import (
|
||||
base64url,
|
||||
get_first_ec_sig_key,
|
||||
get_first_rsa_sig_key,
|
||||
get_jwkset,
|
||||
make_pairwise_sub,
|
||||
make_sub,
|
||||
reverse_pairwise_sub,
|
||||
)
|
||||
|
||||
from .. import utils
|
||||
from .conftest import client_authentication_headers
|
||||
from .conftest import bearer_authentication_headers, client_authentication_headers
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
@ -219,6 +227,17 @@ def test_login_profile_selection(app, oidc_client, profile_user, profile_setting
|
|||
assert claims['entity_data_au'] == 'ie'
|
||||
assert claims['entity_data_ts'] == 'rn'
|
||||
|
||||
user_info_url = make_url('oidc-user-info')
|
||||
response = app.get(user_info_url, headers=bearer_authentication_headers(access_token))
|
||||
|
||||
assert response.json['profile_type'] == 'one-delegate-type'
|
||||
assert response.json['profile_identifier'] == 'Entity 1011'
|
||||
assert response.json['email'] == 'delegate@example1011.org'
|
||||
assert response.json['sub'] == claims['sub']
|
||||
assert response.json['entity_name'] == 'Foobar'
|
||||
assert response.json['entity_data_au'] == 'ie'
|
||||
assert response.json['entity_data_ts'] == 'rn'
|
||||
|
||||
|
||||
def test_login_implicit(app, oidc_client, profile_user, profile_settings):
|
||||
oidc_client.idtoken_algo = oidc_client.ALGO_HMAC
|
||||
|
@ -277,6 +296,17 @@ def test_login_implicit(app, oidc_client, profile_user, profile_settings):
|
|||
assert claims['entity_data_au'] == 'ie'
|
||||
assert claims['entity_data_ts'] == 'rn'
|
||||
|
||||
user_info_url = make_url('oidc-user-info')
|
||||
response = app.get(user_info_url, headers=bearer_authentication_headers(access_token))
|
||||
|
||||
assert response.json['profile_type'] == 'one-delegate-type'
|
||||
assert response.json['profile_identifier'] == 'Entity 1011'
|
||||
assert response.json['email'] == 'delegate@example1011.org'
|
||||
assert response.json['sub'] == claims['sub']
|
||||
assert response.json['entity_name'] == 'Foobar'
|
||||
assert response.json['entity_data_au'] == 'ie'
|
||||
assert response.json['entity_data_ts'] == 'rn'
|
||||
|
||||
|
||||
def test_login_profile_reversible_sub(app, oidc_client, profile_user, profile_settings):
|
||||
oidc_client.idtoken_algo = oidc_client.ALGO_EC
|
||||
|
@ -428,3 +458,103 @@ def test_modify_user_info_hook(app, oidc_client, profile_settings, profile_user,
|
|||
assert claims['email'] == 'def@ad.dre.ss'
|
||||
assert claims['profile'] == profile_user.profiles.first().id
|
||||
assert claims['customclaim'] == 'whatever'
|
||||
|
||||
|
||||
def test_profile_selection_user_credentials_grant(app, oidc_client, admin, simple_user):
|
||||
oidc_client.authorization_flow = OIDCClient.FLOW_RESOURCE_OWNER_CRED
|
||||
oidc_client.scope = 'openid email'
|
||||
oidc_client.save()
|
||||
token_url = make_url('oidc-token')
|
||||
|
||||
if oidc_client.idtoken_algo == OIDCClient.ALGO_HMAC:
|
||||
k = base64url(oidc_client.client_secret.encode('utf-8'))
|
||||
jwk = JWK(kty='oct', k=force_text(k))
|
||||
elif oidc_client.idtoken_algo == OIDCClient.ALGO_RSA:
|
||||
jwk = get_first_rsa_sig_key()
|
||||
elif oidc_client.idtoken_algo == OIDCClient.ALGO_EC:
|
||||
jwk = get_first_ec_sig_key()
|
||||
|
||||
profile_type = ProfileType.objects.create(
|
||||
name='One Manager Type',
|
||||
slug='one-manager-type',
|
||||
)
|
||||
profile = Profile.objects.create(
|
||||
user=simple_user,
|
||||
identifier='abc',
|
||||
profile_type=profile_type,
|
||||
email='profile@ad.dr.ess',
|
||||
)
|
||||
|
||||
params = {
|
||||
'client_id': oidc_client.client_id,
|
||||
'client_secret': oidc_client.client_secret,
|
||||
'grant_type': 'password',
|
||||
'username': simple_user.username,
|
||||
'password': simple_user.username,
|
||||
# profile id yet client doesn't active profile management
|
||||
'profile': profile.id,
|
||||
}
|
||||
response = app.post(token_url, params=params, status=400)
|
||||
|
||||
assert response.json['error'] == 'access_denied'
|
||||
assert response.json['error_description'] == 'User profile requested yet client does not manage profiles.'
|
||||
|
||||
oidc_client.activate_user_profiles = True
|
||||
oidc_client.save()
|
||||
|
||||
# wrong profile id
|
||||
params['profile'] = profile.id + 1
|
||||
response = app.post(token_url, params=params, status=400)
|
||||
|
||||
assert response.json['error'] == 'access_denied'
|
||||
assert response.json['error_description'] == 'Invalid profile'
|
||||
|
||||
another_profile = Profile.objects.create(
|
||||
user=admin,
|
||||
identifier='def',
|
||||
profile_type=profile_type,
|
||||
email='admin@ad.dr.ess',
|
||||
)
|
||||
|
||||
# another user's profile
|
||||
params['profile'] = another_profile.id
|
||||
response = app.post(token_url, params=params, status=400)
|
||||
|
||||
assert response.json['error'] == 'access_denied'
|
||||
# the oidc provider doesn't reveal that this is a valid profile id, linked to another user:
|
||||
assert response.json['error_description'] == 'Invalid profile'
|
||||
|
||||
# correct profile
|
||||
params['profile'] = profile.id
|
||||
response = app.post(token_url, params=params)
|
||||
token = response.json['id_token']
|
||||
jwt = JWT()
|
||||
jwt.deserialize(token, key=jwk)
|
||||
claims = json.loads(jwt.claims)
|
||||
|
||||
assert set(claims) == {
|
||||
'acr',
|
||||
'aud',
|
||||
'auth_time',
|
||||
'exp',
|
||||
'iat',
|
||||
'iss',
|
||||
'sub',
|
||||
# profile-related claims in the id token:
|
||||
'profile_type',
|
||||
'profile_identifier',
|
||||
'email',
|
||||
'email_verified',
|
||||
}
|
||||
assert claims['profile_type'] == 'one-manager-type'
|
||||
assert claims['profile_identifier'] == 'abc'
|
||||
assert claims['email'] == 'profile@ad.dr.ess'
|
||||
|
||||
access_token = response.json['access_token']
|
||||
user_info_url = make_url('oidc-user-info')
|
||||
response = app.get(user_info_url, headers=bearer_authentication_headers(access_token))
|
||||
|
||||
assert response.json['profile_type'] == 'one-manager-type'
|
||||
assert response.json['profile_identifier'] == 'abc'
|
||||
assert response.json['email'] == 'profile@ad.dr.ess'
|
||||
assert response.json['sub'] == claims['sub']
|
||||
|
|
Loading…
Reference in New Issue