def test_client_secret_post_authentication(oidc_settings, app, simple_oidc_client, simple_user):
    utils.login(app, simple_user)
    redirect_uri = simple_oidc_client.redirect_uris.split()[0]

    params = {
        'client_id': simple_oidc_client.client_id,
        'scope': 'openid profile email',
        'redirect_uri': redirect_uri,
        'state': 'xxx',
        'nonce': 'yyy',
        'response_type': 'code',
    }

    authorize_url = make_url('oidc-authorize', params=params)
    response = app.get(authorize_url)
    response = response.form.submit('accept')
    location = urlparse.urlparse(response['Location'])
    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': redirect_uri,
        'client_id': simple_oidc_client.client_id,
        'client_secret': simple_oidc_client.client_secret,
    })

    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'
Beispiel #2
0
    def assertEqualsURL(self, url1, url2, **kwargs):
        '''Check that url1 is equals to url2 augmented with parameters kwargs
           in its query string.

           The string '*' is a special value, when used it just check that the
           given parameter exist in the first url, it does not check the exact
           value.
        '''
        splitted1 = urlparse.urlsplit(url1)
        url2 = utils.make_url(url2, params=kwargs)
        splitted2 = urlparse.urlsplit(url2)
        for i, (elt1, elt2) in enumerate(zip(splitted1, splitted2)):
            if i == 3:
                elt1 = urlparse.parse_qs(elt1, True)
                elt2 = urlparse.parse_qs(elt2, True)
                for k, v in elt1.items():
                    elt1[k] = set(v)
                for k, v in elt2.items():
                    if v == ['*']:
                        elt2[k] = elt1.get(k, v)
                    else:
                        elt2[k] = set(v)
            self.assertTrue(
                elt1 == elt2,
                "URLs are not equal: %s != %s" % (splitted1, splitted2))
Beispiel #3
0
    def assertEqualsURL(self, url1, url2, **kwargs):
        '''Check that url1 is equals to url2 augmented with parameters kwargs
           in its query string.

           The string '*' is a special value, when used it just check that the
           given parameter exist in the first url, it does not check the exact
           value.
        '''
        splitted1 = urlparse.urlsplit(url1)
        url2 = utils.make_url(url2, params=kwargs)
        splitted2 = urlparse.urlsplit(url2)
        for i, (elt1, elt2) in enumerate(zip(splitted1, splitted2)):
            if i == 3:
                elt1 = urlparse.parse_qs(elt1, True)
                elt2 = urlparse.parse_qs(elt2, True)
                for k, v in elt1.items():
                    elt1[k] = set(v)
                for k, v in elt2.items():
                    if v == ['*']:
                        elt2[k] = elt1.get(k, v)
                    else:
                        elt2[k] = set(v)
            self.assertTrue(
                elt1 == elt2,
                "URLs are not equal: %s != %s" % (splitted1, splitted2))
Beispiel #4
0
def test_registration(app, db, settings, mailoutbox, external_redirect):
    next_url, good_next_url = external_redirect

    settings.LANGUAGE_CODE = 'en-us'
    settings.A2_VALIDATE_EMAIL_DOMAIN = can_resolve_dns()

    # disable existing attributes
    models.Attribute.objects.update(disabled=True)

    User = get_user_model()
    url = utils.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url})
    response = app.get(url)
    response.form.set('email', '*****@*****.**')
    response = response.form.submit()

    assert urlparse(response['Location']).path == reverse('registration_complete')

    response = response.follow()
    assert '2 days' in response.content
    assert '*****@*****.**' in response.content
    assert len(mailoutbox) == 1

    link = get_link_from_mail(mailoutbox[0])

    # test password validation
    response = app.get(link)
    response.form.set('password1', 'toto')
    response.form.set('password2', 'toto')
    response = response.form.submit()
    assert '8 characters' in response.content

    # set valid password
    response.form.set('password1', 'T0==toto')
    response.form.set('password2', 'T0==toto')
    response = response.form.submit()
    if good_next_url:
        assert 'You have just created an account.' in response.content
        assert next_url in response.content
    else:
        assert urlparse(response['Location']).path == '/'
        response = response.follow()
        assert 'You have just created an account.' in response.content
    assert User.objects.count() == 1
    assert len(mailoutbox) == 2
    assert 'was successful' in mailoutbox[1].body

    new_user = User.objects.get()
    assert new_user.email == '*****@*****.**'
    assert new_user.username is None
    assert new_user.check_password('T0==toto')
    assert new_user.is_active
    assert not new_user.is_staff
    assert not new_user.is_superuser
    assert str(app.session['_auth_user_id']) == str(new_user.pk)

    response = app.get('/login/')
    response.form.set('username', '*****@*****.**')
    response.form.set('password', 'T0==toto')
    response = response.form.submit(name='login-password-submit')
    assert urlparse(response['Location']).path == reverse('auth_homepage')
