def setUp(self): User = get_user_model() Role = get_role_model() self.user = User.objects.create_user(self.LOGIN, password=self.PASSWORD, email=self.EMAIL, first_name=self.FIRST_NAME, last_name=self.LAST_NAME) self.service = Service.objects.create( name=self.NAME, slug=self.SLUG, urls=self.URL, identifier_attribute='django_user_username', ou=get_default_ou(), logout_url=self.URL + 'logout/') self.service_attribute1 = Attribute.objects.create( service=self.service, slug='email', attribute_name='django_user_email') self.service2 = Service.objects.create( name=self.NAME2, slug=self.SLUG2, urls=self.URL2, ou=get_default_ou(), identifier_attribute='django_user_email') self.service2_attribute1 = Attribute.objects.create( service=self.service2, slug='username', attribute_name='django_user_username') self.authorized_service = Role.objects.create(name='rogue', ou=get_default_ou()) self.factory = RequestFactory()
def setUp(self): User = get_user_model() self.user = User.objects.create_user( self.LOGIN, password=self.PASSWORD, email=self.EMAIL, first_name=self.FIRST_NAME, last_name=self.LAST_NAME ) self.service = Service.objects.create( name=self.NAME, slug=self.SLUG, urls=self.URL, identifier_attribute="django_user_username", ou=get_default_ou(), logout_url=self.URL + "logout/", ) self.service_attribute1 = Attribute.objects.create( service=self.service, slug="email", attribute_name="django_user_email" ) self.service2 = Service.objects.create( name=self.NAME2, slug=self.SLUG2, urls=self.URL2, ou=get_default_ou(), identifier_attribute="django_user_email", ) self.service2_attribute1 = Attribute.objects.create( service=self.service2, slug="username", attribute_name="django_user_username" ) self.factory = RequestFactory()
def test_api_users_list_by_authorized_service(app, superuser): from authentic2.models import Service app.authorization = ('Basic', (superuser.username, superuser.username)) User = get_user_model() Role = get_role_model() user1 = User.objects.create(username='******') user2 = User.objects.create(username='******') user3 = User.objects.create(username='******') role1 = Role.objects.create(name='role1') role2 = Role.objects.create(name='role2') role1.add_child(role2) user1.roles = [role1] user2.roles = [role2] service1 = Service.objects.create(ou=get_default_ou(), name='service1', slug='service1') service1.add_authorized_role(role1) service2 = Service.objects.create(ou=get_default_ou(), name='service2', slug='service2') resp = app.get('/api/users/') assert len(resp.json['results']) == 4 resp = app.get('/api/users/?service-ou=default&service-slug=service1') assert len(resp.json['results']) == 2 assert set(user['username'] for user in resp.json['results']) == set(['user1', 'user2']) resp = app.get('/api/users/?service-ou=default&service-slug=service2') assert len(resp.json['results']) == 4
def test_keep_password_in_session(slapd, settings, client): settings.LDAP_AUTH_SETTINGS = [{ 'url': [slapd.ldap_url], 'basedn': u'o=ôrga', 'use_tls': False, 'keep_password_in_session': True, }] result = client.post('/login/', { 'login-password-submit': '1', 'username': USERNAME, 'password': PASS.decode('utf-8') }, follow=True) assert result.status_code == 200 assert 'Étienne Michu' in str(result) User = get_user_model() assert User.objects.count() == 1 user = User.objects.get() assert user.username == u'%s@ldap' % USERNAME assert user.first_name == u'Étienne' assert user.last_name == 'Michu' assert user.ou == get_default_ou() assert not user.check_password(PASS) assert client.session['ldap-data']['password'] assert DN in result.context['request'].user.ldap_data['password'] assert crypto.aes_base64_decrypt( settings.SECRET_KEY, result.context['request'].user.ldap_data['password'][DN]) == PASS
def test_simple(slapd, settings, client): settings.LDAP_AUTH_SETTINGS = [{ 'url': [slapd.ldap_url], 'basedn': u'o=ôrga', 'use_tls': False, }] result = client.post('/login/', { 'login-password-submit': '1', 'username': USERNAME, 'password': PASS }, follow=True) assert result.status_code == 200 assert 'Étienne Michu' in str(result) User = get_user_model() assert User.objects.count() == 1 user = User.objects.get() assert user.username == u'%s@ldap' % USERNAME assert user.first_name == u'Étienne' assert user.last_name == 'Michu' assert user.is_active is True assert user.is_superuser is False assert user.is_staff is False assert user.groups.count() == 0 assert user.ou == get_default_ou() assert not user.check_password(PASS) assert 'password' not in client.session['ldap-data']
def test_expired_manager(db, simple_user): expired = now() - datetime.timedelta(seconds=1) not_expired = now() + datetime.timedelta(days=1) client = OIDCClient.objects.create( name='client', slug='client', ou=get_default_ou(), redirect_uris='https://example.com/') OIDCAuthorization.objects.create( client=client, user=simple_user, scopes='openid', expired=expired) OIDCAuthorization.objects.create( client=client, user=simple_user, scopes='openid', expired=not_expired) assert OIDCAuthorization.objects.count() == 2 OIDCAuthorization.objects.cleanup() assert OIDCAuthorization.objects.count() == 1 OIDCCode.objects.create( client=client, user=simple_user, scopes='openid', redirect_uri='https://example.com/', session_key='xxx', auth_time=now(), expired=expired) OIDCCode.objects.create( client=client, user=simple_user, scopes='openid', redirect_uri='https://example.com/', session_key='xxx', auth_time=now(), expired=not_expired) assert OIDCCode.objects.count() == 2 OIDCCode.objects.cleanup() assert OIDCCode.objects.count() == 1 OIDCAccessToken.objects.create( client=client, user=simple_user, scopes='openid', session_key='xxx', expired=expired) OIDCAccessToken.objects.create( client=client, user=simple_user, scopes='openid', session_key='xxx', expired=not_expired) assert OIDCAccessToken.objects.count() == 2 OIDCAccessToken.objects.cleanup() assert OIDCAccessToken.objects.count() == 1
def test_role_members_show_all_ou(app, superuser, settings): Role = get_role_model() r = Role.objects.create(name='role', slug='role', ou=get_default_ou()) url = reverse('a2-manager-role-members', kwargs={'pk': r.pk}) response = login(app, superuser, url) assert not response.context['form'].fields['user'].queryset.query.where settings.A2_MANAGER_ROLE_MEMBERS_FROM_OU = True response = app.get(url) assert response.context['form'].fields['user'].queryset.query.where
def oidc_provider(request, db, oidc_provider_jwkset): idtoken_algo = request.param from authentic2_auth_oidc.utils import get_provider, get_provider_by_issuer get_provider.cache.clear() get_provider_by_issuer.cache.clear() if idtoken_algo == OIDCProvider.ALGO_RSA: jwkset = json.loads(oidc_provider_jwkset.export()) else: jwkset = None provider = OIDCProvider.objects.create( id=1, ou=get_default_ou(), name='OIDIDP', issuer='https://idp.example.com/', authorization_endpoint='https://idp.example.com/authorize', token_endpoint='https://idp.example.com/token', end_session_endpoint='https://idp.example.com/logout', userinfo_endpoint='https://idp.example.com/user_info', token_revocation_endpoint='https://idp.example.com/revoke', max_auth_age=10, strategy=OIDCProvider.STRATEGY_CREATE, jwkset_json=jwkset, idtoken_algo=idtoken_algo, ) provider.full_clean() OIDCClaimMapping.objects.create( provider=provider, claim='sub', attribute='username', idtoken_claim=True) OIDCClaimMapping.objects.create( provider=provider, claim='email', attribute='email') OIDCClaimMapping.objects.create( provider=provider, claim='email', required=True, attribute='email') OIDCClaimMapping.objects.create( provider=provider, claim='given_name', required=True, verified=OIDCClaimMapping.ALWAYS_VERIFIED, attribute='first_name') OIDCClaimMapping.objects.create( provider=provider, claim='family_name', required=True, verified=OIDCClaimMapping.VERIFIED_CLAIM, attribute='last_name') OIDCClaimMapping.objects.create( provider=provider, claim='ou', attribute='ou__slug') return provider
def admin(db): user = create_user(username='******', first_name='global', last_name='admin', email='*****@*****.**', is_active=True, ou=get_default_ou()) Role = get_role_model() user.roles.add(Role.objects.get(slug='_a2-manager')) return user
def test_register_no_email_validation(app, admin, django_user_model): User = django_user_model password = '******' username = '******' email = '*****@*****.**' first_name = 'John' last_name = 'Doe' return_url = 'http://sp.example.com/validate/' # invalid payload payload = { 'last_name': last_name, 'return_url': return_url, } headers = basic_authorization_header(admin) assert len(mail.outbox) == 0 response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers, status=400) assert 'errors' in response.json assert response.json['result'] == 0 assert response.json['errors'] == { '__all__': ['You must set at least a username, an email or a first name and a last name'], } # valid payload payload = { 'username': username, 'email': email, 'first_name': first_name, 'last_name': last_name, 'password': password, 'no_email_validation': True, 'return_url': return_url, } assert len(mail.outbox) == 0 response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers) assert len(mail.outbox) == 0 assert response.status_code == 201 assert response.json['result'] == 1 assert response.json['user']['username'] == username assert response.json['user']['email'] == email assert response.json['user']['first_name'] == first_name assert response.json['user']['last_name'] == last_name assert check_password(password, response.json['user']['password']) assert response.json['token'] assert response.json['validation_url'].startswith('http://testserver/accounts/activate/') assert User.objects.count() == 2 user = User.objects.latest('id') assert user.ou == get_default_ou() assert user.username == username assert user.email == email assert user.first_name == first_name assert user.last_name == last_name assert user.check_password(password)
def test_api_post_role_no_ou(app, superuser): app.authorization = ('Basic', (superuser.username, superuser.username)) Role = get_role_model() role_data = { 'slug': 'tea-manager', 'name': 'Tea Manager', } resp = app.post_json('/api/roles/', params=role_data) uuid = resp.json['uuid'] role = Role.objects.get(uuid=uuid) assert role.ou == get_default_ou()
def populate_user_ou(self, user, dn, conn, block, attributes): '''Assign LDAP user to an ou, the default one if ou_slug setting is None''' ou_slug = block['ou_slug'] OU = get_ou_model() if ou_slug: ou_slug = unicode(ou_slug) try: user.ou = OU.objects.get(slug=ou_slug) except OU.DoesNotExist: raise ImproperlyConfigured('ou_slug value is wrong for ldap %r', block['url']) else: user.ou = get_default_ou()
def oidc_client(request, superuser, app): url = reverse('admin:authentic2_idp_oidc_oidcclient_add') assert OIDCClient.objects.count() == 0 response = utils.login(app, superuser, path=url) response.form.set('name', 'oidcclient') response.form.set('slug', 'oidcclient') response.form.set('ou', get_default_ou().pk) response.form.set('unauthorized_url', 'https://example.com/southpark/') response.form.set('redirect_uris', 'https://example.com/callback') for key, value in request.param.iteritems(): response.form.set(key, value) response = response.form.submit().follow() assert OIDCClient.objects.count() == 1 client = OIDCClient.objects.get() utils.logout(app) return client
def test_simple(settings, client): settings.LDAP_AUTH_SETTINGS = [{ 'url': [slapd.ldapi_url], 'basedn': 'o=orga', 'use_tls': False, }] result = client.post('/login/', {'login-password-submit': '1', 'username': '******', 'password': '******'}, follow=True) assert result.status_code == 200 assert 'id="user"' in str(result) assert 'Étienne Michu' in str(result) User = get_user_model() assert User.objects.count() == 1 user = User.objects.get() assert user.username == 'etienne.michu@ldap' assert user.first_name == u'Étienne' assert user.last_name == 'Michu' assert user.ou == get_default_ou() assert not user.check_password('pass')
def get_form_kwargs(self, **kwargs): '''Initialize mail from token''' kwargs = super(RegistrationCompletionView, self).get_form_kwargs(**kwargs) if 'ou' in self.token: OU = get_ou_model() ou = get_object_or_404(OU, id=self.token['ou']) else: ou = get_default_ou() attributes = {'email': self.email, 'ou': ou} for key in self.token: if key in app_settings.A2_PRE_REGISTRATION_FIELDS: attributes[key] = self.token[key] logger.debug(u'attributes %s', attributes) prefilling_list = utils.accumulate_from_backends( self.request, 'registration_form_prefill') logger.debug(u'prefilling_list %s', prefilling_list) # Build a single meaningful prefilling with sets of values prefilling = {} for p in prefilling_list: for name, values in p.items(): if name in self.fields: prefilling.setdefault(name, set()).update(values) logger.debug(u'prefilling %s', prefilling) for name, values in prefilling.items(): attributes[name] = ' '.join(values) logger.debug(u'attributes with prefilling %s', attributes) if self.token.get('user_id'): kwargs['instance'] = User.objects.get(id=self.token.get('user_id')) else: init_kwargs = {} for key in ('email', 'first_name', 'last_name', 'ou'): if key in attributes: init_kwargs[key] = attributes[key] kwargs['instance'] = get_user_model()(**init_kwargs) return kwargs
def dispatch(self, request, *args, **kwargs): if not getattr(settings, 'REGISTRATION_OPEN', True): raise Http404('Registration is not open.') self.token = {} self.ou = get_default_ou() # load pre-filled values if request.GET.get('token'): try: self.token = signing.loads( request.GET.get('token'), max_age=settings.ACCOUNT_ACTIVATION_DAYS * 3600 * 24) except (TypeError, ValueError, signing.BadSignature) as e: logger.warning(u'registration_view: invalid token: %s', e) return HttpResponseBadRequest('invalid token', content_type='text/plain') if 'ou' in self.token: self.ou = OrganizationalUnit.objects.get(pk=self.token['ou']) self.next_url = self.token.pop(REDIRECT_FIELD_NAME, utils.select_next_url(request, None)) return super(BaseRegistrationView, self).dispatch(request, *args, **kwargs)
def test_manager_user_change_email(app, superuser_or_admin, simple_user, mailoutbox): ou = get_default_ou() ou.validate_emails = True ou.save() NEW_EMAIL = '*****@*****.**' assert NEW_EMAIL != simple_user.email response = login( app, superuser_or_admin, reverse('a2-manager-user-by-uuid-detail', kwargs={'slug': unicode(simple_user.uuid)})) assert 'Change user email' in response.content # cannot click it's a submit button :/ response = app.get( reverse('a2-manager-user-by-uuid-change-email', kwargs={'slug': unicode(simple_user.uuid)})) assert response.form['new_email'].value == simple_user.email response.form.set('new_email', NEW_EMAIL) assert len(mailoutbox) == 0 response = response.form.submit().follow() assert 'A mail was sent to [email protected] to verify it.' in response.content assert 'Change user email' in response.content # cannot click it's a submit button :/ assert len(mailoutbox) == 1 assert simple_user.email in mailoutbox[0].body assert NEW_EMAIL in mailoutbox[0].body # logout app.session.flush() link = get_link_from_mail(mailoutbox[0]) response = app.get(link).maybe_follow() assert ( 'your request for changing your email for [email protected] is successful' in response.content) simple_user.refresh_from_db() assert simple_user.email == NEW_EMAIL
def dispatch(self, request, *args, **kwargs): self.token = request.token self.authentication_method = self.token.get('authentication_method', 'email') self.email = request.token['email'] if 'ou' in self.token: self.ou = OrganizationalUnit.objects.get(pk=self.token['ou']) else: self.ou = get_default_ou() self.users = User.objects.filter(email__iexact=self.email) \ .order_by('date_joined') if self.ou: self.users = self.users.filter(ou=self.ou) self.email_is_unique = app_settings.A2_EMAIL_IS_UNIQUE \ or app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE if self.ou: self.email_is_unique |= self.ou.email_is_unique self.init_fields_labels_and_help_texts() # if registration is done during an SSO add the service to the registration event self.service = self.token.get(constants.SERVICE_FIELD_NAME) return super(RegistrationCompletionView, self) \ .dispatch(request, *args, **kwargs)
def test_manager_user_change_email_no_change(app, superuser_or_admin, simple_user, mailoutbox): ou = get_default_ou() ou.validate_emails = True ou.save() NEW_EMAIL = '*****@*****.**' assert NEW_EMAIL != simple_user.email response = login( app, superuser_or_admin, reverse('a2-manager-user-by-uuid-detail', kwargs={'slug': unicode(simple_user.uuid)})) assert 'Change user email' in response.content # cannot click it's a submit button :/ response = app.get( reverse('a2-manager-user-by-uuid-change-email', kwargs={'slug': unicode(simple_user.uuid)})) assert response.form['new_email'].value == simple_user.email assert len(mailoutbox) == 0 response = response.form.submit().follow() assert 'A mail was sent to [email protected] to verify it.' not in response.content
def simple_oidc_client(db): return OIDCClient.objects.create( name='client', slug='client', ou=get_default_ou(), redirect_uris='https://example.com/')
def save(self, *args, **kwargs): if 'ou' not in self.fields: self.instance.ou = get_default_ou() return super(HideOUFieldMixin, self).save(*args, **kwargs)
def test_api_user(client): # create an user, an ou role, a service and a service role ou = get_default_ou() User = get_user_model() user = User.objects.create(ou=ou, username='******', first_name=u'Jôhn', last_name=u'Doe', email='*****@*****.**') user.set_password('password') user.save() Role = get_role_model() role1 = Role.objects.create(name='Role1', ou=ou) role1.members.add(user) service = Service.objects.create(name='Service1', slug='service1', ou=ou) role2 = Role.objects.create(name='Role2', service=service) role2.members.add(user) Role.objects.create(name='Role3', ou=ou) Role.objects.create(name='Role4', service=service) # test failure when unlogged response = client.get('/api/user/', HTTP_ORIGIN='http://testserver') assert response.content == '{}' # login client.login(username='******', password='******') response = client.get('/api/user/', HTTP_ORIGIN='http://testserver') data = json.loads(response.content) assert isinstance(data, dict) assert set(data.keys()) == set(['uuid', 'username', 'first_name', 'ou__slug', 'ou__uuid', 'ou__name', 'last_name', 'email', 'roles', 'services', 'is_superuser', 'ou']) assert data['uuid'] == user.uuid assert data['username'] == user.username assert data['first_name'] == user.first_name assert data['last_name'] == user.last_name assert data['email'] == user.email assert data['is_superuser'] == user.is_superuser assert data['ou'] == ou.name assert data['ou__name'] == ou.name assert data['ou__slug'] == ou.slug assert data['ou__uuid'] == ou.uuid assert isinstance(data['roles'], list) assert len(data['roles']) == 2 for role in data['roles']: assert set(role.keys()) == set(['uuid', 'name', 'slug', 'is_admin', 'is_service', 'ou__uuid', 'ou__name', 'ou__slug']) assert (role['uuid'] == role1.uuid and role['name'] == role1.name and role['slug'] == role1.slug and role['is_admin'] is False and role['is_service'] is False and role['ou__uuid'] == ou.uuid and role['ou__name'] == ou.name and role['ou__slug'] == ou.slug) or \ (role['uuid'] == role2.uuid and role['name'] == role2.name and role['slug'] == role2.slug and role['is_admin'] is False and role['is_service'] is True and role['ou__uuid'] == ou.uuid and role['ou__name'] == ou.name and role['ou__slug'] == ou.slug) assert isinstance(data['services'], list) assert len(data['services']) == 1 s = data['services'][0] assert set(s.keys()) == set(['name', 'slug', 'ou', 'ou__name', 'ou__slug', 'ou__uuid', 'roles']) assert s['name'] == service.name assert s['slug'] == service.slug assert s['ou'] == ou.name assert s['ou__name'] == ou.name assert s['ou__slug'] == ou.slug assert s['ou__uuid'] == ou.uuid assert isinstance(s['roles'], list) assert len(s['roles']) == 2 for role in s['roles']: assert set(role.keys()) == set(['uuid', 'name', 'slug', 'is_admin', 'is_service', 'ou__uuid', 'ou__name', 'ou__slug']) assert (role['uuid'] == role1.uuid and role['name'] == role1.name and role['slug'] == role1.slug and role['is_admin'] is False and role['is_service'] is False and role['ou__uuid'] == ou.uuid and role['ou__name'] == ou.name and role['ou__slug'] == ou.slug) or \ (role['uuid'] == role2.uuid and role['name'] == role2.name and role['slug'] == role2.slug and role['is_admin'] is False and role['is_service'] is True and role['ou__uuid'] == ou.uuid and role['ou__name'] == ou.name and role['ou__slug'] == ou.slug)
def simple_role(db): return Role.objects.create(name='simple role', slug='simple-role', ou=get_default_ou())
def test_user_listing_admin(user): response = login(app, user, '/manage/') # test user listing ou search response = response.click(href='users') assert len(response.form.fields['search-ou']) == 1 assert len(response.form.fields['search-text']) == 1 field = response.form['search-ou'] options = field.options assert len(options) == 4 for key, checked, label in options: assert not checked or key == 'all' assert 'all' in [o[0] for o in options] assert 'none' in [o[0] for o in options] # verify table shown q = response.pyquery.remove_namespaces() assert len(q('table tbody tr')) == 3 assert set([e.text for e in q('table tbody td.username') ]) == {'admin', 'superuser', 'admin.ou1'} # test user's role page response = app.get('/manage/users/%d/roles/' % admin.pk) assert len(response.form.fields['search-ou']) == 1 field = response.form['search-ou'] options = field.options assert len(options) == 4 for key, checked, label in options: assert not checked or key == str(get_default_ou().pk) q = response.pyquery.remove_namespaces() assert len(q('table tbody tr')) == 1 assert q('table tbody tr').text() == u'simple role' response.form.set('search-ou', 'all') response = response.form.submit() q = response.pyquery.remove_namespaces() assert len(q('table tbody tr')) == 1 assert q('table tbody tr').text() == 'None' form = response.forms['search-form'] form.set('search-internals', True) response = form.submit() q = response.pyquery.remove_namespaces() assert len(q('table tbody tr')) == 4 # admin enroled only in the Manager role, other roles are inherited assert len(q('table tbody tr td.via')) == 4 assert len(q('table tbody tr td.via:empty')) == 1 for elt in q('table tbody td.name a'): assert 'Manager' in elt.text form = response.forms['search-form'] form.set('search-ou', 'none') form.set('search-internals', True) response = form.submit() q = response.pyquery.remove_namespaces() assert len(q('table tbody tr')) == 6 for elt in q('table tbody td.name a'): assert 'Manager' in elt.text # test role listing response = app.get('/manage/roles/') assert len(response.form.fields['search-ou']) == 1 field = response.form['search-ou'] options = field.options assert len(options) == 4 for key, checked, label in options: if key == 'all': assert checked else: assert not checked q = response.pyquery.remove_namespaces() assert len(q('table tbody tr')) == 2 names = [elt.text for elt in q('table tbody td.name a')] assert set(names) == {u'simple role', u'role_ou1'} response.form.set('search-ou', 'all') response.form.set('search-internals', True) response = response.form.submit() q = response.pyquery.remove_namespaces() assert len(q('table tbody tr')) == 12 for elt in q('table tbody td.name a'): assert ('OU1' in elt.text or 'Default' in elt.text or 'Manager' in elt.text or elt.text == u'simple role' or elt.text == u'role_ou1') response.form.set('search-ou', 'none') response.form.set('search-internals', True) response = response.form.submit() q = response.pyquery.remove_namespaces() assert len(q('table tbody tr')) == 6 for elt in q('table tbody td.name a'): assert 'Manager' in elt.text
def simple_user(db, ou1): return create_user(username='******', first_name=u'Jôhn', last_name=u'Dôe', email='*****@*****.**', ou=get_default_ou())
def register_issuer(name, issuer=None, openid_configuration=None, verify=True, timeout=None, ou=None): from . import models if issuer and not openid_configuration: openid_configuration_url = get_openid_configuration_url(issuer) try: response = requests.get(openid_configuration_url, verify=verify, timeout=timeout) response.raise_for_status() except requests.RequestException as e: raise ValueError( _('Unable to reach the OpenID Connect configuration for %(issuer)s: ' '%(error)s') % { 'issuer': issuer, 'error': e, }) try: openid_configuration = openid_configuration or response.json() if not isinstance(openid_configuration, dict): raise ValueError(_('MUST be a dictionnary')) keys = set(openid_configuration.keys()) if not keys >= OPENID_CONFIGURATION_REQUIRED: raise ValueError( _('missing keys %s') % (OPENID_CONFIGURATION_REQUIRED - keys)) for key in [ 'issuer', 'authorization_endpoint', 'token_endpoint', 'jwks_uri', 'userinfo_endpoint' ]: if not check_https(openid_configuration[key]): raise ValueError( _('%(key)s is not an https:// URL; %(value)s') % { 'key': key, 'value': openid_configuration[key], }) except ValueError as e: raise ValueError( _('Invalid OpenID Connect configuration for %(issuer)s: ' '%(error)s') % (issuer, e)) if 'code' not in openid_configuration['response_types_supported']: raise ValueError( _('authorization code flow is unsupported: code response type is ' 'unsupported')) try: response = requests.get(openid_configuration['jwks_uri'], verify=verify, timeout=None) response.raise_for_status() except requests.RequestException as e: raise ValueError( _('Unable to reach the OpenID Connect JWKSet for %(issuer)s: ' '%(url)s %(error)s') % { 'issuer': issuer, 'url': openid_configuration['jwks_uri'], 'error': e, }) try: jwkset_json = response.json() except ValueError as e: raise ValueError(_('Invalid JSKSet document: %s') % e) try: old_pk = models.OIDCProvider.objects.get( issuer=openid_configuration['issuer']).pk except models.OIDCProvider.DoesNotExist: old_pk = None if (set(['RS256', 'RS384', 'RS512']) & set( openid_configuration['id_token_signing_alg_values_supported'])): idtoken_algo = models.OIDCProvider.ALGO_RSA elif (set(['HS256', 'HS384', 'HS512']) & set( openid_configuration['id_token_signing_alg_values_supported'])): idtoken_algo = models.OIDCProvider.HMAC else: raise ValueError( _('no common algorithm found for signing idtokens: %s') % openid_configuration['id_token_signing_alg_values_supported']) kwargs = dict( ou=ou or get_default_ou(), name=name, issuer=openid_configuration['issuer'], authorization_endpoint=openid_configuration['authorization_endpoint'], token_endpoint=openid_configuration['token_endpoint'], userinfo_endpoint=openid_configuration['userinfo_endpoint'], jwkset_json=jwkset_json, idtoken_algo=idtoken_algo, strategy=models.OIDCProvider.STRATEGY_CREATE) if old_pk: models.OIDCProvider.objects.filter(pk=old_pk).update(**kwargs) return models.OIDCProvider.objects.get(pk=old_pk) else: return models.OIDCProvider.objects.create(**kwargs)
def test_authorization_code_sso(login_first, oidc_settings, oidc_client, simple_user, app): redirect_uri = oidc_client.redirect_uris.split()[0] params = { 'client_id': oidc_client.client_id, 'scope': 'openid profile email', 'redirect_uri': redirect_uri, 'state': 'xxx', 'nonce': 'yyy', } if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE: params['response_type'] = 'code' elif oidc_client.authorization_flow == oidc_client.FLOW_IMPLICIT: params['response_type'] = 'token id_token' authorize_url = make_url('oidc-authorize', params=params) if login_first: utils.login(app, simple_user) response = app.get(authorize_url) if not login_first: response = response.follow() assert response.request.path == reverse('auth_login') response.form.set('username', simple_user.username) response.form.set('password', simple_user.username) response = response.form.submit(name='login-password-submit') response = response.follow() assert response.request.path == reverse('oidc-authorize') if oidc_client.authorization_mode != OIDCClient.AUTHORIZATION_MODE_NONE: assert 'a2-oidc-authorization-form' in response.content assert OIDCAuthorization.objects.count() == 0 assert OIDCCode.objects.count() == 0 assert OIDCAccessToken.objects.count() == 0 response = response.form.submit('accept') assert OIDCAuthorization.objects.count() == 1 authz = OIDCAuthorization.objects.get() assert authz.client == oidc_client assert authz.user == simple_user assert authz.scope_set() == set('openid profile email'.split()) assert authz.expired >= now() if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE: assert OIDCCode.objects.count() == 1 code = OIDCCode.objects.get() assert code.client == oidc_client assert code.user == simple_user assert code.scope_set() == set('openid profile email'.split()) assert code.state == 'xxx' assert code.nonce == 'yyy' assert code.redirect_uri == redirect_uri assert code.session_key == app.session.session_key assert code.auth_time <= now() assert code.expired >= now() assert response['Location'].startswith(redirect_uri) location = urlparse.urlparse(response['Location']) if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE: query = urlparse.parse_qs(location.query) assert set(query.keys()) == set(['code', 'state']) assert query['code'] == [code.uuid] code = query['code'][0] assert query['state'] == ['xxx'] token_url = make_url('oidc-token') response = app.post(token_url, params={ 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': oidc_client.redirect_uris.split()[0], }, headers=client_authentication_headers(oidc_client)) assert 'error' not in response.json assert 'access_token' in response.json assert 'expires_in' in response.json assert 'id_token' in response.json assert response.json['token_type'] == 'Bearer' access_token = response.json['access_token'] id_token = response.json['id_token'] elif oidc_client.authorization_flow == oidc_client.FLOW_IMPLICIT: assert location.fragment query = urlparse.parse_qs(location.fragment) assert OIDCAccessToken.objects.count() == 1 access_token = OIDCAccessToken.objects.get() assert set(query.keys()) == set(['access_token', 'token_type', 'expires_in', 'id_token', 'state']) assert query['access_token'] == [access_token.uuid] assert query['token_type'] == ['Bearer'] assert query['state'] == ['xxx'] access_token = query['access_token'][0] id_token = query['id_token'][0] if oidc_client.idtoken_algo == oidc_client.ALGO_RSA: key = JWKSet.from_json(app.get(reverse('oidc-certs')).content) elif oidc_client.idtoken_algo == oidc_client.ALGO_HMAC: key = JWK(kty='oct', k=base64.b64encode(oidc_client.client_secret.encode('utf-8'))) else: raise NotImplementedError jwt = JWT(jwt=id_token, key=key) claims = json.loads(jwt.claims) assert set(claims) >= set(['iss', 'sub', 'aud', 'exp', 'iat', 'nonce', 'auth_time', 'acr']) assert claims['nonce'] == 'yyy' assert response.request.url.startswith(claims['iss']) assert claims['aud'] == oidc_client.client_id assert parse_timestamp(claims['iat']) <= now() assert parse_timestamp(claims['auth_time']) <= now() exp_delta = (parse_timestamp(claims['exp']) - now()).total_seconds() assert exp_delta > 0 if oidc_client.idtoken_duration: assert abs(exp_delta - oidc_client.idtoken_duration.total_seconds()) < 2 else: assert abs(exp_delta - 30) < 2 if login_first: assert claims['acr'] == '0' else: assert claims['acr'] == '1' assert claims['sub'] == make_sub(oidc_client, simple_user) assert claims['preferred_username'] == simple_user.username assert claims['given_name'] == simple_user.first_name assert claims['family_name'] == simple_user.last_name assert claims['email'] == simple_user.email assert claims['email_verified'] is False user_info_url = make_url('oidc-user-info') response = app.get(user_info_url, headers=bearer_authentication_headers(access_token)) assert response.json['sub'] == make_sub(oidc_client, simple_user) assert response.json['preferred_username'] == simple_user.username assert response.json['given_name'] == simple_user.first_name assert response.json['family_name'] == simple_user.last_name assert response.json['email'] == simple_user.email assert response.json['email_verified'] is False # when adding extra attributes OIDCClaim.objects.create(client=oidc_client, name='ou', value='django_user_ou_name', scopes='profile') OIDCClaim.objects.create(client=oidc_client, name='roles', value='a2_role_names', scopes='profile, role') simple_user.roles.add(get_role_model().objects.create( name='Whatever', slug='whatever', ou=get_default_ou())) response = app.get(user_info_url, headers=bearer_authentication_headers(access_token)) assert response.json['ou'] == simple_user.ou.name assert response.json['roles'][0] == 'Whatever' # check against a user without username simple_user.username = None simple_user.save() response = app.get(user_info_url, headers=bearer_authentication_headers(access_token)) assert 'preferred_username' not in response.json # Now logout if oidc_client.post_logout_redirect_uris: params = { 'post_logout_redirect_uri': oidc_client.post_logout_redirect_uris, 'state': 'xyz', } logout_url = make_url('oidc-logout', params=params) response = app.get(logout_url) assert 'You have been logged out' in response.content assert 'https://example.com/?state=xyz' in response.content assert '_auth_user_id' not in app.session else: response = app.get(make_url('account_management')) response = response.click('Logout') if oidc_client.frontchannel_logout_uri: iframes = response.pyquery('iframe[src="https://example.com/southpark/logout/"]') assert iframes if oidc_client.frontchannel_timeout: assert iframes.attr('onload').endswith(', %d)' % oidc_client.frontchannel_timeout) else: assert iframes.attr('onload').endswith(', 10000)')
def test_role_control_access(login_first, oidc_settings, oidc_client, simple_user, app): # authorized_role role_authorized = get_role_model().objects.create( name='Goth Kids', slug='goth-kids', ou=get_default_ou()) oidc_client.add_authorized_role(role_authorized) redirect_uri = oidc_client.redirect_uris.split()[0] params = { 'client_id': oidc_client.client_id, 'scope': 'openid profile email', 'redirect_uri': redirect_uri, 'state': 'xxx', 'nonce': 'yyy', } if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE: params['response_type'] = 'code' elif oidc_client.authorization_flow == oidc_client.FLOW_IMPLICIT: params['response_type'] = 'token id_token' authorize_url = make_url('oidc-authorize', params=params) if login_first: utils.login(app, simple_user) # user not authorized response = app.get(authorize_url) assert 'https://example.com/southpark/' in response.content # user authorized simple_user.roles.add(role_authorized) simple_user.save() response = app.get(authorize_url) if not login_first: response = response.follow() response.form.set('username', simple_user.username) response.form.set('password', simple_user.username) response = response.form.submit(name='login-password-submit') response = response.follow() if oidc_client.authorization_mode != oidc_client.AUTHORIZATION_MODE_NONE: response = response.form.submit('accept') assert OIDCAuthorization.objects.get() if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE: code = OIDCCode.objects.get() location = urlparse.urlparse(response['Location']) if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE: query = urlparse.parse_qs(location.query) code = query['code'][0] token_url = make_url('oidc-token') response = app.post(token_url, params={ 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': oidc_client.redirect_uris.split()[0], }, headers=client_authentication_headers(oidc_client)) id_token = response.json['id_token'] elif oidc_client.authorization_flow == oidc_client.FLOW_IMPLICIT: query = urlparse.parse_qs(location.fragment) id_token = query['id_token'][0] if oidc_client.idtoken_algo == oidc_client.ALGO_RSA: key = JWKSet.from_json(app.get(reverse('oidc-certs')).content) elif oidc_client.idtoken_algo == oidc_client.ALGO_HMAC: key = JWK(kty='oct', k=base64.b64encode(oidc_client.client_secret.encode('utf-8'))) else: raise NotImplementedError jwt = JWT(jwt=id_token, key=key) claims = json.loads(jwt.claims) if login_first: assert claims['acr'] == '0' else: assert claims['acr'] == '1'
def test_sso(app, caplog, code, oidc_provider, oidc_provider_jwkset, login_url, login_callback_url, hooks): OU = get_ou_model() cassis = OU.objects.create(name='Cassis', slug='cassis') OU.cached.cache.clear() response = app.get('/admin/').maybe_follow() assert oidc_provider.name in response.content response = response.click(oidc_provider.name) location = urlparse.urlparse(response.location) endpoint = urlparse.urlparse(oidc_provider.authorization_endpoint) assert location.scheme == endpoint.scheme assert location.netloc == endpoint.netloc assert location.path == endpoint.path query = check_simple_qs(urlparse.parse_qs(location.query)) assert query['state'] in app.session['auth_oidc'] assert query['response_type'] == 'code' assert query['client_id'] == str(oidc_provider.client_id) assert query['scope'] == 'openid' assert query['redirect_uri'] == 'http://testserver' + reverse('oidc-login-callback') User = get_user_model() assert User.objects.count() == 0 with utils.check_log(caplog, 'invalid token endpoint response'): with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code): response = app.get(login_callback_url, params={'code': 'yyyy', 'state': query['state']}) with utils.check_log(caplog, 'invalid id_token %r'): with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, extra_id_token={'iss': None}): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) with utils.check_log(caplog, 'invalid id_token %r'): with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, extra_id_token={'sub': None}): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) with utils.check_log(caplog, 'authentication is too old'): with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, extra_id_token={'iat': 1}): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) with utils.check_log(caplog, 'id_token expired'): with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, extra_id_token={'exp': 1}): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) with utils.check_log(caplog, 'invalid id_token audience'): with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, extra_id_token={'aud': 'zz'}): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) assert not hooks.auth_oidc_backend_modify_user with utils.check_log(caplog, 'created user'): with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) assert len(hooks.auth_oidc_backend_modify_user) == 1 assert set(hooks.auth_oidc_backend_modify_user[0]['kwargs']) >= set(['user', 'provider', 'user_info', 'id_token', 'access_token']) assert urlparse.urlparse(response['Location']).path == '/admin/' assert User.objects.count() == 1 user = User.objects.get() assert user.ou == get_default_ou() assert user.username == 'john.doe' assert user.first_name == 'John' assert user.last_name == 'Doe' assert user.email == '*****@*****.**' assert user.attributes.first_name == 'John' assert user.attributes.last_name == 'Doe' assert AttributeValue.objects.filter(content='John', verified=True).count() == 1 assert AttributeValue.objects.filter(content='Doe', verified=False).count() == 1 with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, extra_user_info={'family_name_verified': True}): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) assert AttributeValue.objects.filter(content='Doe', verified=False).count() == 0 assert AttributeValue.objects.filter(content='Doe', verified=True).count() == 1 with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, extra_user_info={'ou': 'cassis'}): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) assert User.objects.count() == 1 user = User.objects.get() assert user.ou == cassis with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) assert User.objects.count() == 1 user = User.objects.get() assert user.ou == get_default_ou() last_modified = user.modified time.sleep(0.1) with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code): response = app.get(login_callback_url, params={'code': code, 'state': query['state']}) assert User.objects.count() == 1 user = User.objects.get() assert user.ou == get_default_ou() assert user.modified == last_modified response = app.get(reverse('account_management')) with utils.check_log(caplog, 'revoked token from OIDC'): with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code): response = response.click(href='logout') assert 'https://idp.example.com/logout' in response.content