diff --git a/docs/sections/claims.rst b/docs/sections/claims.rst index 96e82c8..78160f7 100644 --- a/docs/sections/claims.rst +++ b/docs/sections/claims.rst @@ -12,17 +12,17 @@ List of all the attributes grouped by scopes: +--------------------+----------------+-----------------------+------------------------+ | profile | email | phone | address | +====================+================+=======================+========================+ -| name | email | phone_number | address_formatted | +| name | email | phone_number | formatted | +--------------------+----------------+-----------------------+------------------------+ -| given_name | email_verified | phone_number_verified | address_street_address | +| given_name | email_verified | phone_number_verified | street_address | +--------------------+----------------+-----------------------+------------------------+ -| family_name | | | address_locality | +| family_name | | | locality | +--------------------+----------------+-----------------------+------------------------+ -| middle_name | | | address_region | +| middle_name | | | region | +--------------------+----------------+-----------------------+------------------------+ -| nickname | | | address_postal_code | +| nickname | | | postal_code | +--------------------+----------------+-----------------------+------------------------+ -| preferred_username | | | address_country | +| preferred_username | | | country | +--------------------+----------------+-----------------------+------------------------+ | profile | | | | +--------------------+----------------+-----------------------+------------------------+ @@ -41,35 +41,22 @@ List of all the attributes grouped by scopes: | updated_at | | | | +--------------------+----------------+-----------------------+------------------------+ -Example using a django model:: +Somewhere in your Django ``settings.py``:: - from django.conf import settings - from django.db import models + OIDC_USERINFO = 'myproject.oidc_provider_settings.userinfo' - class UserInfo(models.Model): +Then create the function for the ``OIDC_USERINFO`` setting:: - GENDER_CHOICES = [ - ('F', 'Female'), - ('M', 'Male'), - ] + def userinfo(claims, user): - user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True) - - given_name = models.CharField(max_length=255, blank=True, null=True) - family_name = models.CharField(max_length=255, blank=True, null=True) - gender = models.CharField(max_length=100, choices=GENDER_CHOICES, null=True) - birthdate = models.DateField(null=True) - updated_at = models.DateTimeField(auto_now=True, null=True) + claims['name'] = '{0} {1}'.format(user.first_name, user.last_name) + claims['given_name'] = user.first_name + claims['family_name'] = user.last_name + claims['email'] = user.email + claims['address']['street_address'] = '...' - email_verified = models.NullBooleanField(default=False) + return claims - phone_number = models.CharField(max_length=255, blank=True, null=True) - phone_number_verified = models.NullBooleanField(default=False) - - address_locality = models.CharField(max_length=255, blank=True, null=True) - address_country = models.CharField(max_length=255, blank=True, null=True) - - @classmethod - def get_by_user(cls, user): - return cls.objects.get(user=user) +.. note:: + Please **DO NOT** add extra keys or delete the existing ones in the ``claims`` dict. If you want to add extra claims to some scopes you can use the ``OIDC_EXTRA_SCOPE_CLAIMS`` setting. diff --git a/docs/sections/settings.rst b/docs/sections/settings.rst index 4f07360..e51194e 100644 --- a/docs/sections/settings.rst +++ b/docs/sections/settings.rst @@ -162,4 +162,21 @@ Expressed in seconds. Default is ``60*60``. OIDC_USERINFO ============= -OPTIONAL. ``str``. A string with the location of your class. Read **Standard Claims** section. +OPTIONAL. ``str``. A string with the location of your function. Read **Standard Claims** section. + +The function receives a ``claims`` dictionary with all the standard claims and ``user`` instance. Must returns the ``claims`` dict again. + +Example usage:: + + def userinfo(claims, user): + + claims['name'] = '{0} {1}'.format(user.first_name, user.last_name) + claims['given_name'] = user.first_name + claims['family_name'] = user.last_name + claims['email'] = user.email + claims['address']['street_address'] = '...' + + return claims + +.. note:: + Please **DO NOT** add extra keys or delete the existing ones in the ``claims`` dict. If you want to add extra claims to some scopes you can use the ``OIDC_EXTRA_SCOPE_CLAIMS`` setting. diff --git a/oidc_provider/lib/claims.py b/oidc_provider/lib/claims.py index 0e288e2..4330ee3 100644 --- a/oidc_provider/lib/claims.py +++ b/oidc_provider/lib/claims.py @@ -3,11 +3,20 @@ from django.utils.translation import ugettext as _ from oidc_provider import settings +STANDARD_CLAIMS = { + 'name': '', 'given_name': '', 'family_name': '', 'middle_name': '', 'nickname': '', + 'preferred_username': '', 'profile': '', 'picture': '', 'website': '', 'gender': '', + 'birthdate': '', 'zoneinfo': '', 'locale': '', 'updated_at': '', 'email': '', 'email_verified': '', + 'phone_number': '', 'phone_number_verified': '', 'address': { 'formatted': '', + 'street_address': '', 'locality': '', 'region': '', 'postal_code': '', 'country': '', }, +} + + class ScopeClaims(object): def __init__(self, user, scopes): self.user = user - self.userinfo = settings.get('OIDC_USERINFO', import_str=True).get_by_user(self.user) + self.userinfo = settings.get('OIDC_USERINFO', import_str=True)(STANDARD_CLAIMS, self.user) self.scopes = scopes def create_response_dic(self): @@ -85,20 +94,20 @@ class StandardScopeClaims(ScopeClaims): ) def scope_profile(self): dic = { - 'name': getattr(self.userinfo, 'name', None), - 'given_name': getattr(self.userinfo, 'given_name', None), - 'family_name': getattr(self.userinfo, 'family_name', None), - 'middle_name': getattr(self.userinfo, 'middle_name', None), - 'nickname': getattr(self.userinfo, 'nickname', None), - 'preferred_username': getattr(self.userinfo, 'preferred_username', None), - 'profile': getattr(self.userinfo, 'profile', None), - 'picture': getattr(self.userinfo, 'picture', None), - 'website': getattr(self.userinfo, 'website', None), - 'gender': getattr(self.userinfo, 'gender', None), - 'birthdate': getattr(self.userinfo, 'birthdate', None), - 'zoneinfo': getattr(self.userinfo, 'zoneinfo', None), - 'locale': getattr(self.userinfo, 'locale', None), - 'updated_at': getattr(self.userinfo, 'updated_at', None), + 'name': self.userinfo.get('name'), + 'given_name': self.userinfo.get('given_name'), + 'family_name': self.userinfo.get('family_name'), + 'middle_name': self.userinfo.get('middle_name'), + 'nickname': self.userinfo.get('nickname'), + 'preferred_username': self.userinfo.get('preferred_username'), + 'profile': self.userinfo.get('profile'), + 'picture': self.userinfo.get('picture'), + 'website': self.userinfo.get('website'), + 'gender': self.userinfo.get('gender'), + 'birthdate': self.userinfo.get('birthdate'), + 'zoneinfo': self.userinfo.get('zoneinfo'), + 'locale': self.userinfo.get('locale'), + 'updated_at': self.userinfo.get('updated_at'), } return dic @@ -109,8 +118,8 @@ class StandardScopeClaims(ScopeClaims): ) def scope_email(self): dic = { - 'email': getattr(self.user, 'email', None), - 'email_verified': getattr(self.userinfo, 'email_verified', None), + 'email': self.userinfo.get('email'), + 'email_verified': self.userinfo.get('email_verified'), } return dic @@ -121,8 +130,8 @@ class StandardScopeClaims(ScopeClaims): ) def scope_phone(self): dic = { - 'phone_number': getattr(self.userinfo, 'phone_number', None), - 'phone_number_verified': getattr(self.userinfo, 'phone_number_verified', None), + 'phone_number': self.userinfo.get('phone_number'), + 'phone_number_verified': self.userinfo.get('phone_number_verified'), } return dic @@ -134,12 +143,12 @@ class StandardScopeClaims(ScopeClaims): def scope_address(self): dic = { 'address': { - 'formatted': getattr(self.userinfo, 'address_formatted', None), - 'street_address': getattr(self.userinfo, 'address_street_address', None), - 'locality': getattr(self.userinfo, 'address_locality', None), - 'region': getattr(self.userinfo, 'address_region', None), - 'postal_code': getattr(self.userinfo, 'address_postal_code', None), - 'country': getattr(self.userinfo, 'address_country', None), + 'formatted': self.userinfo.get('address', {}).get('formatted'), + 'street_address': self.userinfo.get('address', {}).get('street_address'), + 'locality': self.userinfo.get('address', {}).get('locality'), + 'region': self.userinfo.get('address', {}).get('region'), + 'postal_code': self.userinfo.get('address', {}).get('postal_code'), + 'country': self.userinfo.get('address', {}).get('country'), } } diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 01f49d8..d2d1951 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -214,10 +214,13 @@ class AuthorizeEndpoint(object): Return a list with the description of all the scopes requested. """ scopes = StandardScopeClaims.get_scopes_info(self.params.scope) - scopes_extra = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True).get_scopes_info(self.params.scope) - for index_extra, scope_extra in enumerate(scopes_extra): - for index, scope in enumerate(scopes[:]): - if scope_extra['scope'] == scope['scope']: - del scopes[index] + if settings.get('OIDC_EXTRA_SCOPE_CLAIMS'): + scopes_extra = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True).get_scopes_info(self.params.scope) + for index_extra, scope_extra in enumerate(scopes_extra): + for index, scope in enumerate(scopes[:]): + if scope_extra['scope'] == scope['scope']: + del scopes[index] + else: + scopes_extra = [] return scopes + scopes_extra diff --git a/oidc_provider/lib/utils/common.py b/oidc_provider/lib/utils/common.py index 78305c6..6b625e3 100644 --- a/oidc_provider/lib/utils/common.py +++ b/oidc_provider/lib/utils/common.py @@ -45,14 +45,12 @@ def get_issuer(site_url=None, request=None): return issuer -class DefaultUserInfo(object): +def default_userinfo(claims, user): """ - Default class for setting OIDC_USERINFO. + Default function for setting OIDC_USERINFO. + `claims` is a dict that contains all the OIDC standard claims. """ - - @classmethod - def get_by_user(cls, user): - return None + return claims def default_sub_generator(user): diff --git a/oidc_provider/settings.py b/oidc_provider/settings.py index 00f2c70..8e16aab 100644 --- a/oidc_provider/settings.py +++ b/oidc_provider/settings.py @@ -41,9 +41,9 @@ class DefaultSettings(object): def OIDC_EXTRA_SCOPE_CLAIMS(self): """ OPTIONAL. A string with the location of your class. - Used to add extra scopes specific for your app. + Used to add extra scopes specific for your app. """ - return 'oidc_provider.lib.claims.ScopeClaims' + return None @property def OIDC_IDTOKEN_EXPIRE(self): @@ -95,10 +95,10 @@ class DefaultSettings(object): @property def OIDC_USERINFO(self): """ - OPTIONAL. A string with the location of your class. - Used to add extra scopes specific for your app. + OPTIONAL. A string with the location of your function. + Used to populate standard claims with your user information. """ - return 'oidc_provider.lib.utils.common.DefaultUserInfo' + return 'oidc_provider.lib.utils.common.default_userinfo' @property def OIDC_IDTOKEN_PROCESSING_HOOK(self): diff --git a/oidc_provider/tests/app/settings.py b/oidc_provider/tests/app/settings.py index 281b28c..113f43c 100644 --- a/oidc_provider/tests/app/settings.py +++ b/oidc_provider/tests/app/settings.py @@ -58,4 +58,4 @@ USE_TZ = True # OIDC Provider settings. SITE_URL = 'http://localhost:8000' -OIDC_USERINFO = 'oidc_provider.tests.app.utils.FakeUserInfo' +OIDC_USERINFO = 'oidc_provider.tests.app.utils.userinfo' diff --git a/oidc_provider/tests/app/utils.py b/oidc_provider/tests/app/utils.py index 684dac8..616b130 100644 --- a/oidc_provider/tests/app/utils.py +++ b/oidc_provider/tests/app/utils.py @@ -73,27 +73,16 @@ def is_code_valid(url, user, client): return is_code_ok -class FakeUserInfo(object): +def userinfo(claims, user): """ - Fake class for setting OIDC_USERINFO. + Fake function for setting OIDC_USERINFO. """ - - given_name = 'John' - family_name = 'Doe' - nickname = 'johndoe' - website = 'http://johndoe.com' - - phone_number = '+49-89-636-48018' - phone_number_verified = True - - address_street_address = 'Evergreen 742' - address_locality = 'Glendive' - address_region = 'Montana' - address_country = 'United States' - - @classmethod - def get_by_user(cls, user): - return cls() + claims['given_name'] = 'John' + claims['family_name'] = 'Doe' + claims['name'] = '{0} {1}'.format(claims['given_name'], claims['family_name']) + claims['email'] = user.email + claims['address']['country'] = 'Argentina' + return claims def fake_sub_generator(user): diff --git a/oidc_provider/views.py b/oidc_provider/views.py index 417958e..a22ca17 100644 --- a/oidc_provider/views.py +++ b/oidc_provider/views.py @@ -157,13 +157,11 @@ def userinfo(request, *args, **kwargs): } standard_claims = StandardScopeClaims(token.user, token.scope) - dic.update(standard_claims.create_response_dic()) - extra_claims = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True)( - token.user, token.scope) - - dic.update(extra_claims.create_response_dic()) + if settings.get('OIDC_EXTRA_SCOPE_CLAIMS'): + extra_claims = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True)(token.user, token.scope) + dic.update(extra_claims.create_response_dic()) response = JsonResponse(dic, status=200) response['Cache-Control'] = 'no-store'