Beispiel #5
0
    def redirect_logout_list(self, request, next=None):
        from .models import OIDCProvider

        tokens = request.session.get('auth_oidc', {}).get('tokens', [])
        urls = []
        if tokens:
            for token in tokens:
                provider = OIDCProvider.objects.get(pk=token['provider_pk'])
                # ignore providers wihtout SLO
                if not provider.end_session_endpoint:
                    continue
                params = {}
                if 'id_token' in token['token_response']:
                    params['id_token_hint'] = token['token_response'][
                        'id_token']
                if 'access_token' in token[
                        'token_response'] and provider.token_revocation_endpoint:
                    self.revoke_token(provider,
                                      token['token_response']['access_token'])
                params[
                    'post_logout_redirect_uri'] = request.build_absolute_uri(
                        reverse('auth_logout'))
                urls.append(
                    make_url(provider.end_session_endpoint, params=params))
        return urls
Beispiel #6
0
    def get_success_url(self):
        try:
            redirect_url, next_field = app_settings.A2_REGISTRATION_REDIRECT
        except Exception:
            redirect_url = app_settings.A2_REGISTRATION_REDIRECT
            next_field = REDIRECT_FIELD_NAME

        if self.token and self.token.get(REDIRECT_FIELD_NAME):
            url = self.token[REDIRECT_FIELD_NAME]
            if redirect_url:
                url = make_url(redirect_url, params={next_field: url})
        else:
            if redirect_url:
                url = redirect_url
            else:
                url = make_url(self.success_url)
        return url
Beispiel #7
0
 def get(self, request):
     referrer = request.META['HTTP_REFERER']
     next_url = request.REQUEST.get('service') or make_url('auth_homepage')
     if referrer:
         model = Service.objects.for_service(referrer)
         if model:
             return logout_view(request, next_url=next_url,
                     check_referer=False, do_local=False)
     return redirect(request, next_url)
Beispiel #8
0
def test_registration_realm(app, db, settings, mailoutbox):
    settings.LANGUAGE_CODE = 'en-us'
    settings.A2_VALIDATE_EMAIL_DOMAIN = can_resolve_dns()
    settings.A2_REGISTRATION_REALM = 'realm'
    settings.A2_REDIRECT_WHITELIST = ['http://relying-party.org/']
    settings.A2_REQUIRED_FIELDS = ['username']

    # disable existing attributes
    models.Attribute.objects.update(disabled=True)

    User = get_user_model()
    next_url = 'http://relying-party.org/'
    url = utils.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url})

    response = app.get(url)
    response.form.set('email', '*****@*****.**')
    response = response.form.submit()

    assert urlparse(response['Location']).path == reverse('registration_complete')

    response = response.follow()
    assert '2 days' in response.content
    assert '*****@*****.**' in response.content
    assert '2 days' in response.content
    assert len(mailoutbox) == 1

    link = get_link_from_mail(mailoutbox[0])

    # register
    response = app.get(link)
    response.form.set('username', 'toto')
    response.form.set('password1', 'T0==toto')
    response.form.set('password2', 'T0==toto')
    response = response.form.submit()
    assert 'You have just created an account.' in response.content
    assert next_url in response.content
    assert len(mailoutbox) == 2
    assert 'was successful' in mailoutbox[1].body

    # verify user has expected attributes
    new_user = User.objects.get()
    assert new_user.username == 'toto@realm'
    assert new_user.email == '*****@*****.**'
    assert new_user.check_password('T0==toto')
    assert new_user.is_active
    assert not new_user.is_staff
    assert not new_user.is_superuser
    assert str(app.session['_auth_user_id']) == str(new_user.pk)

    # test login
    response = app.get('/login/')
    response.form.set('username', '*****@*****.**')
    response.form.set('password', 'T0==toto')
    response = response.form.submit(name='login-password-submit')
    assert urlparse(response['Location']).path == reverse('auth_homepage')
Beispiel #9
0
 def get(self, request):
     referrer = request.META['HTTP_REFERER']
     next_url = request.GET.get('service') or make_url('auth_homepage')
     if referrer:
         model = Service.objects.for_service(referrer)
         if model:
             return logout_view(request,
                                next_url=next_url,
                                check_referer=False,
                                do_local=False)
     return redirect(request, next_url)
Beispiel #10
0
 def authenticate(self, request, st):
     '''
        Redirect to an login page, pass a cookie to the login page to
        associate the login event with the service ticket, if renew was
        asked
     '''
     nonce = st.ticket_id
     next_url = make_url('a2-idp-cas-continue', params={
         SERVICE_PARAM: st.service_url, NONCE_FIELD_NAME: nonce})
     return login_require(request, next_url=next_url,
             params={NONCE_FIELD_NAME: nonce})
Beispiel #11
0
 def authenticate(self, request, st):
     '''
        Redirect to an login page, pass a cookie to the login page to
        associate the login event with the service ticket, if renew was
        asked
     '''
     nonce = st.ticket_id
     next_url = make_url('a2-idp-cas-continue',
                         params={
                             SERVICE_PARAM: st.service_url,
                             NONCE_FIELD_NAME: nonce
                         })
     return login_require(request,
                          next_url=next_url,
                          params={NONCE_FIELD_NAME: nonce})
Beispiel #12
0
def logout(request):
    post_logout_redirect_uri = request.GET.get('post_logout_redirect_uri')
    state = request.GET.get('state')
    if post_logout_redirect_uri:
        providers = models.OIDCClient.objects.filter(
            post_logout_redirect_uris__contains=post_logout_redirect_uri)
        for provider in providers:
            if post_logout_redirect_uri in provider.post_logout_redirect_uris.split():
                break
        else:
            messages.warning(request, _('Invalid post logout URI'))
            return redirect(request, settings.LOGIN_REDIRECT_URL)
        if state:
            post_logout_redirect_uri = make_url(post_logout_redirect_uri, params={'state': state})
    # FIXME: do something with id_token_hint
    id_token_hint = request.GET.get('id_token_hint')
    return a2_logout(request, next_url=post_logout_redirect_uri, do_local=False,
                     check_referer=False)
