Projet

Général

Profil

0001-idp_oidc-use-invalid_grant-error-in-token-endpoint-6.patch

Benjamin Dauvergne, 27 juillet 2022 17:31

Télécharger (9,38 ko)

Voir les différences:

Subject: [PATCH] idp_oidc: use invalid_grant error in token endpoint (#66544)

 src/authentic2_idp_oidc/views.py | 13 +++--
 tests/idp_oidc/conftest.py       | 28 +++++++----
 tests/idp_oidc/test_misc.py      | 84 +++++++++++++++++++++++++++-----
 3 files changed, 100 insertions(+), 25 deletions(-)
src/authentic2_idp_oidc/views.py
74 74
            content['error_description'] = self.error_description
75 75

  
76 76
        if self.client:
77
            content['client_id'] = self.client.client_id
77 78
            msg = 'idp_oidc: error "%s" in %s endpoint "%s" for client %s'
78 79
            if self.extra_info:
79 80
                msg += ' (%s)' % self.extra_info
......
181 182
    error_code = 'invalid_client'
182 183

  
183 184

  
185
class InvalidGrant(OIDCException):
186
    error_code = 'invalid_grant'
187

  
188

  
184 189
class WrongClientSecret(InvalidClient):
185 190
    error_description = _('Wrong client secret')
186 191

  
......
730 735
    try:
731 736
        oidc_code = models.OIDCCode.objects.select_related().get(uuid=code)
732 737
    except models.OIDCCode.DoesNotExist:
733
        raise InvalidRequest(_('Parameter "code" is invalid'), client=client)
738
        raise InvalidGrant(_('Code is unknown.'), client=client)
739
    if oidc_code.client != client:
740
        raise InvalidGrant(_('Code was issued to a different client.'), client=client)
734 741
    if not oidc_code.is_valid():
735
        raise InvalidRequest(_('Parameter "code" has expired or user is disconnected'), client=client)
742
        raise InvalidGrant(_('Code has expired, user is disconnected or session was lost.'), client=client)
736 743
    redirect_uri = request.POST.get('redirect_uri')
737 744
    if oidc_code.redirect_uri != redirect_uri:
738
        raise InvalidRequest(_('Parameter "redirect_uri" does not match the code.'), client=client)
745
        raise InvalidGrant(_('Redirect_uri does not match the code.'), client=client)
739 746
    if client.access_token_duration is None:
740 747
        expires_in = datetime.timedelta(seconds=oidc_code.session.get_expiry_age())
741 748
        expired = None
tests/idp_oidc/conftest.py
82 82
    return settings
83 83

  
84 84

  
85
def make_client(app, superuser, params=None):
86
    Attribute.objects.create(
85
def make_client(app, params=None):
86
    Attribute.objects.get_or_create(
87 87
        name='cityscape_image',
88
        label='cityscape',
89
        kind='profile_image',
90
        asked_on_registration=True,
91
        required=False,
92
        user_visible=True,
93
        user_editable=True,
88
        defaults=dict(
89
            label='cityscape',
90
            kind='profile_image',
91
            asked_on_registration=True,
92
            required=False,
93
            user_visible=True,
94
            user_editable=True,
95
        ),
94 96
    )
95 97

  
96 98
    client = OIDCClient(
97 99
        name='oidcclient',
98 100
        slug='oidcclient',
101
        client_id='1234',
99 102
        ou=get_default_ou(),
100 103
        unauthorized_url='https://example.com/southpark/',
101 104
        redirect_uris='https://example.com/callbac%C3%A9',
......
111 114
    return client
112 115

  
113 116

  
117
@pytest.fixture(name='make_client')
118
def make_client_fixture():
119
    return make_client
120

  
121

  
114 122
@pytest.fixture
115 123
def client(app, superuser):
116
    return make_client(app, superuser, {})
124
    return make_client(app)
117 125

  
118 126

  
119 127
@pytest.fixture
......
125 133

  
126 134
@pytest.fixture
127 135
def oidc_client(request, superuser, app, simple_user, oidc_settings):
128
    return make_client(app, superuser, getattr(request, 'param', None) or {})
136
    return make_client(app, getattr(request, 'param', None) or {})
129 137

  
130 138

  
131 139
@pytest.fixture
tests/idp_oidc/test_misc.py
470 470

  
471 471

  
472 472
@pytest.mark.parametrize('oidc_client', OIDC_CLIENT_PARAMS, indirect=True)
473
def test_invalid_request(oidc_client, caplog, oidc_settings, simple_user, app):
473
def test_invalid_request(oidc_client, caplog, oidc_settings, simple_user, app, make_client):
474 474
    redirect_uri = oidc_client.redirect_uris.split()[0]
475 475
    if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE:
476 476
        fragment = False
......
865 865
    # check expired codes
866 866
    if oidc_client.authorization_flow == oidc_client.FLOW_AUTHORIZATION_CODE:
867 867
        assert OIDCCode.objects.count() == 1
868
        code = OIDCCode.objects.get()
869
        assert code.is_valid()
870
        # make code expire
871
        code.expired = now() - datetime.timedelta(seconds=120)
872
        assert not code.is_valid()
873
        code.save()
868
        oidc_code = OIDCCode.objects.get()
869
        assert oidc_code.is_valid()
870

  
874 871
        location = urllib.parse.urlparse(response['Location'])
875 872
        query = urllib.parse.parse_qs(location.query)
876 873
        assert set(query.keys()) == {'code'}
877
        assert query['code'] == [code.uuid]
874
        assert query['code'] == [oidc_code.uuid]
878 875
        code = query['code'][0]
879 876
        token_url = make_url('oidc-token')
877

  
878
        # missing code parameter
880 879
        params = {
881 880
            'grant_type': 'authorization_code',
882 881
            'redirect_uri': oidc_client.redirect_uris.split()[0],
......
888 887
        assert response.json['error_description'] == 'Missing parameter "code"'
889 888

  
890 889
        params['code'] = code
890

  
891
        # wrong redirect_uri
892
        response = app.post(
893
            token_url,
894
            params=dict(params, redirect_uri='https://example.com/'),
895
            headers=client_authentication_headers(oidc_client),
896
            status=400,
897
        )
898
        assert 'error' in response.json
899
        assert response.json['error'] == 'invalid_grant', response.json
900
        assert response.json['error_description'] == 'Redirect_uri does not match the code.'
901
        assert response.json['client_id'] == '1234'
902

  
903
        # unknown code
904
        response = app.post(
905
            token_url,
906
            params=dict(params, code='xyz'),
907
            headers=client_authentication_headers(oidc_client),
908
            status=400,
909
        )
910
        assert 'error' in response.json
911
        assert response.json['error'] == 'invalid_grant'
912
        assert response.json['error_description'] == 'Code is unknown.'
913
        assert response.json['client_id'] == '1234'
914

  
915
        # code from another client
916
        other_client = make_client(app, params={'slug': 'other', 'name': 'other', 'client_id': 'abcd'})
917
        other_oidc_code = OIDCCode.objects.create(
918
            client=other_client,
919
            user=oidc_code.user,
920
            profile=None,
921
            scopes='',
922
            state='1234',
923
            nonce='1234',
924
            expired=now() + datetime.timedelta(hours=1),
925
            redirect_uri=oidc_code.redirect_uri,
926
            auth_time=now(),
927
            session_key=oidc_code.session_key,
928
        )
929
        response = app.post(
930
            token_url,
931
            params=dict(params, code=other_oidc_code.uuid),
932
            headers=client_authentication_headers(oidc_client),
933
            status=400,
934
        )
935
        assert 'error' in response.json
936
        assert response.json['error'] == 'invalid_grant'
937
        assert response.json['error_description'] == 'Code was issued to a different client.', response.json
938
        assert response.json['client_id'] == '1234'
939
        other_oidc_code.delete()
940
        other_client.delete()
941

  
942
        # make code expire
943
        oidc_code.expired = now() - datetime.timedelta(seconds=120)
944
        assert not oidc_code.is_valid()
945
        oidc_code.save()
946

  
947
        # expired code
891 948
        response = app.post(
892 949
            token_url, params=params, headers=client_authentication_headers(oidc_client), status=400
893 950
        )
894 951
        assert 'error' in response.json
895
        assert response.json['error'] == 'invalid_request'
896
        assert response.json['error_description'] == 'Parameter "code" has expired or user is disconnected'
952
        assert response.json['error'] == 'invalid_grant'
953
        assert (
954
            response.json['error_description']
955
            == 'Code has expired, user is disconnected or session was lost.'
956
        )
957
        assert response.json['client_id'] == '1234'
897 958

  
898 959
    # invalid logout
899 960
    logout_url = make_url(
......
926 987
            status=400,
927 988
        )
928 989
        assert 'error' in response.json
929
        assert response.json['error'] == 'invalid_request'
930
        assert response.json['error_description'] == 'Parameter "code" has expired or user is disconnected'
990
        assert response.json['error'] == 'invalid_grant'
931 991

  
932 992

  
933 993
def test_client_secret_post_authentication(oidc_settings, app, simple_oidc_client, simple_user):
934
-