2019-03-08 19:42:50 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2019-03-08 19:42:50 +03:00
2022-09-02 22:18:23 +03:00
package integration
2019-03-08 19:42:50 +03:00
import (
2020-11-13 15:51:07 +03:00
"bytes"
2023-06-27 16:00:15 +03:00
"context"
2024-04-18 10:55:08 +03:00
"crypto/sha256"
"encoding/base64"
2023-06-27 16:00:15 +03:00
"fmt"
2021-09-22 08:38:34 +03:00
"io"
2022-03-23 07:54:07 +03:00
"net/http"
2024-09-22 13:57:03 +03:00
"net/http/httptest"
2024-04-18 10:55:08 +03:00
"net/url"
2024-08-09 12:14:40 +03:00
"strings"
2019-03-08 19:42:50 +03:00
"testing"
2023-06-27 16:00:15 +03:00
auth_model "code.gitea.io/gitea/models/auth"
2024-08-09 12:14:40 +03:00
"code.gitea.io/gitea/models/db"
2023-06-27 16:00:15 +03:00
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
2021-07-24 19:03:58 +03:00
"code.gitea.io/gitea/modules/json"
2019-04-12 10:50:21 +03:00
"code.gitea.io/gitea/modules/setting"
2024-08-09 12:14:40 +03:00
api "code.gitea.io/gitea/modules/structs"
2023-06-27 16:00:15 +03:00
"code.gitea.io/gitea/modules/test"
2022-10-12 07:22:43 +03:00
"code.gitea.io/gitea/routers/web/auth"
2024-09-22 13:57:03 +03:00
forgejo_context "code.gitea.io/gitea/services/context"
2022-09-02 22:18:23 +03:00
"code.gitea.io/gitea/tests"
2019-04-12 10:50:21 +03:00
2023-06-27 16:00:15 +03:00
"github.com/markbates/goth"
2019-03-08 19:42:50 +03:00
"github.com/stretchr/testify/assert"
2024-07-30 22:41:10 +03:00
"github.com/stretchr/testify/require"
2019-03-08 19:42:50 +03:00
)
2022-10-12 07:22:43 +03:00
func TestAuthorizeNoClientID ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-03-08 19:42:50 +03:00
req := NewRequest ( t , "GET" , "/login/oauth/authorize" )
ctx := loginUser ( t , "user2" )
2022-10-12 07:22:43 +03:00
resp := ctx . MakeRequest ( t , req , http . StatusBadRequest )
assert . Contains ( t , resp . Body . String ( ) , "Client ID not registered" )
}
func TestAuthorizeUnregisteredRedirect ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
req := NewRequest ( t , "GET" , "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=UNREGISTERED&response_type=code&state=thestate" )
ctx := loginUser ( t , "user1" )
resp := ctx . MakeRequest ( t , req , http . StatusBadRequest )
assert . Contains ( t , resp . Body . String ( ) , "Unregistered Redirect URI" )
}
func TestAuthorizeUnsupportedResponseType ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
req := NewRequest ( t , "GET" , "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=UNEXPECTED&state=thestate" )
ctx := loginUser ( t , "user1" )
resp := ctx . MakeRequest ( t , req , http . StatusSeeOther )
u , err := resp . Result ( ) . Location ( )
2024-07-30 22:41:10 +03:00
require . NoError ( t , err )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "unsupported_response_type" , u . Query ( ) . Get ( "error" ) )
assert . Equal ( t , "Only code response type is supported." , u . Query ( ) . Get ( "error_description" ) )
}
func TestAuthorizeUnsupportedCodeChallengeMethod ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
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" )
ctx := loginUser ( t , "user1" )
resp := ctx . MakeRequest ( t , req , http . StatusSeeOther )
u , err := resp . Result ( ) . Location ( )
2024-07-30 22:41:10 +03:00
require . NoError ( t , err )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "invalid_request" , u . Query ( ) . Get ( "error" ) )
assert . Equal ( t , "unsupported code challenge method" , u . Query ( ) . Get ( "error_description" ) )
2019-03-08 19:42:50 +03:00
}
2022-10-12 07:22:43 +03:00
func TestAuthorizeLoginRedirect ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-03-08 19:42:50 +03:00
req := NewRequest ( t , "GET" , "/login/oauth/authorize" )
2022-03-23 07:54:07 +03:00
assert . Contains ( t , MakeRequest ( t , req , http . StatusSeeOther ) . Body . String ( ) , "/user/login" )
2019-03-08 19:42:50 +03:00
}
2022-10-12 07:22:43 +03:00
func TestAuthorizeShow ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2022-10-12 07:22:43 +03:00
req := NewRequest ( t , "GET" , "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate" )
2019-03-08 19:42:50 +03:00
ctx := loginUser ( t , "user4" )
2022-03-23 07:54:07 +03:00
resp := ctx . MakeRequest ( t , req , http . StatusOK )
2019-03-08 19:42:50 +03:00
htmlDoc := NewHTMLParser ( t , resp . Body )
htmlDoc . AssertElement ( t , "#authorize-app" , true )
htmlDoc . GetCSRF ( )
}
2024-06-05 18:50:15 +03:00
func TestOAuth_AuthorizeConfidentialTwice ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
// da7da3ba-9a13-4167-856f-3899de0b0138 a confidential client in models/fixtures/oauth2_application.yml
// request authorization for the first time shows the grant page ...
authorizeURL := "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate"
req := NewRequest ( t , "GET" , authorizeURL )
ctx := loginUser ( t , "user4" )
resp := ctx . MakeRequest ( t , req , http . StatusOK )
htmlDoc := NewHTMLParser ( t , resp . Body )
htmlDoc . AssertElement ( t , "#authorize-app" , true )
// ... and the user grants the authorization
req = NewRequestWithValues ( t , "POST" , "/login/oauth/grant" , map [ string ] string {
"_csrf" : htmlDoc . GetCSRF ( ) ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"redirect_uri" : "a" ,
"state" : "thestate" ,
"granted" : "true" ,
} )
resp = ctx . MakeRequest ( t , req , http . StatusSeeOther )
assert . Contains ( t , test . RedirectURL ( resp ) , "code=" )
// request authorization the second time and the grant page is not shown again, redirection happens immediately
req = NewRequest ( t , "GET" , authorizeURL )
resp = ctx . MakeRequest ( t , req , http . StatusSeeOther )
assert . Contains ( t , test . RedirectURL ( resp ) , "code=" )
}
func TestOAuth_AuthorizePublicTwice ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
// ce5a1322-42a7-11ed-b878-0242ac120002 is a public client in models/fixtures/oauth2_application.yml
authorizeURL := "/login/oauth/authorize?client_id=ce5a1322-42a7-11ed-b878-0242ac120002&redirect_uri=b&response_type=code&code_challenge_method=plain&code_challenge=CODE&state=thestate"
ctx := loginUser ( t , "user4" )
// a public client must be authorized every time
for _ , name := range [ ] string { "First" , "Second" } {
t . Run ( name , func ( t * testing . T ) {
req := NewRequest ( t , "GET" , authorizeURL )
resp := ctx . MakeRequest ( t , req , http . StatusOK )
htmlDoc := NewHTMLParser ( t , resp . Body )
htmlDoc . AssertElement ( t , "#authorize-app" , true )
req = NewRequestWithValues ( t , "POST" , "/login/oauth/grant" , map [ string ] string {
"_csrf" : htmlDoc . GetCSRF ( ) ,
"client_id" : "ce5a1322-42a7-11ed-b878-0242ac120002" ,
"redirect_uri" : "b" ,
"state" : "thestate" ,
"granted" : "true" ,
} )
resp = ctx . MakeRequest ( t , req , http . StatusSeeOther )
assert . Contains ( t , test . RedirectURL ( resp ) , "code=" )
} )
}
}
2022-10-12 07:22:43 +03:00
func TestAuthorizeRedirectWithExistingGrant ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2022-10-12 07:22:43 +03:00
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" )
2019-03-08 19:42:50 +03:00
ctx := loginUser ( t , "user1" )
2022-03-23 07:54:07 +03:00
resp := ctx . MakeRequest ( t , req , http . StatusSeeOther )
2019-03-08 19:42:50 +03:00
u , err := resp . Result ( ) . Location ( )
2024-07-30 22:41:10 +03:00
require . NoError ( t , err )
2019-03-08 19:42:50 +03:00
assert . Equal ( t , "thestate" , u . Query ( ) . Get ( "state" ) )
2024-07-30 22:41:10 +03:00
assert . Greaterf ( t , len ( u . Query ( ) . Get ( "code" ) ) , 30 , "authorization code '%s' should be longer then 30" , u . Query ( ) . Get ( "code" ) )
2022-10-12 07:22:43 +03:00
u . RawQuery = ""
assert . Equal ( t , "https://example.com/xyzzy" , u . String ( ) )
2019-03-08 19:42:50 +03:00
}
2022-10-24 10:59:24 +03:00
func TestAuthorizePKCERequiredForPublicClient ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
req := NewRequest ( t , "GET" , "/login/oauth/authorize?client_id=ce5a1322-42a7-11ed-b878-0242ac120002&redirect_uri=http%3A%2F%2F127.0.0.1&response_type=code&state=thestate" )
ctx := loginUser ( t , "user1" )
resp := ctx . MakeRequest ( t , req , http . StatusSeeOther )
u , err := resp . Result ( ) . Location ( )
2024-07-30 22:41:10 +03:00
require . NoError ( t , err )
2022-10-24 10:59:24 +03:00
assert . Equal ( t , "invalid_request" , u . Query ( ) . Get ( "error" ) )
assert . Equal ( t , "PKCE is required for public clients" , u . Query ( ) . Get ( "error_description" ) )
}
2019-03-08 19:42:50 +03:00
func TestAccessTokenExchange ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-03-08 19:42:50 +03:00
req := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-03-08 19:42:50 +03:00
} )
2022-03-23 07:54:07 +03:00
resp := MakeRequest ( t , req , http . StatusOK )
2019-03-08 19:42:50 +03:00
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
}
parsed := new ( response )
2021-03-02 00:08:10 +03:00
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsed ) )
assert . Greater ( t , len ( parsed . AccessToken ) , 10 )
assert . Greater ( t , len ( parsed . RefreshToken ) , 10 )
2023-06-03 06:59:28 +03:00
}
func TestAccessTokenExchangeWithPublicClient ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
req := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"client_id" : "ce5a1322-42a7-11ed-b878-0242ac120002" ,
"redirect_uri" : "http://127.0.0.1" ,
"code" : "authcodepublic" ,
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
} )
resp := MakeRequest ( t , req , http . StatusOK )
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
}
parsed := new ( response )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsed ) )
assert . Greater ( t , len ( parsed . AccessToken ) , 10 )
assert . Greater ( t , len ( parsed . RefreshToken ) , 10 )
2019-03-08 19:42:50 +03:00
}
2022-10-12 07:22:43 +03:00
func TestAccessTokenExchangeJSON ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-04-15 18:54:50 +03:00
req := NewRequestWithJSON ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-04-15 18:54:50 +03:00
} )
2022-03-23 07:54:07 +03:00
resp := MakeRequest ( t , req , http . StatusOK )
2019-04-15 18:54:50 +03:00
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
}
parsed := new ( response )
2021-03-02 00:08:10 +03:00
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsed ) )
assert . Greater ( t , len ( parsed . AccessToken ) , 10 )
assert . Greater ( t , len ( parsed . RefreshToken ) , 10 )
2019-04-15 18:54:50 +03:00
}
2022-10-12 07:22:43 +03:00
func TestAccessTokenExchangeWithoutPKCE ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2022-10-12 07:22:43 +03:00
req := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
2019-03-08 19:42:50 +03:00
"grant_type" : "authorization_code" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
} )
2022-10-12 07:22:43 +03:00
resp := MakeRequest ( t , req , http . StatusBadRequest )
parsedError := new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "unauthorized_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "failed PKCE code challenge" , parsedError . ErrorDescription )
2019-03-08 19:42:50 +03:00
}
func TestAccessTokenExchangeWithInvalidCredentials ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-03-08 19:42:50 +03:00
// invalid client id
req := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"client_id" : "???" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-03-08 19:42:50 +03:00
} )
2022-10-12 07:22:43 +03:00
resp := MakeRequest ( t , req , http . StatusBadRequest )
parsedError := new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "invalid_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "cannot load client with client id: '???'" , parsedError . ErrorDescription )
2019-03-08 19:42:50 +03:00
// invalid client secret
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "???" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-03-08 19:42:50 +03:00
} )
2022-10-12 07:22:43 +03:00
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError = new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "unauthorized_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "invalid client secret" , parsedError . ErrorDescription )
2019-03-08 19:42:50 +03:00
// invalid redirect uri
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "???" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-03-08 19:42:50 +03:00
} )
2022-10-12 07:22:43 +03:00
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError = new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "unauthorized_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "unexpected redirect URI" , parsedError . ErrorDescription )
2019-03-08 19:42:50 +03:00
// invalid authorization code
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"code" : "???" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-03-08 19:42:50 +03:00
} )
2022-10-12 07:22:43 +03:00
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError = new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "unauthorized_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "client is not authorized" , parsedError . ErrorDescription )
2019-03-08 19:42:50 +03:00
// invalid grant_type
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "???" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-03-08 19:42:50 +03:00
} )
2022-10-12 07:22:43 +03:00
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError = new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "unsupported_grant_type" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "Only refresh_token or authorization_code grant type is supported" , parsedError . ErrorDescription )
2019-03-08 19:42:50 +03:00
}
2019-03-11 05:54:59 +03:00
func TestAccessTokenExchangeWithBasicAuth ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-03-11 05:54:59 +03:00
req := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-03-11 05:54:59 +03:00
} )
req . Header . Add ( "Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9" )
2022-03-23 07:54:07 +03:00
resp := MakeRequest ( t , req , http . StatusOK )
2019-03-11 05:54:59 +03:00
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
}
parsed := new ( response )
2021-03-02 00:08:10 +03:00
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsed ) )
assert . Greater ( t , len ( parsed . AccessToken ) , 10 )
assert . Greater ( t , len ( parsed . RefreshToken ) , 10 )
2019-03-11 05:54:59 +03:00
// use wrong client_secret
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-03-11 05:54:59 +03:00
} )
req . Header . Add ( "Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OmJsYWJsYQ==" )
2022-10-12 07:22:43 +03:00
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError := new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "unauthorized_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "invalid client secret" , parsedError . ErrorDescription )
2019-03-11 05:54:59 +03:00
// missing header
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
} )
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError = new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "invalid_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "cannot load client with client id: ''" , parsedError . ErrorDescription )
// client_id inconsistent with Authorization header
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
"client_id" : "inconsistent" ,
} )
req . Header . Add ( "Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9" )
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError = new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "invalid_request" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "client_id in request body inconsistent with Authorization header" , parsedError . ErrorDescription )
// client_secret inconsistent with Authorization header
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
"client_secret" : "inconsistent" ,
2019-03-11 05:54:59 +03:00
} )
2022-10-12 07:22:43 +03:00
req . Header . Add ( "Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9" )
2022-10-23 08:28:46 +03:00
resp = MakeRequest ( t , req , http . StatusBadRequest )
2022-10-12 07:22:43 +03:00
parsedError = new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "invalid_request" , string ( parsedError . ErrorCode ) )
2022-10-23 08:28:46 +03:00
assert . Equal ( t , "client_secret in request body inconsistent with Authorization header" , parsedError . ErrorDescription )
2019-03-11 05:54:59 +03:00
}
2019-04-12 10:50:21 +03:00
func TestRefreshTokenInvalidation ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-04-12 10:50:21 +03:00
req := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
2022-10-12 07:22:43 +03:00
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
2019-04-12 10:50:21 +03:00
} )
2022-03-23 07:54:07 +03:00
resp := MakeRequest ( t , req , http . StatusOK )
2019-04-12 10:50:21 +03:00
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
}
parsed := new ( response )
2021-03-02 00:08:10 +03:00
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsed ) )
2019-04-12 10:50:21 +03:00
// test without invalidation
setting . OAuth2 . InvalidateRefreshTokens = false
2022-10-23 08:28:46 +03:00
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "refresh_token" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
// omit secret
"redirect_uri" : "a" ,
"refresh_token" : parsed . RefreshToken ,
} )
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError := new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-23 08:28:46 +03:00
assert . Equal ( t , "invalid_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "invalid empty client secret" , parsedError . ErrorDescription )
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "refresh_token" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"refresh_token" : "UNEXPECTED" ,
} )
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError = new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-23 08:28:46 +03:00
assert . Equal ( t , "unauthorized_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "unable to parse refresh token" , parsedError . ErrorDescription )
req = NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
2019-04-12 10:50:21 +03:00
"grant_type" : "refresh_token" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"refresh_token" : parsed . RefreshToken ,
} )
2021-01-29 18:35:30 +03:00
2022-10-23 08:28:46 +03:00
bs , err := io . ReadAll ( req . Body )
2024-07-30 22:41:10 +03:00
require . NoError ( t , err )
2020-11-13 15:51:07 +03:00
2022-10-23 08:28:46 +03:00
req . Body = io . NopCloser ( bytes . NewReader ( bs ) )
MakeRequest ( t , req , http . StatusOK )
2020-11-13 15:51:07 +03:00
2022-10-23 08:28:46 +03:00
req . Body = io . NopCloser ( bytes . NewReader ( bs ) )
MakeRequest ( t , req , http . StatusOK )
2019-04-12 10:50:21 +03:00
// test with invalidation
setting . OAuth2 . InvalidateRefreshTokens = true
2022-10-23 08:28:46 +03:00
req . Body = io . NopCloser ( bytes . NewReader ( bs ) )
MakeRequest ( t , req , http . StatusOK )
2020-11-13 15:51:07 +03:00
2022-10-12 07:22:43 +03:00
// repeat request should fail
2022-10-23 08:28:46 +03:00
req . Body = io . NopCloser ( bytes . NewReader ( bs ) )
resp = MakeRequest ( t , req , http . StatusBadRequest )
parsedError = new ( auth . AccessTokenError )
2024-07-30 22:41:10 +03:00
require . NoError ( t , json . Unmarshal ( resp . Body . Bytes ( ) , parsedError ) )
2022-10-12 07:22:43 +03:00
assert . Equal ( t , "unauthorized_client" , string ( parsedError . ErrorCode ) )
assert . Equal ( t , "token was already used" , parsedError . ErrorDescription )
2019-04-12 10:50:21 +03:00
}
2023-06-27 16:00:15 +03:00
func TestSignInOAuthCallbackSignIn ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
//
// OAuth2 authentication source GitLab
//
gitlabName := "gitlab"
gitlab := addAuthSource ( t , authSourcePayloadGitLabCustom ( gitlabName ) )
//
// Create a user as if it had been previously been created by the GitLab
// authentication source.
//
userGitLabUserID := "5678"
userGitLab := & user_model . User {
Name : "gitlabuser" ,
Email : "gitlabuser@example.com" ,
Passwd : "gitlabuserpassword" ,
Type : user_model . UserTypeIndividual ,
LoginType : auth_model . OAuth2 ,
LoginSource : gitlab . ID ,
LoginName : userGitLabUserID ,
}
defer createUser ( context . Background ( ) , t , userGitLab ) ( )
//
// A request for user information sent to Goth will return a
// goth.User exactly matching the user created above.
//
defer mockCompleteUserAuth ( func ( res http . ResponseWriter , req * http . Request ) ( goth . User , error ) {
return goth . User {
Provider : gitlabName ,
UserID : userGitLabUserID ,
Email : userGitLab . Email ,
} , nil
} ) ( )
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/user/oauth2/%s/callback?code=XYZ&state=XYZ" , gitlabName ) )
resp := MakeRequest ( t , req , http . StatusSeeOther )
2024-07-30 22:41:10 +03:00
assert . Equal ( t , "/" , test . RedirectURL ( resp ) )
2023-06-27 16:00:15 +03:00
userAfterLogin := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : userGitLab . ID } )
assert . Greater ( t , userAfterLogin . LastLoginUnix , userGitLab . LastLoginUnix )
}
2023-11-19 14:50:05 +03:00
2024-06-10 11:04:00 +03:00
func TestSignInOAuthCallbackWithoutPKCEWhenUnsupported ( t * testing . T ) {
// https://codeberg.org/forgejo/forgejo/issues/4033
2024-04-18 10:55:08 +03:00
defer tests . PrepareTestEnv ( t ) ( )
// Setup authentication source
gitlabName := "gitlab"
gitlab := addAuthSource ( t , authSourcePayloadGitLabCustom ( gitlabName ) )
// Create a user as if it had been previously been created by the authentication source.
userGitLabUserID := "5678"
userGitLab := & user_model . User {
Name : "gitlabuser" ,
Email : "gitlabuser@example.com" ,
Passwd : "gitlabuserpassword" ,
Type : user_model . UserTypeIndividual ,
LoginType : auth_model . OAuth2 ,
LoginSource : gitlab . ID ,
LoginName : userGitLabUserID ,
}
defer createUser ( context . Background ( ) , t , userGitLab ) ( )
// initial redirection (to generate the code_challenge)
session := emptyTestSession ( t )
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/user/oauth2/%s" , gitlabName ) )
resp := session . MakeRequest ( t , req , http . StatusTemporaryRedirect )
dest , err := url . Parse ( resp . Header ( ) . Get ( "Location" ) )
2024-07-30 22:41:10 +03:00
require . NoError ( t , err )
2024-06-10 11:04:00 +03:00
assert . Empty ( t , dest . Query ( ) . Get ( "code_challenge_method" ) )
assert . Empty ( t , dest . Query ( ) . Get ( "code_challenge" ) )
2024-04-18 10:55:08 +03:00
// callback (to check the initial code_challenge)
defer mockCompleteUserAuth ( func ( res http . ResponseWriter , req * http . Request ) ( goth . User , error ) {
2024-06-10 11:04:00 +03:00
assert . Empty ( t , req . URL . Query ( ) . Get ( "code_verifier" ) )
2024-04-18 10:55:08 +03:00
return goth . User {
Provider : gitlabName ,
UserID : userGitLabUserID ,
Email : userGitLab . Email ,
} , nil
} ) ( )
req = NewRequest ( t , "GET" , fmt . Sprintf ( "/user/oauth2/%s/callback?code=XYZ&state=XYZ" , gitlabName ) )
resp = session . MakeRequest ( t , req , http . StatusSeeOther )
assert . Equal ( t , "/" , test . RedirectURL ( resp ) )
unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : userGitLab . ID } )
}
2024-06-10 11:04:00 +03:00
func TestSignInOAuthCallbackPKCE ( t * testing . T ) {
onGiteaRun ( t , func ( t * testing . T , u * url . URL ) {
// Setup authentication source
sourceName := "oidc"
authSource := addAuthSource ( t , authSourcePayloadOpenIDConnect ( sourceName , u . String ( ) ) )
// Create a user as if it had been previously been created by the authentication source.
userID := "5678"
user := & user_model . User {
Name : "oidc.user" ,
Email : "oidc.user@example.com" ,
Passwd : "oidc.userpassword" ,
Type : user_model . UserTypeIndividual ,
LoginType : auth_model . OAuth2 ,
LoginSource : authSource . ID ,
LoginName : userID ,
}
defer createUser ( context . Background ( ) , t , user ) ( )
// initial redirection (to generate the code_challenge)
session := emptyTestSession ( t )
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/user/oauth2/%s" , sourceName ) )
resp := session . MakeRequest ( t , req , http . StatusTemporaryRedirect )
dest , err := url . Parse ( resp . Header ( ) . Get ( "Location" ) )
2024-07-30 22:41:10 +03:00
require . NoError ( t , err )
2024-06-10 11:04:00 +03:00
assert . Equal ( t , "S256" , dest . Query ( ) . Get ( "code_challenge_method" ) )
codeChallenge := dest . Query ( ) . Get ( "code_challenge" )
assert . NotEmpty ( t , codeChallenge )
// callback (to check the initial code_challenge)
defer mockCompleteUserAuth ( func ( res http . ResponseWriter , req * http . Request ) ( goth . User , error ) {
codeVerifier := req . URL . Query ( ) . Get ( "code_verifier" )
assert . NotEmpty ( t , codeVerifier )
assert . Greater ( t , len ( codeVerifier ) , 40 , codeVerifier )
sha2 := sha256 . New ( )
io . WriteString ( sha2 , codeVerifier )
assert . Equal ( t , codeChallenge , base64 . RawURLEncoding . EncodeToString ( sha2 . Sum ( nil ) ) )
return goth . User {
Provider : sourceName ,
UserID : userID ,
Email : user . Email ,
} , nil
} ) ( )
req = NewRequest ( t , "GET" , fmt . Sprintf ( "/user/oauth2/%s/callback?code=XYZ&state=XYZ" , sourceName ) )
resp = session . MakeRequest ( t , req , http . StatusSeeOther )
assert . Equal ( t , "/" , test . RedirectURL ( resp ) )
unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : user . ID } )
} )
}
2024-03-07 12:50:04 +03:00
func TestSignInOAuthCallbackRedirectToEscaping ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
//
// OAuth2 authentication source GitLab
//
gitlabName := "gitlab"
gitlab := addAuthSource ( t , authSourcePayloadGitLabCustom ( gitlabName ) )
//
2024-06-05 18:50:15 +03:00
// Create a user as if it had been previously created by the GitLab
2024-03-07 12:50:04 +03:00
// authentication source.
//
userGitLabUserID := "5678"
userGitLab := & user_model . User {
Name : "gitlabuser" ,
Email : "gitlabuser@example.com" ,
Passwd : "gitlabuserpassword" ,
Type : user_model . UserTypeIndividual ,
LoginType : auth_model . OAuth2 ,
LoginSource : gitlab . ID ,
LoginName : userGitLabUserID ,
}
defer createUser ( context . Background ( ) , t , userGitLab ) ( )
//
// A request for user information sent to Goth will return a
// goth.User exactly matching the user created above.
//
defer mockCompleteUserAuth ( func ( res http . ResponseWriter , req * http . Request ) ( goth . User , error ) {
return goth . User {
Provider : gitlabName ,
UserID : userGitLabUserID ,
Email : userGitLab . Email ,
} , nil
} ) ( )
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/user/oauth2/%s/callback?code=XYZ&state=XYZ" , gitlabName ) )
req . AddCookie ( & http . Cookie {
Name : "redirect_to" ,
Value : "/login/oauth/authorize?redirect_uri=https%3A%2F%2Ftranslate.example.org" ,
Path : "/" ,
} )
resp := MakeRequest ( t , req , http . StatusSeeOther )
hasNewSessionCookie := false
sessionCookieName := setting . SessionConfig . CookieName
for _ , c := range resp . Result ( ) . Cookies ( ) {
if c . Name == sessionCookieName {
hasNewSessionCookie = true
break
}
t . Log ( "Got cookie" , c . Name )
}
assert . True ( t , hasNewSessionCookie , "Session cookie %q is missing" , sessionCookieName )
assert . Equal ( t , "/login/oauth/authorize?redirect_uri=https://translate.example.org" , test . RedirectURL ( resp ) )
}
2023-11-19 14:50:05 +03:00
func TestSignUpViaOAuthWithMissingFields ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
// enable auto-creation of accounts via OAuth2
enableAutoRegistration := setting . OAuth2Client . EnableAutoRegistration
setting . OAuth2Client . EnableAutoRegistration = true
defer func ( ) {
setting . OAuth2Client . EnableAutoRegistration = enableAutoRegistration
} ( )
// OAuth2 authentication source GitLab
gitlabName := "gitlab"
addAuthSource ( t , authSourcePayloadGitLabCustom ( gitlabName ) )
userGitLabUserID := "5678"
// The Goth User returned by the oauth2 integration is missing
// an email address, so we won't be able to automatically create a local account for it.
defer mockCompleteUserAuth ( func ( res http . ResponseWriter , req * http . Request ) ( goth . User , error ) {
return goth . User {
Provider : gitlabName ,
UserID : userGitLabUserID ,
} , nil
} ) ( )
req := NewRequest ( t , "GET" , fmt . Sprintf ( "/user/oauth2/%s/callback?code=XYZ&state=XYZ" , gitlabName ) )
resp := MakeRequest ( t , req , http . StatusSeeOther )
2024-07-30 22:41:10 +03:00
assert . Equal ( t , "/user/link_account" , test . RedirectURL ( resp ) )
2023-11-19 14:50:05 +03:00
}
2024-06-05 13:33:10 +03:00
func TestOAuth_GrantApplicationOAuth ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
req := NewRequest ( t , "GET" , "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate" )
ctx := loginUser ( t , "user4" )
resp := ctx . MakeRequest ( t , req , http . StatusOK )
htmlDoc := NewHTMLParser ( t , resp . Body )
htmlDoc . AssertElement ( t , "#authorize-app" , true )
req = NewRequestWithValues ( t , "POST" , "/login/oauth/grant" , map [ string ] string {
"_csrf" : htmlDoc . GetCSRF ( ) ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"redirect_uri" : "a" ,
"state" : "thestate" ,
"granted" : "false" ,
} )
resp = ctx . MakeRequest ( t , req , http . StatusSeeOther )
assert . Contains ( t , test . RedirectURL ( resp ) , "error=access_denied&error_description=the+request+is+denied" )
}
2024-08-08 09:32:14 +03:00
func TestOAuthIntrospection ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
req := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
"grant_type" : "authorization_code" ,
"client_id" : "da7da3ba-9a13-4167-856f-3899de0b0138" ,
"client_secret" : "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=" ,
"redirect_uri" : "a" ,
"code" : "authcode" ,
"code_verifier" : "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt" ,
} )
resp := MakeRequest ( t , req , http . StatusOK )
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
}
parsed := new ( response )
DecodeJSON ( t , resp , parsed )
assert . Greater ( t , len ( parsed . AccessToken ) , 10 )
assert . Greater ( t , len ( parsed . RefreshToken ) , 10 )
type introspectResponse struct {
Active bool ` json:"active" `
Scope string ` json:"scope,omitempty" `
Username string ` json:"username" `
}
// successful request with a valid client_id/client_secret and a valid token
t . Run ( "successful request with valid token" , func ( t * testing . T ) {
req := NewRequestWithValues ( t , "POST" , "/login/oauth/introspect" , map [ string ] string {
"token" : parsed . AccessToken ,
} )
req . Header . Add ( "Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9" )
resp := MakeRequest ( t , req , http . StatusOK )
introspectParsed := new ( introspectResponse )
DecodeJSON ( t , resp , introspectParsed )
assert . True ( t , introspectParsed . Active )
assert . Equal ( t , "user1" , introspectParsed . Username )
} )
// successful request with a valid client_id/client_secret, but an invalid token
t . Run ( "successful request with invalid token" , func ( t * testing . T ) {
req := NewRequestWithValues ( t , "POST" , "/login/oauth/introspect" , map [ string ] string {
"token" : "xyzzy" ,
} )
req . Header . Add ( "Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9" )
resp := MakeRequest ( t , req , http . StatusOK )
introspectParsed := new ( introspectResponse )
DecodeJSON ( t , resp , introspectParsed )
assert . False ( t , introspectParsed . Active )
} )
// unsuccessful request with an invalid client_id/client_secret
t . Run ( "unsuccessful request due to invalid basic auth" , func ( t * testing . T ) {
req := NewRequestWithValues ( t , "POST" , "/login/oauth/introspect" , map [ string ] string {
"token" : parsed . AccessToken ,
} )
req . Header . Add ( "Authorization" , "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpK" )
resp := MakeRequest ( t , req , http . StatusUnauthorized )
assert . Contains ( t , resp . Body . String ( ) , "no valid authorization" )
} )
}
2024-08-09 12:14:40 +03:00
2024-09-22 13:57:03 +03:00
func requireCookieCSRF ( t * testing . T , resp http . ResponseWriter ) string {
for _ , c := range resp . ( * httptest . ResponseRecorder ) . Result ( ) . Cookies ( ) {
if c . Name == "_csrf" {
return c . Value
}
}
require . True ( t , false , "_csrf not found in cookies" )
return ""
}
2024-08-09 12:14:40 +03:00
func TestOAuth_GrantScopesReadUser ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 2 } )
appBody := api . CreateOAuth2ApplicationOptions {
Name : "oauth-provider-scopes-test" ,
RedirectURIs : [ ] string {
"a" ,
} ,
ConfidentialClient : true ,
}
req := NewRequestWithJSON ( t , "POST" , "/api/v1/user/applications/oauth2" , & appBody ) .
AddBasicAuth ( user . Name )
resp := MakeRequest ( t , req , http . StatusCreated )
var app * api . OAuth2Application
DecodeJSON ( t , resp , & app )
grant := & auth_model . OAuth2Grant {
ApplicationID : app . ID ,
UserID : user . ID ,
Scope : "openid profile email read:user" ,
}
err := db . Insert ( db . DefaultContext , grant )
require . NoError ( t , err )
assert . Contains ( t , grant . Scope , "openid profile email read:user" )
ctx := loginUserWithPasswordRemember ( t , user . Name , "password" , true )
authorizeURL := fmt . Sprintf ( "/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate" , app . ClientID )
authorizeReq := NewRequest ( t , "GET" , authorizeURL )
authorizeResp := ctx . MakeRequest ( t , authorizeReq , http . StatusSeeOther )
authcode := strings . Split ( strings . Split ( authorizeResp . Body . String ( ) , "?code=" ) [ 1 ] , "&" ) [ 0 ]
grantReq := NewRequestWithValues ( t , "POST" , "/login/oauth/grant" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"client_id" : app . ClientID ,
"redirect_uri" : "a" ,
"state" : "thestate" ,
"granted" : "true" ,
} )
2024-09-22 13:57:03 +03:00
grantResp := ctx . MakeRequest ( t , grantReq , http . StatusBadRequest )
assert . NotContains ( t , grantResp . Body . String ( ) , forgejo_context . CsrfErrorString )
2024-08-09 12:14:40 +03:00
accessTokenReq := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"grant_type" : "authorization_code" ,
"client_id" : app . ClientID ,
"client_secret" : app . ClientSecret ,
"redirect_uri" : "a" ,
"code" : authcode ,
} )
accessTokenResp := ctx . MakeRequest ( t , accessTokenReq , 200 )
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
}
parsed := new ( response )
require . NoError ( t , json . Unmarshal ( accessTokenResp . Body . Bytes ( ) , parsed ) )
userReq := NewRequest ( t , "GET" , "/api/v1/user" )
userReq . SetHeader ( "Authorization" , "Bearer " + parsed . AccessToken )
userResp := MakeRequest ( t , userReq , http . StatusOK )
// assert.Contains(t, string(userResp.Body.Bytes()), "blah")
type userResponse struct {
Login string ` json:"login" `
Email string ` json:"email" `
}
userParsed := new ( userResponse )
require . NoError ( t , json . Unmarshal ( userResp . Body . Bytes ( ) , userParsed ) )
assert . Contains ( t , userParsed . Email , "user2@example.com" )
}
func TestOAuth_GrantScopesFailReadRepository ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 2 } )
appBody := api . CreateOAuth2ApplicationOptions {
Name : "oauth-provider-scopes-test" ,
RedirectURIs : [ ] string {
"a" ,
} ,
ConfidentialClient : true ,
}
req := NewRequestWithJSON ( t , "POST" , "/api/v1/user/applications/oauth2" , & appBody ) .
AddBasicAuth ( user . Name )
resp := MakeRequest ( t , req , http . StatusCreated )
var app * api . OAuth2Application
DecodeJSON ( t , resp , & app )
grant := & auth_model . OAuth2Grant {
ApplicationID : app . ID ,
UserID : user . ID ,
Scope : "openid profile email read:user" ,
}
err := db . Insert ( db . DefaultContext , grant )
require . NoError ( t , err )
assert . Contains ( t , grant . Scope , "openid profile email read:user" )
ctx := loginUserWithPasswordRemember ( t , user . Name , "password" , true )
authorizeURL := fmt . Sprintf ( "/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate" , app . ClientID )
authorizeReq := NewRequest ( t , "GET" , authorizeURL )
authorizeResp := ctx . MakeRequest ( t , authorizeReq , http . StatusSeeOther )
authcode := strings . Split ( strings . Split ( authorizeResp . Body . String ( ) , "?code=" ) [ 1 ] , "&" ) [ 0 ]
grantReq := NewRequestWithValues ( t , "POST" , "/login/oauth/grant" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"client_id" : app . ClientID ,
"redirect_uri" : "a" ,
"state" : "thestate" ,
"granted" : "true" ,
} )
2024-09-22 13:57:03 +03:00
grantResp := ctx . MakeRequest ( t , grantReq , http . StatusBadRequest )
assert . NotContains ( t , grantResp . Body . String ( ) , forgejo_context . CsrfErrorString )
2024-08-09 12:14:40 +03:00
accessTokenReq := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"grant_type" : "authorization_code" ,
"client_id" : app . ClientID ,
"client_secret" : app . ClientSecret ,
"redirect_uri" : "a" ,
"code" : authcode ,
} )
accessTokenResp := ctx . MakeRequest ( t , accessTokenReq , http . StatusOK )
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
}
parsed := new ( response )
require . NoError ( t , json . Unmarshal ( accessTokenResp . Body . Bytes ( ) , parsed ) )
userReq := NewRequest ( t , "GET" , "/api/v1/users/user2/repos" )
userReq . SetHeader ( "Authorization" , "Bearer " + parsed . AccessToken )
userResp := MakeRequest ( t , userReq , http . StatusForbidden )
type userResponse struct {
Message string ` json:"message" `
}
userParsed := new ( userResponse )
require . NoError ( t , json . Unmarshal ( userResp . Body . Bytes ( ) , userParsed ) )
assert . Contains ( t , userParsed . Message , "token does not have at least one of required scope(s): [read:repository]" )
}
func TestOAuth_GrantScopesReadRepository ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 2 } )
appBody := api . CreateOAuth2ApplicationOptions {
Name : "oauth-provider-scopes-test" ,
RedirectURIs : [ ] string {
"a" ,
} ,
ConfidentialClient : true ,
}
req := NewRequestWithJSON ( t , "POST" , "/api/v1/user/applications/oauth2" , & appBody ) .
AddBasicAuth ( user . Name )
resp := MakeRequest ( t , req , http . StatusCreated )
var app * api . OAuth2Application
DecodeJSON ( t , resp , & app )
grant := & auth_model . OAuth2Grant {
ApplicationID : app . ID ,
UserID : user . ID ,
Scope : "openid profile email read:user read:repository" ,
}
err := db . Insert ( db . DefaultContext , grant )
require . NoError ( t , err )
assert . Contains ( t , grant . Scope , "openid profile email read:user read:repository" )
ctx := loginUserWithPasswordRemember ( t , user . Name , "password" , true )
authorizeURL := fmt . Sprintf ( "/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate" , app . ClientID )
authorizeReq := NewRequest ( t , "GET" , authorizeURL )
authorizeResp := ctx . MakeRequest ( t , authorizeReq , http . StatusSeeOther )
authcode := strings . Split ( strings . Split ( authorizeResp . Body . String ( ) , "?code=" ) [ 1 ] , "&" ) [ 0 ]
grantReq := NewRequestWithValues ( t , "POST" , "/login/oauth/grant" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"client_id" : app . ClientID ,
"redirect_uri" : "a" ,
"state" : "thestate" ,
"granted" : "true" ,
} )
2024-09-22 13:57:03 +03:00
grantResp := ctx . MakeRequest ( t , grantReq , http . StatusBadRequest )
assert . NotContains ( t , grantResp . Body . String ( ) , forgejo_context . CsrfErrorString )
2024-08-09 12:14:40 +03:00
accessTokenReq := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"grant_type" : "authorization_code" ,
"client_id" : app . ClientID ,
"client_secret" : app . ClientSecret ,
"redirect_uri" : "a" ,
"code" : authcode ,
} )
accessTokenResp := ctx . MakeRequest ( t , accessTokenReq , http . StatusOK )
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
}
parsed := new ( response )
require . NoError ( t , json . Unmarshal ( accessTokenResp . Body . Bytes ( ) , parsed ) )
userReq := NewRequest ( t , "GET" , "/api/v1/users/user2/repos" )
userReq . SetHeader ( "Authorization" , "Bearer " + parsed . AccessToken )
userResp := MakeRequest ( t , userReq , http . StatusOK )
type repos struct {
FullRepoName string ` json:"full_name" `
}
var userResponse [ ] * repos
require . NoError ( t , json . Unmarshal ( userResp . Body . Bytes ( ) , & userResponse ) )
if assert . NotEmpty ( t , userResponse ) {
assert . Contains ( t , userResponse [ 0 ] . FullRepoName , "user2/repo1" )
}
}
func TestOAuth_GrantScopesReadPrivateGroups ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
// setting.OAuth2.EnableAdditionalGrantScopes = true
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { Name : "user5" } )
appBody := api . CreateOAuth2ApplicationOptions {
Name : "oauth-provider-scopes-test" ,
RedirectURIs : [ ] string {
"a" ,
} ,
ConfidentialClient : true ,
}
appReq := NewRequestWithJSON ( t , "POST" , "/api/v1/user/applications/oauth2" , & appBody ) .
AddBasicAuth ( user . Name )
appResp := MakeRequest ( t , appReq , http . StatusCreated )
var app * api . OAuth2Application
DecodeJSON ( t , appResp , & app )
grant := & auth_model . OAuth2Grant {
ApplicationID : app . ID ,
UserID : user . ID ,
Scope : "openid profile email groups read:user" ,
}
err := db . Insert ( db . DefaultContext , grant )
require . NoError ( t , err )
assert . Contains ( t , grant . Scope , "openid profile email groups read:user" )
ctx := loginUserWithPasswordRemember ( t , user . Name , "password" , true )
authorizeURL := fmt . Sprintf ( "/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate" , app . ClientID )
authorizeReq := NewRequest ( t , "GET" , authorizeURL )
authorizeResp := ctx . MakeRequest ( t , authorizeReq , http . StatusSeeOther )
authcode := strings . Split ( strings . Split ( authorizeResp . Body . String ( ) , "?code=" ) [ 1 ] , "&" ) [ 0 ]
grantReq := NewRequestWithValues ( t , "POST" , "/login/oauth/grant" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"client_id" : app . ClientID ,
"redirect_uri" : "a" ,
"state" : "thestate" ,
"granted" : "true" ,
} )
2024-09-22 13:57:03 +03:00
grantResp := ctx . MakeRequest ( t , grantReq , http . StatusBadRequest )
assert . NotContains ( t , grantResp . Body . String ( ) , forgejo_context . CsrfErrorString )
2024-08-09 12:14:40 +03:00
accessTokenReq := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"grant_type" : "authorization_code" ,
"client_id" : app . ClientID ,
"client_secret" : app . ClientSecret ,
"redirect_uri" : "a" ,
"code" : authcode ,
} )
accessTokenResp := ctx . MakeRequest ( t , accessTokenReq , http . StatusOK )
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
IDToken string ` json:"id_token,omitempty" `
}
parsed := new ( response )
require . NoError ( t , json . Unmarshal ( accessTokenResp . Body . Bytes ( ) , parsed ) )
parts := strings . Split ( parsed . IDToken , "." )
payload , _ := base64 . RawURLEncoding . DecodeString ( parts [ 1 ] )
type IDTokenClaims struct {
Groups [ ] string ` json:"groups" `
}
claims := new ( IDTokenClaims )
require . NoError ( t , json . Unmarshal ( payload , claims ) )
for _ , group := range [ ] string { "limited_org36" , "limited_org36:team20writepackage" , "org6" , "org6:owners" , "org7" , "org7:owners" , "privated_org" , "privated_org:team14writeauth" } {
assert . Contains ( t , claims . Groups , group )
}
}
func TestOAuth_GrantScopesReadOnlyPublicGroups ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
setting . OAuth2 . EnableAdditionalGrantScopes = true
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { Name : "user5" } )
appBody := api . CreateOAuth2ApplicationOptions {
Name : "oauth-provider-scopes-test" ,
RedirectURIs : [ ] string {
"a" ,
} ,
ConfidentialClient : true ,
}
appReq := NewRequestWithJSON ( t , "POST" , "/api/v1/user/applications/oauth2" , & appBody ) .
AddBasicAuth ( user . Name )
appResp := MakeRequest ( t , appReq , http . StatusCreated )
var app * api . OAuth2Application
DecodeJSON ( t , appResp , & app )
grant := & auth_model . OAuth2Grant {
ApplicationID : app . ID ,
UserID : user . ID ,
Scope : "openid profile email groups read:user" ,
}
err := db . Insert ( db . DefaultContext , grant )
require . NoError ( t , err )
assert . Contains ( t , grant . Scope , "openid profile email groups read:user" )
ctx := loginUserWithPasswordRemember ( t , user . Name , "password" , true )
authorizeURL := fmt . Sprintf ( "/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate" , app . ClientID )
authorizeReq := NewRequest ( t , "GET" , authorizeURL )
authorizeResp := ctx . MakeRequest ( t , authorizeReq , http . StatusSeeOther )
authcode := strings . Split ( strings . Split ( authorizeResp . Body . String ( ) , "?code=" ) [ 1 ] , "&" ) [ 0 ]
grantReq := NewRequestWithValues ( t , "POST" , "/login/oauth/grant" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"client_id" : app . ClientID ,
"redirect_uri" : "a" ,
"state" : "thestate" ,
"granted" : "true" ,
} )
2024-09-22 13:57:03 +03:00
grantResp := ctx . MakeRequest ( t , grantReq , http . StatusBadRequest )
assert . NotContains ( t , grantResp . Body . String ( ) , forgejo_context . CsrfErrorString )
2024-08-09 12:14:40 +03:00
accessTokenReq := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"grant_type" : "authorization_code" ,
"client_id" : app . ClientID ,
"client_secret" : app . ClientSecret ,
"redirect_uri" : "a" ,
"code" : authcode ,
} )
accessTokenResp := ctx . MakeRequest ( t , accessTokenReq , http . StatusOK )
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
IDToken string ` json:"id_token,omitempty" `
}
parsed := new ( response )
require . NoError ( t , json . Unmarshal ( accessTokenResp . Body . Bytes ( ) , parsed ) )
parts := strings . Split ( parsed . IDToken , "." )
payload , _ := base64 . RawURLEncoding . DecodeString ( parts [ 1 ] )
type IDTokenClaims struct {
Groups [ ] string ` json:"groups" `
}
claims := new ( IDTokenClaims )
require . NoError ( t , json . Unmarshal ( payload , claims ) )
for _ , privOrg := range [ ] string { "org7" , "org7:owners" , "privated_org" , "privated_org:team14writeauth" } {
assert . NotContains ( t , claims . Groups , privOrg )
}
userReq := NewRequest ( t , "GET" , "/login/oauth/userinfo" )
userReq . SetHeader ( "Authorization" , "Bearer " + parsed . AccessToken )
userResp := MakeRequest ( t , userReq , http . StatusOK )
type userinfo struct {
Groups [ ] string ` json:"groups" `
}
parsedUserInfo := new ( userinfo )
require . NoError ( t , json . Unmarshal ( userResp . Body . Bytes ( ) , parsedUserInfo ) )
for _ , privOrg := range [ ] string { "org7" , "org7:owners" , "privated_org" , "privated_org:team14writeauth" } {
assert . NotContains ( t , parsedUserInfo . Groups , privOrg )
}
}
func TestOAuth_GrantScopesReadPublicGroupsWithTheReadScope ( t * testing . T ) {
defer tests . PrepareTestEnv ( t ) ( )
setting . OAuth2 . EnableAdditionalGrantScopes = true
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { Name : "user5" } )
appBody := api . CreateOAuth2ApplicationOptions {
Name : "oauth-provider-scopes-test" ,
RedirectURIs : [ ] string {
"a" ,
} ,
ConfidentialClient : true ,
}
appReq := NewRequestWithJSON ( t , "POST" , "/api/v1/user/applications/oauth2" , & appBody ) .
AddBasicAuth ( user . Name )
appResp := MakeRequest ( t , appReq , http . StatusCreated )
var app * api . OAuth2Application
DecodeJSON ( t , appResp , & app )
grant := & auth_model . OAuth2Grant {
ApplicationID : app . ID ,
UserID : user . ID ,
Scope : "openid profile email groups read:user read:organization" ,
}
err := db . Insert ( db . DefaultContext , grant )
require . NoError ( t , err )
assert . Contains ( t , grant . Scope , "openid profile email groups read:user read:organization" )
ctx := loginUserWithPasswordRemember ( t , user . Name , "password" , true )
authorizeURL := fmt . Sprintf ( "/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate" , app . ClientID )
authorizeReq := NewRequest ( t , "GET" , authorizeURL )
authorizeResp := ctx . MakeRequest ( t , authorizeReq , http . StatusSeeOther )
authcode := strings . Split ( strings . Split ( authorizeResp . Body . String ( ) , "?code=" ) [ 1 ] , "&" ) [ 0 ]
grantReq := NewRequestWithValues ( t , "POST" , "/login/oauth/grant" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"client_id" : app . ClientID ,
"redirect_uri" : "a" ,
"state" : "thestate" ,
"granted" : "true" ,
} )
2024-09-22 13:57:03 +03:00
grantResp := ctx . MakeRequest ( t , grantReq , http . StatusBadRequest )
assert . NotContains ( t , grantResp . Body . String ( ) , forgejo_context . CsrfErrorString )
2024-08-09 12:14:40 +03:00
accessTokenReq := NewRequestWithValues ( t , "POST" , "/login/oauth/access_token" , map [ string ] string {
2024-09-22 13:57:03 +03:00
"_csrf" : requireCookieCSRF ( t , authorizeResp ) ,
2024-08-09 12:14:40 +03:00
"grant_type" : "authorization_code" ,
"client_id" : app . ClientID ,
"client_secret" : app . ClientSecret ,
"redirect_uri" : "a" ,
"code" : authcode ,
} )
accessTokenResp := ctx . MakeRequest ( t , accessTokenReq , http . StatusOK )
type response struct {
AccessToken string ` json:"access_token" `
TokenType string ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
IDToken string ` json:"id_token,omitempty" `
}
parsed := new ( response )
require . NoError ( t , json . Unmarshal ( accessTokenResp . Body . Bytes ( ) , parsed ) )
parts := strings . Split ( parsed . IDToken , "." )
payload , _ := base64 . RawURLEncoding . DecodeString ( parts [ 1 ] )
type IDTokenClaims struct {
Groups [ ] string ` json:"groups" `
}
claims := new ( IDTokenClaims )
require . NoError ( t , json . Unmarshal ( payload , claims ) )
for _ , privOrg := range [ ] string { "org7" , "org7:owners" , "privated_org" , "privated_org:team14writeauth" } {
assert . Contains ( t , claims . Groups , privOrg )
}
userReq := NewRequest ( t , "GET" , "/login/oauth/userinfo" )
userReq . SetHeader ( "Authorization" , "Bearer " + parsed . AccessToken )
userResp := MakeRequest ( t , userReq , http . StatusOK )
type userinfo struct {
Groups [ ] string ` json:"groups" `
}
parsedUserInfo := new ( userinfo )
require . NoError ( t , json . Unmarshal ( userResp . Body . Bytes ( ) , parsedUserInfo ) )
for _ , privOrg := range [ ] string { "org7" , "org7:owners" , "privated_org" , "privated_org:team14writeauth" } {
assert . Contains ( t , parsedUserInfo . Groups , privOrg )
}
}