def test_registration_service_slug(oidc_settings, app, simple_oidc_client, simple_user, hooks,
                                   mailoutbox):
    redirect_uri = simple_oidc_client.redirect_uris.split()[0]

    params = {
        'client_id': simple_oidc_client.client_id,
        'scope': 'openid profile email',
        'redirect_uri': redirect_uri,
        'state': 'xxx',
        'nonce': 'yyy',
        'response_type': 'code',
    }

    authorize_url = make_url('oidc-authorize', params=params)
    response = app.get(authorize_url)

    location = urlparse.urlparse(response['Location'])
    query = urlparse.parse_qs(location.query)
    assert query['service'] == ['client']
    response = response.follow().click('Register')
    location = urlparse.urlparse(response.request.url)
    query = urlparse.parse_qs(location.query)
    assert query['service'] == ['client']

    response.form.set('email', '*****@*****.**')
    response = response.form.submit()
    assert len(mailoutbox) == 1
    link = utils.get_link_from_mail(mailoutbox[0])
    response = app.get(link)
    response.form.set('first_name', 'John')
    response.form.set('last_name', 'Doe')
    response.form.set('password1', 'T0==toto')
    response.form.set('password2', 'T0==toto')
    response = response.form.submit()
    assert hooks.event[0]['kwargs']['name'] == 'sso-request'
    assert hooks.event[0]['kwargs']['service'].slug == 'client'

    assert hooks.event[1]['kwargs']['name'] == 'registration'
    assert hooks.event[1]['kwargs']['service'] == 'client'

    assert hooks.event[2]['kwargs']['name'] == 'login'
    assert hooks.event[2]['kwargs']['how'] == 'email'
    assert hooks.event[2]['kwargs']['service'] == 'client'
Beispiel #14
0
 def test_make_url(self):
     from authentic2.utils import make_url
     self.assertEqualsURL(make_url('../coin'), '../coin')
     self.assertEqualsURL(make_url('../boob', params={'next': '..'}),
                          '../boob?next=..')
     self.assertEqualsURL(make_url('../boob', params={'next': '..'},
                                   append={'xx': 'yy'}),
                          '../boob?xx=yy&next=..')
     self.assertEqualsURL(make_url('../boob', params={'next': '..'},
                                   append={'next': 'yy'}),
                          '../boob?next=..&next=yy')
     self.assertEqualsURL(make_url('auth_login', params={'next': '/zob'}),
                          '/login/?next=%2Fzob')
     self.assertEqualsURL(make_url('auth_login', params={'next': '/zob'},
                                   fragment='a2-panel'),
                          '/login/?next=%2Fzob#a2-panel')
