@@ -12,29 +12,59 @@ import (
12
12
13
13
"code.gitea.io/gitea/modules/json"
14
14
"code.gitea.io/gitea/modules/setting"
15
+ "code.gitea.io/gitea/routers/web/auth"
15
16
"code.gitea.io/gitea/tests"
16
17
17
18
"github.com/stretchr/testify/assert"
18
19
)
19
20
20
- const defaultAuthorize = "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate"
21
-
22
- func TestNoClientID (t * testing.T ) {
21
+ func TestAuthorizeNoClientID (t * testing.T ) {
23
22
defer tests .PrepareTestEnv (t )()
24
23
req := NewRequest (t , "GET" , "/login/oauth/authorize" )
25
24
ctx := loginUser (t , "user2" )
26
- ctx .MakeRequest (t , req , http .StatusBadRequest )
25
+ resp := ctx .MakeRequest (t , req , http .StatusBadRequest )
26
+ assert .Contains (t , resp .Body .String (), "Client ID not registered" )
27
+ }
28
+
29
+ func TestAuthorizeUnregisteredRedirect (t * testing.T ) {
30
+ defer tests .PrepareTestEnv (t )()
31
+ req := NewRequest (t , "GET" , "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=UNREGISTERED&response_type=code&state=thestate" )
32
+ ctx := loginUser (t , "user1" )
33
+ resp := ctx .MakeRequest (t , req , http .StatusBadRequest )
34
+ assert .Contains (t , resp .Body .String (), "Unregistered Redirect URI" )
35
+ }
36
+
37
+ func TestAuthorizeUnsupportedResponseType (t * testing.T ) {
38
+ defer tests .PrepareTestEnv (t )()
39
+ req := NewRequest (t , "GET" , "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=UNEXPECTED&state=thestate" )
40
+ ctx := loginUser (t , "user1" )
41
+ resp := ctx .MakeRequest (t , req , http .StatusSeeOther )
42
+ u , err := resp .Result ().Location ()
43
+ assert .NoError (t , err )
44
+ assert .Equal (t , "unsupported_response_type" , u .Query ().Get ("error" ))
45
+ assert .Equal (t , "Only code response type is supported." , u .Query ().Get ("error_description" ))
46
+ }
47
+
48
+ func TestAuthorizeUnsupportedCodeChallengeMethod (t * testing.T ) {
49
+ defer tests .PrepareTestEnv (t )()
50
+ req := NewRequest (t , "GET" , "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate&code_challenge_method=UNEXPECTED" )
51
+ ctx := loginUser (t , "user1" )
52
+ resp := ctx .MakeRequest (t , req , http .StatusSeeOther )
53
+ u , err := resp .Result ().Location ()
54
+ assert .NoError (t , err )
55
+ assert .Equal (t , "invalid_request" , u .Query ().Get ("error" ))
56
+ assert .Equal (t , "unsupported code challenge method" , u .Query ().Get ("error_description" ))
27
57
}
28
58
29
- func TestLoginRedirect (t * testing.T ) {
59
+ func TestAuthorizeLoginRedirect (t * testing.T ) {
30
60
defer tests .PrepareTestEnv (t )()
31
61
req := NewRequest (t , "GET" , "/login/oauth/authorize" )
32
62
assert .Contains (t , MakeRequest (t , req , http .StatusSeeOther ).Body .String (), "/user/login" )
33
63
}
34
64
35
- func TestShowAuthorize (t * testing.T ) {
65
+ func TestAuthorizeShow (t * testing.T ) {
36
66
defer tests .PrepareTestEnv (t )()
37
- req := NewRequest (t , "GET" , defaultAuthorize )
67
+ req := NewRequest (t , "GET" , "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate" )
38
68
ctx := loginUser (t , "user4" )
39
69
resp := ctx .MakeRequest (t , req , http .StatusOK )
40
70
@@ -43,15 +73,17 @@ func TestShowAuthorize(t *testing.T) {
43
73
htmlDoc .GetCSRF ()
44
74
}
45
75
46
- func TestRedirectWithExistingGrant (t * testing.T ) {
76
+ func TestAuthorizeRedirectWithExistingGrant (t * testing.T ) {
47
77
defer tests .PrepareTestEnv (t )()
48
- req := NewRequest (t , "GET" , defaultAuthorize )
78
+ req := NewRequest (t , "GET" , "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https%3A%2F%2Fexample.com%2Fxyzzy&response_type=code&state=thestate" )
49
79
ctx := loginUser (t , "user1" )
50
80
resp := ctx .MakeRequest (t , req , http .StatusSeeOther )
51
81
u , err := resp .Result ().Location ()
52
82
assert .NoError (t , err )
53
83
assert .Equal (t , "thestate" , u .Query ().Get ("state" ))
54
84
assert .Truef (t , len (u .Query ().Get ("code" )) > 30 , "authorization code '%s' should be longer then 30" , u .Query ().Get ("code" ))
85
+ u .RawQuery = ""
86
+ assert .Equal (t , "https://example.com/xyzzy" , u .String ())
55
87
}
56
88
57
89
func TestAccessTokenExchange (t * testing.T ) {
@@ -62,7 +94,7 @@ func TestAccessTokenExchange(t *testing.T) {
62
94
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
63
95
"redirect_uri" : "a" ,
64
96
"code" : "authcode" ,
65
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
97
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
66
98
})
67
99
resp := MakeRequest (t , req , http .StatusOK )
68
100
type response struct {
@@ -78,15 +110,15 @@ func TestAccessTokenExchange(t *testing.T) {
78
110
assert .True (t , len (parsed .RefreshToken ) > 10 )
79
111
}
80
112
81
- func TestAccessTokenExchangeWithoutPKCE (t * testing.T ) {
113
+ func TestAccessTokenExchangeJSON (t * testing.T ) {
82
114
defer tests .PrepareTestEnv (t )()
83
115
req := NewRequestWithJSON (t , "POST" , "/login/oauth/access_token" , map [string ]string {
84
116
"grant_type" : "authorization_code" ,
85
117
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
86
118
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
87
119
"redirect_uri" : "a" ,
88
120
"code" : "authcode" ,
89
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
121
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
90
122
})
91
123
resp := MakeRequest (t , req , http .StatusOK )
92
124
type response struct {
@@ -102,16 +134,20 @@ func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
102
134
assert .True (t , len (parsed .RefreshToken ) > 10 )
103
135
}
104
136
105
- func TestAccessTokenExchangeJSON (t * testing.T ) {
137
+ func TestAccessTokenExchangeWithoutPKCE (t * testing.T ) {
106
138
defer tests .PrepareTestEnv (t )()
107
- req := NewRequestWithJSON (t , "POST" , "/login/oauth/access_token" , map [string ]string {
139
+ req := NewRequestWithValues (t , "POST" , "/login/oauth/access_token" , map [string ]string {
108
140
"grant_type" : "authorization_code" ,
109
141
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
110
142
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
111
143
"redirect_uri" : "a" ,
112
144
"code" : "authcode" ,
113
145
})
114
- MakeRequest (t , req , http .StatusBadRequest )
146
+ resp := MakeRequest (t , req , http .StatusBadRequest )
147
+ parsedError := new (auth.AccessTokenError )
148
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
149
+ assert .Equal (t , "unauthorized_client" , string (parsedError .ErrorCode ))
150
+ assert .Equal (t , "failed PKCE code challenge" , parsedError .ErrorDescription )
115
151
}
116
152
117
153
func TestAccessTokenExchangeWithInvalidCredentials (t * testing.T ) {
@@ -123,49 +159,73 @@ func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
123
159
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
124
160
"redirect_uri" : "a" ,
125
161
"code" : "authcode" ,
126
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
162
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
127
163
})
128
- MakeRequest (t , req , http .StatusBadRequest )
164
+ resp := MakeRequest (t , req , http .StatusBadRequest )
165
+ parsedError := new (auth.AccessTokenError )
166
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
167
+ assert .Equal (t , "invalid_client" , string (parsedError .ErrorCode ))
168
+ assert .Equal (t , "cannot load client with client id: '???'" , parsedError .ErrorDescription )
169
+
129
170
// invalid client secret
130
171
req = NewRequestWithValues (t , "POST" , "/login/oauth/access_token" , map [string ]string {
131
172
"grant_type" : "authorization_code" ,
132
173
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
133
174
"client_secret" : "???" ,
134
175
"redirect_uri" : "a" ,
135
176
"code" : "authcode" ,
136
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
177
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
137
178
})
138
- MakeRequest (t , req , http .StatusBadRequest )
179
+ resp = MakeRequest (t , req , http .StatusBadRequest )
180
+ parsedError = new (auth.AccessTokenError )
181
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
182
+ assert .Equal (t , "unauthorized_client" , string (parsedError .ErrorCode ))
183
+ assert .Equal (t , "invalid client secret" , parsedError .ErrorDescription )
184
+
139
185
// invalid redirect uri
140
186
req = NewRequestWithValues (t , "POST" , "/login/oauth/access_token" , map [string ]string {
141
187
"grant_type" : "authorization_code" ,
142
188
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
143
189
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
144
190
"redirect_uri" : "???" ,
145
191
"code" : "authcode" ,
146
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
192
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
147
193
})
148
- MakeRequest (t , req , http .StatusBadRequest )
194
+ resp = MakeRequest (t , req , http .StatusBadRequest )
195
+ parsedError = new (auth.AccessTokenError )
196
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
197
+ assert .Equal (t , "unauthorized_client" , string (parsedError .ErrorCode ))
198
+ assert .Equal (t , "unexpected redirect URI" , parsedError .ErrorDescription )
199
+
149
200
// invalid authorization code
150
201
req = NewRequestWithValues (t , "POST" , "/login/oauth/access_token" , map [string ]string {
151
202
"grant_type" : "authorization_code" ,
152
203
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
153
204
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
154
205
"redirect_uri" : "a" ,
155
206
"code" : "???" ,
156
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
207
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
157
208
})
158
- MakeRequest (t , req , http .StatusBadRequest )
209
+ resp = MakeRequest (t , req , http .StatusBadRequest )
210
+ parsedError = new (auth.AccessTokenError )
211
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
212
+ assert .Equal (t , "unauthorized_client" , string (parsedError .ErrorCode ))
213
+ assert .Equal (t , "client is not authorized" , parsedError .ErrorDescription )
214
+
159
215
// invalid grant_type
160
216
req = NewRequestWithValues (t , "POST" , "/login/oauth/access_token" , map [string ]string {
161
217
"grant_type" : "???" ,
162
218
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
163
219
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
164
220
"redirect_uri" : "a" ,
165
221
"code" : "authcode" ,
166
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
222
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
167
223
})
168
- MakeRequest (t , req , http .StatusBadRequest )
224
+ resp = MakeRequest (t , req , http .StatusBadRequest )
225
+ parsedError = new (auth.AccessTokenError )
226
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
227
+ assert .Equal (t , "unsupported_grant_type" , string (parsedError .ErrorCode ))
228
+ assert .Equal (t , "Only refresh_token or authorization_code grant type is supported" , parsedError .ErrorDescription )
169
229
}
170
230
171
231
func TestAccessTokenExchangeWithBasicAuth (t * testing.T ) {
@@ -174,7 +234,7 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
174
234
"grant_type" : "authorization_code" ,
175
235
"redirect_uri" : "a" ,
176
236
"code" : "authcode" ,
177
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
237
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
178
238
})
179
239
req .Header .Add ("Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9" )
180
240
resp := MakeRequest (t , req , http .StatusOK )
@@ -195,19 +255,54 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
195
255
"grant_type" : "authorization_code" ,
196
256
"redirect_uri" : "a" ,
197
257
"code" : "authcode" ,
198
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
258
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
199
259
})
200
260
req .Header .Add ("Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OmJsYWJsYQ==" )
201
- MakeRequest (t , req , http .StatusBadRequest )
261
+ resp = MakeRequest (t , req , http .StatusBadRequest )
262
+ parsedError := new (auth.AccessTokenError )
263
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
264
+ assert .Equal (t , "unauthorized_client" , string (parsedError .ErrorCode ))
265
+ assert .Equal (t , "invalid client secret" , parsedError .ErrorDescription )
202
266
203
267
// missing header
204
268
req = NewRequestWithValues (t , "POST" , "/login/oauth/access_token" , map [string ]string {
205
269
"grant_type" : "authorization_code" ,
206
270
"redirect_uri" : "a" ,
207
271
"code" : "authcode" ,
208
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
272
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
273
+ })
274
+ resp = MakeRequest (t , req , http .StatusBadRequest )
275
+ parsedError = new (auth.AccessTokenError )
276
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
277
+ assert .Equal (t , "invalid_client" , string (parsedError .ErrorCode ))
278
+ assert .Equal (t , "cannot load client with client id: ''" , parsedError .ErrorDescription )
279
+
280
+ // client_id inconsistent with Authorization header
281
+ req = NewRequestWithValues (t , "POST" , "/login/oauth/access_token" , map [string ]string {
282
+ "grant_type" : "authorization_code" ,
283
+ "redirect_uri" : "a" ,
284
+ "code" : "authcode" ,
285
+ "client_id" : "inconsistent" ,
286
+ })
287
+ req .Header .Add ("Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9" )
288
+ resp = MakeRequest (t , req , http .StatusBadRequest )
289
+ parsedError = new (auth.AccessTokenError )
290
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
291
+ assert .Equal (t , "invalid_request" , string (parsedError .ErrorCode ))
292
+ assert .Equal (t , "client_id in request body inconsistent with Authorization header" , parsedError .ErrorDescription )
293
+
294
+ // client_secret inconsistent with Authorization header
295
+ req = NewRequestWithValues (t , "POST" , "/login/oauth/access_token" , map [string ]string {
296
+ "grant_type" : "authorization_code" ,
297
+ "redirect_uri" : "a" ,
298
+ "code" : "authcode" ,
299
+ "client_secret" : "inconsistent" ,
209
300
})
210
- MakeRequest (t , req , http .StatusBadRequest )
301
+ req .Header .Add ("Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9" )
302
+ parsedError = new (auth.AccessTokenError )
303
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
304
+ assert .Equal (t , "invalid_request" , string (parsedError .ErrorCode ))
305
+ assert .Equal (t , "client_id in request body inconsistent with Authorization header" , parsedError .ErrorDescription )
211
306
}
212
307
213
308
func TestRefreshTokenInvalidation (t * testing.T ) {
@@ -218,7 +313,7 @@ func TestRefreshTokenInvalidation(t *testing.T) {
218
313
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
219
314
"redirect_uri" : "a" ,
220
315
"code" : "authcode" ,
221
- "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" , // test PKCE additionally
316
+ "code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
222
317
})
223
318
resp := MakeRequest (t , req , http .StatusOK )
224
319
type response struct {
@@ -256,6 +351,11 @@ func TestRefreshTokenInvalidation(t *testing.T) {
256
351
refreshReq .Body = io .NopCloser (bytes .NewReader (bs ))
257
352
MakeRequest (t , refreshReq , http .StatusOK )
258
353
354
+ // repeat request should fail
259
355
refreshReq .Body = io .NopCloser (bytes .NewReader (bs ))
260
- MakeRequest (t , refreshReq , http .StatusBadRequest )
356
+ resp = MakeRequest (t , refreshReq , http .StatusBadRequest )
357
+ parsedError := new (auth.AccessTokenError )
358
+ assert .NoError (t , json .Unmarshal (resp .Body .Bytes (), parsedError ))
359
+ assert .Equal (t , "unauthorized_client" , string (parsedError .ErrorCode ))
360
+ assert .Equal (t , "token was already used" , parsedError .ErrorDescription )
261
361
}
0 commit comments