Beispiel #15
0
def test_registration_redirect_tuple(app, db, settings, mailoutbox, external_redirect):
    next_url, good_next_url = external_redirect

    settings.A2_REGISTRATION_REDIRECT = 'http://cms/welcome/', 'target'
    settings.A2_VALIDATE_EMAIL_DOMAIN = can_resolve_dns()

    new_next_url = settings.A2_REGISTRATION_REDIRECT[0]
    if good_next_url:
        new_next_url += '?target=' + urlquote(next_url)

    # disable existing attributes
    models.Attribute.objects.update(disabled=True)

    url = utils.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url})
    response = app.get(url)
    response.form.set('email', '*****@*****.**')
    response = response.form.submit()
    response = response.follow()
    link = get_link_from_mail(mailoutbox[0])
    response = app.get(link)
    response.form.set('password1', 'T0==toto')
    response.form.set('password2', 'T0==toto')
    response = response.form.submit()
    assert new_next_url in response.content
 def do_test_sso(self,
                 make_authn_request_kwargs={},
                 check_federation=True,
                 cancel=False,
                 default_name_id_format='persistent',
                 authorized_service=True):
     self.setup(default_name_id_format=default_name_id_format)
     client = Client()
     # Launch an AuthnRequest
     url, body, request_id = self.make_authn_request(
         **make_authn_request_kwargs)
     response = client.get(url)
     self.assertRedirectsComplex(
         response, reverse('auth_login'), **{
             'nonce':
             '*',
             SERVICE_FIELD_NAME:
             self.slug,
             REDIRECT_FIELD_NAME:
             make_url('a2-idp-saml-continue',
                      params={
                          NONCE_FIELD_NAME: request_id,
                      }),
         })
     nonce = urlparse.parse_qs(
         urlparse.urlparse(response['Location']).query)['nonce'][0]
     url = response['Location']
     response = client.get(url)
     self.assertEqual(response.status_code, 200)
     self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
     self.assertInHTML(
         u'<button class="cancel-button" name="cancel">%s</button>' %
         _('Cancel'),
         response.content,
         count=1)
     if cancel:
         response = client.post(url, {
             'cancel': 1,
         })
         self.assertRedirectsComplex(response,
                                     reverse('a2-idp-saml-continue'),
                                     cancel='*',
                                     nonce=nonce)
         response = client.get(response['Location'])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response['Content-type'].split(';')[0],
                          'text/html')
         doc = parse(StringIO.StringIO(response.content)).getroot()
         self.assertEqual(len(doc.forms),
                          1,
                          msg='the number of forms is not 1')
         self.assertEqual(doc.forms[0].get('action'),
                          '%s/sso/POST' % self.base_url)
         self.assertIn('SAMLResponse', doc.forms[0].fields)
         saml_response = doc.forms[0].fields['SAMLResponse']
         try:
             base64.b64decode(saml_response)
         except TypeError:
             self.fail('SAMLResponse is not base64 encoded: %s' %
                       saml_response)
         with self.assertRaises(lasso.ProfileRequestDeniedError):
             assertion = self.parse_authn_response(saml_response)
     elif not authorized_service:
         self.liberty_provider.add_authorized_role(self.role_authorized)
         # User without the authorized role
         role_goth = Role.objects.create(name='Goth Kids', slug='goth-kids')
         self.user.roles.all().delete()
         self.user.roles.add(role_goth)
         response = client.post(
             url, {
                 'username': self.email,
                 'password': self.password,
                 'login-password-submit': 1
             })
         response = client.get(response['Location'])
         assert 'https://whatever.com/loser/' in response.content
         # User with the authorized role
         self.user.roles.add(self.role_authorized)
         response = client.post(
             url, {
                 'username': self.email,
                 'password': self.password,
                 'login-password-submit': 1
             })
         response = client.get(response['Location'])
         assert 'SAMLResponse' in response.content
     else:
         response = client.post(
             url, {
                 'username': self.email,
                 'password': self.password,
                 'login-password-submit': 1,
             })
         self.assertRedirectsComplex(response,
                                     reverse('a2-idp-saml-continue'),
                                     nonce=nonce)
         response = client.get(response['Location'])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response['Content-type'].split(';')[0],
                          'text/html')
         doc = parse(StringIO.StringIO(response.content)).getroot()
         self.assertEqual(len(doc.forms),
                          1,
                          msg='the number of forms is not 1')
         self.assertEqual(doc.forms[0].get('action'),
                          '%s/sso/POST' % self.base_url)
         self.assertIn('SAMLResponse', doc.forms[0].fields)
         saml_response = doc.forms[0].fields['SAMLResponse']
         try:
             base64.b64decode(saml_response)
         except TypeError:
             self.fail('SAMLResponse is not base64 encoded: %s' %
                       saml_response)
         login = self.parse_authn_response(saml_response)
         assertion = login.assertion
         session_not_on_or_after = login.assertion.authnStatement[
             0].sessionNotOnOrAfter
         assert session_not_on_or_after is not None
         assert (datetime.datetime.strptime(session_not_on_or_after,
                                            '%Y-%m-%dT%H:%M:%SZ') >
                 datetime.datetime.utcnow())
         assertion_xml = assertion.exportToXml()
         namespaces = {
             'saml': lasso.SAML2_ASSERTION_HREF,
         }
         constraints = ()
         # check nameid
         if check_federation:
             format = make_authn_request_kwargs.get('format')
             if not format:
                 if self.default_sp_options_idp_policy.default_name_id_format == 'username':
                     self.assertEqual(
                         login.assertion.subject.nameID.format,
                         lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED)
                     self.assertEqual(
                         login.assertion.subject.nameID.content,
                         self.user.username.encode('utf-8'))
                 elif self.default_sp_options_idp_policy.default_name_id_format == 'uuid':
                     self.assertEqual(
                         login.assertion.subject.nameID.format,
                         lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED)
                     self.assertEqual(
                         login.assertion.subject.nameID.content,
                         self.user.uuid.encode('utf-8'))
             elif format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT:
                 federation = saml_models.LibertyFederation.objects.get()
                 constraints += (
                     ('/saml:Assertion/saml:Subject/saml:NameID',
                      federation.name_id_content),
                     ('/saml:Assertion/saml:Subject/saml:NameID/@Format',
                      lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT),
                     ('/saml:Assertion/saml:Subject/saml:NameID/@SPNameQualifier',
                      '%s/' % self.base_url),
                 )
             elif (format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL
                   or (not format and default_name_id_format == 'email')):
                 constraints += (
                     ('/saml:Assertion/saml:Subject/saml:NameID',
                      self.email),
                     ('/saml:Assertion/saml:Subject/saml:NameID/@Format',
                      lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL),
                 )
         constraints += (
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/"
              "@NameFormat", lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/"
              "@FriendlyName", 'First name'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/"
              "saml:AttributeValue", 'John'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/"
              "@NameFormat", lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/"
              "@FriendlyName", 'Last name'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/"
              "saml:AttributeValue", 'Doe'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/"
              "@NameFormat", lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/"
              "@FriendlyName", 'Superuser status'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/"
              "saml:AttributeValue", 'true'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='code_code']/"
              "@NameFormat", lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='code_code']/"
              "@FriendlyName", 'code'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='code_code']/"
              "saml:AttributeValue", '1234'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='mobile']/"
              "@NameFormat", lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='mobile']/"
              "@FriendlyName", 'mobile'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='mobile']/"
              "saml:AttributeValue", '5678'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='verified_attributes']/"
              "@NameFormat", lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='verified_attributes']/"
              "@FriendlyName", 'Verified attributes'),
             ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='verified_attributes']/"
              "saml:AttributeValue", set(['code_code', 'mobile'])),
         )
         self.assertXPathConstraints(assertion_xml, constraints, namespaces)
def test_invalid_request(caplog, oidc_settings, oidc_client, simple_user, app):
    redirect_uri = oidc_client.redirect_uris.split()[0]
    if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE:
        fragment = False
        response_type = 'code'
    elif oidc_client.authorization_flow == oidc_client.FLOW_IMPLICIT:
        fragment = True
        response_type = 'id_token token'
    else:
        raise NotImplementedError

    # client_id
    authorize_url = make_url('oidc-authorize', params={})

    response = app.get(authorize_url, status=400)
    assert 'missing parameter \'client_id\'' in response.content

    # redirect_uri
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
    })

    response = app.get(authorize_url, status=400)
    assert 'missing parameter \'redirect_uri\'' in response.content

    # invalid client_id
    authorize_url = make_url('oidc-authorize', params={
        'client_id': 'xxx',
        'redirect_uri': redirect_uri,
    })

    response = app.get(authorize_url, status=400)
    assert 'unknown client_id' in response.content

    # missing response_type
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
    })

    response = app.get(authorize_url)
    assert_oidc_error(response, 'invalid_request', 'missing parameter \'response_type\'',
                      fragment=fragment)
    logrecord = [rec for rec in caplog.records if rec.funcName == 'authorization_error'][0]
    assert logrecord.levelname == 'WARNING'
    assert logrecord.redirect_uri == 'https://example.com/callback'
    assert 'missing parameter \'response_type\'' in logrecord.message

    # missing scope
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': 'code',
    })

    response = app.get(authorize_url)
    assert_oidc_error(response, 'invalid_request', 'missing parameter \'scope\'', fragment=fragment)

    # invalid max_age
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': 'code',
        'scope': 'openid',
        'max_age': 'xxx',
    })
    response = app.get(authorize_url)
    assert_oidc_error(response, 'invalid_request', 'max_age is not', fragment=fragment)
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': 'code',
        'scope': 'openid',
        'max_age': '-1',
    })
    response = app.get(authorize_url)
    assert_oidc_error(response, 'invalid_request', 'max_age is not', fragment=fragment)

    # invalid redirect_uri
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': 'xxx',
        'response_type': 'code',
        'scope': 'openid',
    })

    response = app.get(authorize_url)
    assert_oidc_error(response, 'invalid_request', 'unauthorized redirect_uri', fragment=fragment)

    # unsupported response_type
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': 'xxx',
        'scope': 'openid',
    })

    response = app.get(authorize_url)
    if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE:
        assert_oidc_error(response, 'unsupported_response_type', 'only code is supported')
    elif oidc_client.authorization_flow == oidc_client.FLOW_IMPLICIT:
        assert_oidc_error(response, 'unsupported_response_type',
                          'only "id_token token" or "id_token" are supported', fragment=fragment)

    # openid scope is missing
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'profile',
    })

    response = app.get(authorize_url)
    assert_oidc_error(response, 'invalid_request', 'openid scope is missing', fragment=fragment)

    # use of an unknown scope
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email profile zob',
    })

    response = app.get(authorize_url)
    assert_oidc_error(response, 'invalid_scope', fragment=fragment)

    # restriction on scopes
    oidc_settings.A2_IDP_OIDC_SCOPES = ['openid']
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email',
    })

    response = app.get(authorize_url)
    assert_oidc_error(response, 'invalid_scope', fragment=fragment)
    del oidc_settings.A2_IDP_OIDC_SCOPES

    # cancel
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email profile',
        'cancel': '1',
    })

    response = app.get(authorize_url)
    assert_oidc_error(response, 'access_denied', error_description='user did not authenticate',
                      fragment=fragment)

    # prompt=none
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email profile',
        'prompt': 'none',
    })

    response = app.get(authorize_url)
    assert_oidc_error(response, 'login_required', error_description='prompt is none',
                      fragment=fragment)

    utils.login(app, simple_user)

    # prompt=none max_age=0
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email profile',
        'max_age': '0',
        'prompt': 'none',
    })

    response = app.get(authorize_url)
    assert_oidc_error(response, 'login_required', error_description='prompt is none',
                      fragment=fragment)

    # max_age=0
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email profile',
        'max_age': '0',
    })
    response = app.get(authorize_url)
    assert urlparse.urlparse(response['Location']).path == reverse('auth_login')

    # prompt=login
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email profile',
        'prompt': 'login',
    })
    response = app.get(authorize_url)
    assert urlparse.urlparse(response['Location']).path == reverse('auth_login')

    # user refuse authorization
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email profile',
        'prompt': 'none',
    })
    response = app.get(authorize_url)
    if oidc_client.authorization_mode != oidc_client.AUTHORIZATION_MODE_NONE:
        assert_oidc_error(response, 'consent_required', error_description='prompt is none',
                          fragment=fragment)

    # user refuse authorization
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email profile',
    })
    response = app.get(authorize_url)
    if oidc_client.authorization_mode != oidc_client.AUTHORIZATION_MODE_NONE:
        response = response.form.submit('refuse')
        assert_oidc_error(response, 'access_denied', error_description='user denied access',
                          fragment=fragment)

    # authorization exists
    authorize = OIDCAuthorization.objects.create(
        client=oidc_client, user=simple_user, scopes='openid profile email',
        expired=now() + datetime.timedelta(days=2))
    response = app.get(authorize_url)
    if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE:
        assert_authorization_response(response, code=None, fragment=fragment)
    elif oidc_client.authorization_flow == oidc_client.FLOW_IMPLICIT:
        assert_authorization_response(response, access_token=None, id_token=None, expires_in=None,
                                      token_type=None, fragment=fragment)

    # client ask for explicit authorization
    authorize_url = make_url('oidc-authorize', params={
        'client_id': oidc_client.client_id,
        'redirect_uri': redirect_uri,
        'response_type': response_type,
        'scope': 'openid email profile',
        'prompt': 'consent',
    })
    response = app.get(authorize_url)
    assert 'a2-oidc-authorization-form' in response.content
    # check all authorization have been deleted, it's our policy
    assert OIDCAuthorization.objects.count() == 0
    if oidc_client.authorization_mode == oidc_client.AUTHORIZATION_MODE_NONE:
        # authorization mode is none, but explicit consent is asked, we validate it
        response = response.form.submit('accept')

    # authorization has expired
    OIDCCode.objects.all().delete()
    authorize.expired = now() - datetime.timedelta(days=2)
    authorize.save()
    response = app.get(authorize_url)
    assert 'a2-oidc-authorization-form' in response.content
    authorize.expired = now() + datetime.timedelta(days=2)
    authorize.scopes = 'openid profile'
    authorize.save()
    assert OIDCAuthorization.objects.count() == 1
    response = response.form.submit('accept')
    assert OIDCAuthorization.objects.count() == 1
    # old authorizations have been deleted
    assert OIDCAuthorization.objects.get().pk != authorize.pk

    # check expired codes
    if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE:
        assert OIDCCode.objects.count() == 1
        code = OIDCCode.objects.get()
        assert code.is_valid()
        # make code expire
        code.expired = now() - datetime.timedelta(seconds=120)
        assert not code.is_valid()
        code.save()
        location = urlparse.urlparse(response['Location'])
        query = urlparse.parse_qs(location.query)
        assert set(query.keys()) == set(['code'])
        assert query['code'] == [code.uuid]
        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), status=400)
        assert 'error' in response.json
        assert response.json['error'] == 'invalid_request'
        assert response.json['desc'] == 'code has expired or user is disconnected'

    # invalid logout
    logout_url = make_url('oidc-logout', params={
        'post_logout_redirect_uri': 'https://whatever.com/',
    })
    response = app.get(logout_url)
    assert '_auth_user_id' in app.session
    assert 'Location' in response.headers

    # check code expiration after logout
    if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE:
        code = OIDCCode.objects.get()
        code.expired = now() + datetime.timedelta(seconds=120)
        code.save()
        assert code.is_valid()
        utils.logout(app)
        code = OIDCCode.objects.get()
        assert not code.is_valid()
        response = app.post(token_url, params={
            'grant_type': 'authorization_code',
            'code': code.uuid,
            'redirect_uri': oidc_client.redirect_uris.split()[0],
        }, headers=client_authentication_headers(oidc_client), status=400)
        assert 'error' in response.json
        assert response.json['error'] == 'invalid_request'
        assert response.json['desc'] == 'code has expired or user is disconnected'
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_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)')
Beispiel #20
0
    def test_email_is_unique_double_registration(self):
        from django.contrib.auth import get_user_model
        from rest_framework import test
        from rest_framework import status

        # disable existing attributes
        models.Attribute.objects.update(disabled=True)

        user = self.reguser3
        cred = self.reguser3_cred
        User = get_user_model()
        user_count = User.objects.count()
        client = test.APIClient()
        password = '******'
        username = '******'
        email = '*****@*****.**'
        return_url = 'http://sp.org/register/'
        payload = {
            'email': email,
            'username': username,
            'ou': self.ou.slug,
            'password': password,
            'return_url': return_url,
        }
        outbox_level = len(mail.outbox)
        client.credentials(HTTP_AUTHORIZATION='Basic %s' % cred)
        response = client.post(reverse('a2-api-register'),
                               content_type='application/json',
                               data=json.dumps(payload))
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertIn('result', response.data)
        self.assertEqual(response.data['result'], 1)
        self.assertIn('token', response.data)
        token = response.data['token']
        self.assertEqual(len(mail.outbox), outbox_level + 1)
        outbox_level = len(mail.outbox)

        # Second registration
        response2 = client.post(reverse('a2-api-register'),
                                content_type='application/json',
                                data=json.dumps(payload))
        self.assertEqual(response2.status_code, status.HTTP_202_ACCEPTED)
        self.assertIn('result', response2.data)
        self.assertEqual(response2.data['result'], 1)
        self.assertIn('token', response2.data)
        token2 = response2.data['token']
        self.assertEqual(len(mail.outbox), outbox_level + 1)

        activation_mail1, activation_mail2 = mail.outbox

        # User side - user click on first email
        client = Client()
        activation_url = get_link_from_mail(activation_mail1)
        response = client.get(activation_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        assert utils.make_url(return_url, params={'token': token}) in response.content
        self.assertEqual(User.objects.count(), user_count + 1)
        response = client.get(reverse('auth_homepage'))
        self.assertContains(response, username)
        last_user = User.objects.order_by('id').last()
        self.assertEqual(last_user.username, username)
        self.assertEqual(last_user.email, email)
        self.assertEqual(last_user.ou.slug, self.ou.slug)
        self.assertTrue(last_user.check_password(password))

        # User click on second email
        client = Client()
        activation_url = get_link_from_mail(activation_mail2)
        response = client.get(activation_url)
        self.assertEqual(response.status_code, status.HTTP_302_FOUND)
        self.assertEqual(response['Location'],
                         utils.make_url(return_url, params={'token': token2}))
        self.assertEqual(User.objects.count(), user_count + 1)
        response = client.get(reverse('auth_homepage'))
        self.assertContains(response, username)
        last_user2 = User.objects.order_by('id').last()
        self.assertEqual(User.objects.filter(email=payload['email']).count(), 1)
        self.assertEqual(last_user.id, last_user2.id)
        self.assertEqual(last_user2.username, username)
        self.assertEqual(last_user2.email, email)
        self.assertEqual(last_user2.ou.slug, self.ou.slug)
        self.assertTrue(last_user2.check_password(password))

        # Test email is unique with case change
        client = test.APIClient()
        client.credentials(HTTP_AUTHORIZATION='Basic %s' % cred)
        payload = {
            'email': email.upper(),
            'username': username + '1',
            'ou': self.ou.slug,
            'password': password,
            'return_url': return_url,
        }
        response = client.post(reverse('a2-api-register'),
                               content_type='application/json',
                               data=json.dumps(payload))
        self.assertEqual(response.data['errors']['__all__'],
                         [_('You already have an account')])
        # Username is required
        payload = {
            'email': '1' + email,
            'ou': self.ou.slug,
            'password': password,
            'return_url': return_url,
        }
        response = client.post(reverse('a2-api-register'),
                               content_type='application/json',
                               data=json.dumps(payload))
        self.assertEqual(response.data['errors']['__all__'],
                         [_('Username is required in this ou')])
        # Test username is unique
        payload = {
            'email': '1' + email,
            'username': username,
            'ou': self.ou.slug,
            'password': password,
            'return_url': return_url,
        }
        response = client.post(reverse('a2-api-register'),
                               content_type='application/json',
                               data=json.dumps(payload))
        self.assertEqual(response.data['errors']['__all__'],
                         [_('You already have an account')])
Beispiel #21
0
    def do_test_sso(self, make_authn_request_kwargs={}, check_federation=True,
                    cancel=False, default_name_id_format='persistent'):
        self.setup(default_name_id_format=default_name_id_format)
        client = Client()
        # Launch an AuthnRequest
        url, body, request_id = self.make_authn_request(
            **make_authn_request_kwargs)
        response = client.get(url)
        self.assertRedirectsComplex(response, reverse('auth_login'), **{
            'nonce': '*',
            REDIRECT_FIELD_NAME: make_url('a2-idp-saml-continue',
                                          params={
                                              NONCE_FIELD_NAME: request_id
                                          }
                                         ),
        })
        nonce = urlparse.parse_qs(
            urlparse.urlparse(
                response['Location']).query)['nonce'][0]
        url = response['Location']
        response = client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
        self.assertInHTML(u'<input type="submit" name="cancel" '
                          'value="%s"/>' % _('Cancel'), response.content,
                          count=1)
        if cancel:
            response = client.post(url, {
                    'cancel': 1,
            })
            self.assertRedirectsComplex(response,
                                        reverse('a2-idp-saml-continue'),
                                        cancel='*', nonce=nonce)
            response = client.get(response['Location'])
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response['Content-type'].split(';')[0],
                             'text/html')
            doc = parse(StringIO.StringIO(response.content)).getroot()
            self.assertEqual(len(doc.forms), 1,
                             msg='the number of forms is not 1')
            self.assertEqual(doc.forms[0].get('action'),
                             '%s/sso/POST' % self.base_url)
            self.assertIn('SAMLResponse', doc.forms[0].fields)
            saml_response = doc.forms[0].fields['SAMLResponse']
            try:
                saml_response_decoded = base64.b64decode(saml_response)
            except TypeError:
                self.fail('SAMLResponse is not base64 encoded: %s'
                          % saml_response)
            with self.assertRaises(lasso.ProfileRequestDeniedError):
                assertion = self.parse_authn_response(saml_response)
            namespaces = {'samlp': lasso.SAML2_PROTOCOL_HREF}
            constraints = (
                ("/samlp:Response/samlp:Status/samlp:StatusCode/@Value", lasso.SAML2_STATUS_CODE_RESPONDER),   
                ("/samlp:Response/samlp:Status/samlp:StatusCode/samlp:StatusCode/@Value", lasso.SAML2_STATUS_CODE_REQUEST_DENIED),
                ("/samlp:Response/samlp:Status/samlp:StatusMessage", 'User canceled login process')
            )
            self.assertXPathConstraints(saml_response_decoded, constraints, namespaces)
        else:
            response = client.post(url, {
                'username': self.email,
                'password': self.password,
                'login-password-submit': 1,
            })
            self.assertRedirectsComplex(
                response, reverse('a2-idp-saml-continue'), nonce=nonce)
            response = client.get(response['Location'])
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response['Content-type'].split(';')[0], 'text/html')
            doc = parse(StringIO.StringIO(response.content)).getroot()
            self.assertEqual(len(doc.forms), 1, msg='the number of forms is not 1')
            self.assertEqual(
                doc.forms[0].get('action'), '%s/sso/POST' % self.base_url)
            self.assertIn('SAMLResponse', doc.forms[0].fields)
            saml_response = doc.forms[0].fields['SAMLResponse']
            try:
                base64.b64decode(saml_response)
            except TypeError:
                self.fail('SAMLResponse is not base64 encoded: %s' % saml_response)
            
            login = self.parse_authn_response(saml_response)
            assertion = login.assertion
            assertion_xml = assertion.exportToXml()
            namespaces = {
                'saml': lasso.SAML2_ASSERTION_HREF,
            }
            constraints = ()
            # check nameid
            if check_federation:
                format = make_authn_request_kwargs.get('format')
                if not format:
                    if self.default_sp_options_idp_policy.default_name_id_format == 'username':
                        self.assertEqual(login.assertion.subject.nameID.format,
                                         lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED)
                        self.assertEqual(login.assertion.subject.nameID.content,
                                         self.user.username.encode('utf-8'))
                    elif self.default_sp_options_idp_policy.default_name_id_format == 'uuid':
                        self.assertEqual(login.assertion.subject.nameID.format,
                                         lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED)
                        self.assertEqual(login.assertion.subject.nameID.content,
                                         self.user.uuid.encode('utf-8'))
                elif format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT:
                    federation = saml_models.LibertyFederation.objects.get()
                    constraints += (
                        ('/saml:Assertion/saml:Subject/saml:NameID',
                            federation.name_id_content),
                        ('/saml:Assertion/saml:Subject/saml:NameID/@Format',
                            lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT),
                        ('/saml:Assertion/saml:Subject/saml:NameID/@SPNameQualifier',
                            '%s/' % self.base_url),

                    )
                elif format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL or \
                   (not format and default_name_id_format == 'email'):
                    constraints += (
                        ('/saml:Assertion/saml:Subject/saml:NameID',
                            self.email),
                        ('/saml:Assertion/saml:Subject/saml:NameID/@Format',
                            lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL),
                    )
            constraints += (
                ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/"
                    "@NameFormat", lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC),
                ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/"
                    "@FriendlyName", 'First name'),
                ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/"
                    "saml:AttributeValue", 'John'),

                ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/"
                    "@NameFormat", lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC),
                ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/"
                    "@FriendlyName", 'Last name'),
                ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/"
                    "saml:AttributeValue", 'Doe'),

                ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/"
                    "@NameFormat", lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC),
                ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/"
                    "@FriendlyName", 'Superuser status'),
                ("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/"
                    "saml:AttributeValue", 'true'),
            )
            self.assertXPathConstraints(assertion_xml, constraints, namespaces)
Beispiel #22
0
    def test_email_username_is_unique_double_registration(self):
        from django.contrib.auth import get_user_model
        from rest_framework import test
        from rest_framework import status

        # disable existing attributes
        models.Attribute.objects.update(disabled=True)

        cred = self.reguser3_cred
        User = get_user_model()
        user_count = User.objects.count()
        client = test.APIClient()
        password = '******'
        username = '******'
        email = '*****@*****.**'
        return_url = 'http://sp.org/register/'
        payload = {
            'email': email,
            'username': username,
            'ou': self.ou.slug,
            'password': password,
            'return_url': return_url,
        }
        outbox_level = len(mail.outbox)
        client.credentials(HTTP_AUTHORIZATION='Basic %s' % cred)
        response = client.post(reverse('a2-api-register'),
                               content_type='application/json',
                               data=json.dumps(payload))
        self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
        self.assertIn('result', response.data)
        self.assertEqual(response.data['result'], 1)
        self.assertIn('token', response.data)
        token = response.data['token']
        self.assertEqual(len(mail.outbox), outbox_level + 1)
        outbox_level = len(mail.outbox)

        # Second registration
        payload['email'] = '*****@*****.**'
        response2 = client.post(reverse('a2-api-register'),
                                content_type='application/json',
                                data=json.dumps(payload))
        self.assertEqual(response2.status_code, status.HTTP_202_ACCEPTED)
        self.assertIn('result', response2.data)
        self.assertEqual(response2.data['result'], 1)
        self.assertIn('token', response2.data)
        self.assertEqual(len(mail.outbox), outbox_level + 1)

        activation_mail1, activation_mail2 = mail.outbox

        # User side - user click on first email
        client = Client()
        activation_url = get_link_from_mail(activation_mail1)
        response = client.get(activation_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        assert utils.make_url(return_url, params={'token': token}) in response.content
        self.assertEqual(User.objects.count(), user_count + 1)
        response = client.get(reverse('auth_homepage'))
        self.assertContains(response, username)
        last_user = User.objects.order_by('id').last()
        self.assertEqual(last_user.username, username)
        self.assertEqual(last_user.email, email)
        self.assertEqual(last_user.ou.slug, self.ou.slug)
        self.assertTrue(last_user.check_password(password))

        # User click on second email
        client = Client()
        activation_url = get_link_from_mail(activation_mail2)
        response = client.get(activation_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.status_code, 200)
        self.assertFormError(
            response, 'form', 'username',
            _('This username is already in use. Please supply a different username.'))