123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161 |
- package httpd
- import (
- "context"
- "encoding/json"
- "fmt"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "reflect"
- "runtime"
- "testing"
- "time"
- "unsafe"
- "github.com/coreos/go-oidc/v3/oidc"
- "github.com/go-chi/jwtauth/v5"
- "github.com/rs/xid"
- "github.com/sftpgo/sdk"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "golang.org/x/oauth2"
- "github.com/drakkan/sftpgo/v2/common"
- "github.com/drakkan/sftpgo/v2/dataprovider"
- "github.com/drakkan/sftpgo/v2/kms"
- "github.com/drakkan/sftpgo/v2/util"
- "github.com/drakkan/sftpgo/v2/vfs"
- )
- const (
- oidcMockAddr = "127.0.0.1:11111"
- )
- type mockTokenSource struct {
- token *oauth2.Token
- err error
- }
- func (t *mockTokenSource) Token() (*oauth2.Token, error) {
- return t.token, t.err
- }
- type mockOAuth2Config struct {
- tokenSource *mockTokenSource
- authCodeURL string
- token *oauth2.Token
- err error
- }
- func (c *mockOAuth2Config) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
- return c.authCodeURL
- }
- func (c *mockOAuth2Config) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
- return c.token, c.err
- }
- func (c *mockOAuth2Config) TokenSource(ctx context.Context, t *oauth2.Token) oauth2.TokenSource {
- return c.tokenSource
- }
- type mockOIDCVerifier struct {
- token *oidc.IDToken
- err error
- }
- func (v *mockOIDCVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error) {
- return v.token, v.err
- }
- // hack because the field is unexported
- func setIDTokenClaims(idToken *oidc.IDToken, claims []byte) {
- pointerVal := reflect.ValueOf(idToken)
- val := reflect.Indirect(pointerVal)
- member := val.FieldByName("claims")
- ptr := unsafe.Pointer(member.UnsafeAddr())
- realPtr := (*[]byte)(ptr)
- *realPtr = claims
- }
- func TestOIDCInitialization(t *testing.T) {
- config := OIDC{}
- err := config.initialize()
- assert.NoError(t, err)
- config = OIDC{
- ClientID: "sftpgo-client",
- ClientSecret: "jRsmE0SWnuZjP7djBqNq0mrf8QN77j2c",
- ConfigURL: fmt.Sprintf("http://%v/", oidcMockAddr),
- RedirectBaseURL: "http://127.0.0.1:8081/",
- UsernameField: "preferred_username",
- RoleField: "sftpgo_role",
- }
- err = config.initialize()
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "oidc: unable to initialize provider")
- }
- config.ConfigURL = fmt.Sprintf("http://%v/auth/realms/sftpgo", oidcMockAddr)
- err = config.initialize()
- assert.NoError(t, err)
- assert.Equal(t, "http://127.0.0.1:8081"+webOIDCRedirectPath, config.getRedirectURL())
- }
- func TestOIDCLoginLogout(t *testing.T) {
- oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
- require.True(t, ok)
- server := getTestOIDCServer()
- err := server.binding.OIDC.initialize()
- assert.NoError(t, err)
- server.initializeRouter()
- rr := httptest.NewRecorder()
- r, err := http.NewRequest(http.MethodGet, webOIDCRedirectPath, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusBadRequest, rr.Code)
- assert.Contains(t, rr.Body.String(), "Authentication state did not match")
- expiredAuthReq := oidcPendingAuth{
- State: xid.New().String(),
- Nonce: xid.New().String(),
- Audience: tokenAudienceWebClient,
- IssuedAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-10 * time.Minute)),
- }
- oidcMgr.addPendingAuth(expiredAuthReq)
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+expiredAuthReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusBadRequest, rr.Code)
- assert.Contains(t, rr.Body.String(), "Authentication state did not match")
- oidcMgr.removePendingAuth(expiredAuthReq.State)
- server.binding.OIDC.oauth2Config = &mockOAuth2Config{
- tokenSource: &mockTokenSource{},
- authCodeURL: webOIDCRedirectPath,
- err: common.ErrGenericFailure,
- }
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: common.ErrGenericFailure,
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webAdminOIDCLoginPath, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webOIDCRedirectPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 1)
- var state string
- for k := range oidcMgr.pendingAuths {
- state = k
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+state, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webAdminLoginPath, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusOK, rr.Code)
- // now the same for the web client
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webClientOIDCLoginPath, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webOIDCRedirectPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 1)
- for k := range oidcMgr.pendingAuths {
- state = k
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+state, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webClientLoginPath, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusOK, rr.Code)
- // now return an OAuth2 token without the id_token
- server.binding.OIDC.oauth2Config = &mockOAuth2Config{
- tokenSource: &mockTokenSource{},
- authCodeURL: webOIDCRedirectPath,
- token: &oauth2.Token{
- AccessToken: "123",
- Expiry: time.Now().Add(5 * time.Minute),
- },
- err: nil,
- }
- authReq := newOIDCPendingAuth(tokenAudienceWebClient)
- oidcMgr.addPendingAuth(authReq)
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- // now fail to verify the id token
- token := &oauth2.Token{
- AccessToken: "123",
- Expiry: time.Now().Add(5 * time.Minute),
- }
- token = token.WithExtra(map[string]any{
- "id_token": "id_token_val",
- })
- server.binding.OIDC.oauth2Config = &mockOAuth2Config{
- tokenSource: &mockTokenSource{},
- authCodeURL: webOIDCRedirectPath,
- token: token,
- err: nil,
- }
- authReq = newOIDCPendingAuth(tokenAudienceWebClient)
- oidcMgr.addPendingAuth(authReq)
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- // id token nonce does not match
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: &oidc.IDToken{},
- }
- authReq = newOIDCPendingAuth(tokenAudienceWebClient)
- oidcMgr.addPendingAuth(authReq)
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- // null id token claims
- authReq = newOIDCPendingAuth(tokenAudienceWebClient)
- oidcMgr.addPendingAuth(authReq)
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: &oidc.IDToken{
- Nonce: authReq.Nonce,
- },
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- // invalid id token claims (no username)
- authReq = newOIDCPendingAuth(tokenAudienceWebClient)
- oidcMgr.addPendingAuth(authReq)
- idToken := &oidc.IDToken{
- Nonce: authReq.Nonce,
- Expiry: time.Now().Add(5 * time.Minute),
- }
- setIDTokenClaims(idToken, []byte(`{"aud": "my_client_id"}`))
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: idToken,
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- // invalid audience
- authReq = newOIDCPendingAuth(tokenAudienceWebClient)
- oidcMgr.addPendingAuth(authReq)
- idToken = &oidc.IDToken{
- Nonce: authReq.Nonce,
- Expiry: time.Now().Add(5 * time.Minute),
- }
- setIDTokenClaims(idToken, []byte(`{"preferred_username":"test","sftpgo_role":"admin"}`))
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: idToken,
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- // invalid audience
- authReq = newOIDCPendingAuth(tokenAudienceWebAdmin)
- oidcMgr.addPendingAuth(authReq)
- idToken = &oidc.IDToken{
- Nonce: authReq.Nonce,
- Expiry: time.Now().Add(5 * time.Minute),
- }
- setIDTokenClaims(idToken, []byte(`{"preferred_username":"test"}`))
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: idToken,
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- // mapped user not found
- authReq = newOIDCPendingAuth(tokenAudienceWebAdmin)
- oidcMgr.addPendingAuth(authReq)
- idToken = &oidc.IDToken{
- Nonce: authReq.Nonce,
- Expiry: time.Now().Add(5 * time.Minute),
- }
- setIDTokenClaims(idToken, []byte(`{"preferred_username":"test","sftpgo_role":"admin"}`))
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: idToken,
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- // admin login ok
- authReq = newOIDCPendingAuth(tokenAudienceWebAdmin)
- oidcMgr.addPendingAuth(authReq)
- idToken = &oidc.IDToken{
- Nonce: authReq.Nonce,
- Expiry: time.Now().Add(5 * time.Minute),
- }
- setIDTokenClaims(idToken, []byte(`{"preferred_username":"admin","sftpgo_role":"admin","sid":"sid123"}`))
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: idToken,
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webUsersPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- require.Len(t, oidcMgr.tokens, 1)
- // admin profile is not available
- var tokenCookie string
- for k := range oidcMgr.tokens {
- tokenCookie = k
- }
- oidcToken, err := oidcMgr.getToken(tokenCookie)
- assert.NoError(t, err)
- assert.Equal(t, "sid123", oidcToken.SessionID)
- assert.True(t, oidcToken.isAdmin())
- assert.False(t, oidcToken.isExpired())
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webAdminProfilePath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusForbidden, rr.Code)
- // the admin can access the allowed pages
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webUsersPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusOK, rr.Code)
- // try with an invalid cookie
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webUsersPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, xid.New().String()))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
- // Web Client is not available with an admin token
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- // logout the admin user
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webLogoutPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- require.Len(t, oidcMgr.tokens, 0)
- // now login and logout a user
- username := "test_oidc_user"
- user := dataprovider.User{
- BaseUser: sdk.BaseUser{
- Username: username,
- Password: "pwd",
- HomeDir: filepath.Join(os.TempDir(), username),
- Status: 1,
- Permissions: map[string][]string{
- "/": {dataprovider.PermAny},
- },
- },
- Filters: dataprovider.UserFilters{
- BaseUserFilters: sdk.BaseUserFilters{
- WebClient: []string{sdk.WebClientSharesDisabled},
- },
- },
- }
- err = dataprovider.AddUser(&user, "", "")
- assert.NoError(t, err)
- authReq = newOIDCPendingAuth(tokenAudienceWebClient)
- oidcMgr.addPendingAuth(authReq)
- idToken = &oidc.IDToken{
- Nonce: authReq.Nonce,
- Expiry: time.Now().Add(5 * time.Minute),
- }
- setIDTokenClaims(idToken, []byte(`{"preferred_username":"test_oidc_user"}`))
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: idToken,
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientFilesPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- require.Len(t, oidcMgr.tokens, 1)
- // user profile is not available
- for k := range oidcMgr.tokens {
- tokenCookie = k
- }
- oidcToken, err = oidcMgr.getToken(tokenCookie)
- assert.NoError(t, err)
- assert.Empty(t, oidcToken.SessionID)
- assert.False(t, oidcToken.isAdmin())
- assert.False(t, oidcToken.isExpired())
- if assert.Len(t, oidcToken.Permissions, 1) {
- assert.Equal(t, sdk.WebClientSharesDisabled, oidcToken.Permissions[0])
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
- assert.NoError(t, err)
- r.RequestURI = webClientProfilePath
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusForbidden, rr.Code)
- // the user can access the allowed pages
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusOK, rr.Code)
- // try with an invalid cookie
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, xid.New().String()))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- // Web Admin is not available with a client cookie
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webUsersPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
- // logout the user
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, tokenCookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- require.Len(t, oidcMgr.pendingAuths, 0)
- require.Len(t, oidcMgr.tokens, 0)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- err = dataprovider.DeleteUser(username, "", "")
- assert.NoError(t, err)
- }
- func TestOIDCRefreshToken(t *testing.T) {
- oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
- require.True(t, ok)
- token := oidcToken{
- Cookie: xid.New().String(),
- AccessToken: xid.New().String(),
- TokenType: "Bearer",
- ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-1 * time.Minute)),
- Nonce: xid.New().String(),
- }
- config := mockOAuth2Config{
- tokenSource: &mockTokenSource{
- err: common.ErrGenericFailure,
- },
- }
- verifier := mockOIDCVerifier{
- err: common.ErrGenericFailure,
- }
- err := token.refresh(&config, &verifier)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "refresh token not set")
- }
- token.RefreshToken = xid.New().String()
- err = token.refresh(&config, &verifier)
- assert.ErrorIs(t, err, common.ErrGenericFailure)
- newToken := &oauth2.Token{
- AccessToken: xid.New().String(),
- RefreshToken: xid.New().String(),
- Expiry: time.Now().Add(5 * time.Minute),
- }
- config = mockOAuth2Config{
- tokenSource: &mockTokenSource{
- token: newToken,
- },
- }
- verifier = mockOIDCVerifier{
- token: &oidc.IDToken{},
- }
- err = token.refresh(&config, &verifier)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "the refreshed token has no id token")
- }
- newToken = newToken.WithExtra(map[string]any{
- "id_token": "id_token_val",
- })
- newToken.Expiry = time.Time{}
- config = mockOAuth2Config{
- tokenSource: &mockTokenSource{
- token: newToken,
- },
- }
- verifier = mockOIDCVerifier{
- err: common.ErrGenericFailure,
- }
- err = token.refresh(&config, &verifier)
- assert.ErrorIs(t, err, common.ErrGenericFailure)
- newToken = newToken.WithExtra(map[string]any{
- "id_token": "id_token_val",
- })
- newToken.Expiry = time.Now().Add(5 * time.Minute)
- config = mockOAuth2Config{
- tokenSource: &mockTokenSource{
- token: newToken,
- },
- }
- verifier = mockOIDCVerifier{
- token: &oidc.IDToken{},
- }
- err = token.refresh(&config, &verifier)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "the refreshed token nonce mismatch")
- }
- verifier = mockOIDCVerifier{
- token: &oidc.IDToken{
- Nonce: token.Nonce,
- },
- }
- err = token.refresh(&config, &verifier)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "oidc: claims not set")
- }
- idToken := &oidc.IDToken{
- Nonce: token.Nonce,
- }
- setIDTokenClaims(idToken, []byte(`{"sid":"id_token_sid"}`))
- verifier = mockOIDCVerifier{
- token: idToken,
- }
- err = token.refresh(&config, &verifier)
- assert.NoError(t, err)
- require.Len(t, oidcMgr.tokens, 1)
- oidcMgr.removeToken(token.Cookie)
- require.Len(t, oidcMgr.tokens, 0)
- }
- func TestValidateOIDCToken(t *testing.T) {
- oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
- require.True(t, ok)
- server := getTestOIDCServer()
- err := server.binding.OIDC.initialize()
- assert.NoError(t, err)
- server.initializeRouter()
- rr := httptest.NewRecorder()
- r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
- assert.NoError(t, err)
- _, err = server.validateOIDCToken(rr, r, false)
- assert.ErrorIs(t, err, errInvalidToken)
- // expired token and refresh error
- server.binding.OIDC.oauth2Config = &mockOAuth2Config{
- tokenSource: &mockTokenSource{
- err: common.ErrGenericFailure,
- },
- }
- token := oidcToken{
- Cookie: xid.New().String(),
- AccessToken: xid.New().String(),
- ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-2 * time.Minute)),
- }
- oidcMgr.addToken(token)
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, token.Cookie))
- _, err = server.validateOIDCToken(rr, r, false)
- assert.ErrorIs(t, err, errInvalidToken)
- oidcMgr.removeToken(token.Cookie)
- assert.Len(t, oidcMgr.tokens, 0)
- server.tokenAuth = jwtauth.New("PS256", util.GenerateRandomBytes(32), nil)
- token = oidcToken{
- Cookie: xid.New().String(),
- AccessToken: xid.New().String(),
- }
- oidcMgr.addToken(token)
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, token.Cookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- oidcMgr.removeToken(token.Cookie)
- assert.Len(t, oidcMgr.tokens, 0)
- token = oidcToken{
- Cookie: xid.New().String(),
- AccessToken: xid.New().String(),
- Role: "admin",
- }
- oidcMgr.addToken(token)
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webLogoutPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", oidcCookieKey, token.Cookie))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminLoginPath, rr.Header().Get("Location"))
- oidcMgr.removeToken(token.Cookie)
- assert.Len(t, oidcMgr.tokens, 0)
- }
- func TestSkipOIDCAuth(t *testing.T) {
- server := getTestOIDCServer()
- err := server.binding.OIDC.initialize()
- assert.NoError(t, err)
- server.initializeRouter()
- jwtTokenClaims := jwtTokenClaims{
- Username: "user",
- }
- _, tokenString, err := jwtTokenClaims.createToken(server.tokenAuth, tokenAudienceWebClient, "")
- assert.NoError(t, err)
- rr := httptest.NewRecorder()
- r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
- assert.NoError(t, err)
- r.Header.Set("Cookie", fmt.Sprintf("%v=%v", jwtCookieKey, tokenString))
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- }
- func TestOIDCLogoutErrors(t *testing.T) {
- server := getTestOIDCServer()
- assert.Empty(t, server.binding.OIDC.providerLogoutURL)
- server.logoutFromOIDCOP("")
- server.binding.OIDC.providerLogoutURL = "http://foo\x7f.com/"
- server.doOIDCFromLogout("")
- server.binding.OIDC.providerLogoutURL = "http://127.0.0.1:11234"
- server.doOIDCFromLogout("")
- }
- func TestOIDCToken(t *testing.T) {
- admin := dataprovider.Admin{
- Username: "test_oidc_admin",
- Password: "p",
- Permissions: []string{dataprovider.PermAdminAny},
- Status: 0,
- }
- err := dataprovider.AddAdmin(&admin, "", "")
- assert.NoError(t, err)
- token := oidcToken{
- Username: admin.Username,
- Role: "admin",
- }
- req, err := http.NewRequest(http.MethodGet, webUsersPath, nil)
- assert.NoError(t, err)
- err = token.getUser(req)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "is disabled")
- }
- err = dataprovider.DeleteAdmin(admin.Username, "", "")
- assert.NoError(t, err)
- username := "test_oidc_user"
- token.Username = username
- token.Role = ""
- err = token.getUser(req)
- if assert.Error(t, err) {
- _, ok := err.(*util.RecordNotFoundError)
- assert.True(t, ok)
- }
- user := dataprovider.User{
- BaseUser: sdk.BaseUser{
- Username: username,
- Password: "p",
- HomeDir: filepath.Join(os.TempDir(), username),
- Status: 0,
- Permissions: map[string][]string{
- "/": {dataprovider.PermAny},
- },
- },
- Filters: dataprovider.UserFilters{
- BaseUserFilters: sdk.BaseUserFilters{
- DeniedProtocols: []string{common.ProtocolHTTP},
- },
- },
- }
- err = dataprovider.AddUser(&user, "", "")
- assert.NoError(t, err)
- err = token.getUser(req)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "is disabled")
- }
- user, err = dataprovider.UserExists(username)
- assert.NoError(t, err)
- user.Status = 1
- user.Password = "np"
- err = dataprovider.UpdateUser(&user, "", "")
- assert.NoError(t, err)
- err = token.getUser(req)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "protocol HTTP is not allowed")
- }
- user.Filters.DeniedProtocols = nil
- user.FsConfig.Provider = sdk.SFTPFilesystemProvider
- user.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
- BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
- Endpoint: "127.0.0.1:8022",
- Username: username,
- },
- Password: kms.NewPlainSecret("np"),
- }
- err = dataprovider.UpdateUser(&user, "", "")
- assert.NoError(t, err)
- err = token.getUser(req)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "SFTP loop")
- }
- common.Config.PostConnectHook = fmt.Sprintf("http://%v/404", oidcMockAddr)
- err = token.getUser(req)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "access denied by post connect hook")
- }
- common.Config.PostConnectHook = ""
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- err = dataprovider.DeleteUser(username, "", "")
- assert.NoError(t, err)
- }
- func TestMemoryOIDCManager(t *testing.T) {
- oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
- require.True(t, ok)
- require.Len(t, oidcMgr.pendingAuths, 0)
- authReq := newOIDCPendingAuth(tokenAudienceWebAdmin)
- oidcMgr.addPendingAuth(authReq)
- require.Len(t, oidcMgr.pendingAuths, 1)
- _, err := oidcMgr.getPendingAuth(authReq.State)
- assert.NoError(t, err)
- oidcMgr.removePendingAuth(authReq.State)
- require.Len(t, oidcMgr.pendingAuths, 0)
- authReq.IssuedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-61 * time.Second))
- oidcMgr.addPendingAuth(authReq)
- require.Len(t, oidcMgr.pendingAuths, 1)
- _, err = oidcMgr.getPendingAuth(authReq.State)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "too old")
- }
- oidcMgr.cleanup()
- require.Len(t, oidcMgr.pendingAuths, 0)
- token := oidcToken{
- AccessToken: xid.New().String(),
- Nonce: xid.New().String(),
- SessionID: xid.New().String(),
- Cookie: xid.New().String(),
- Username: xid.New().String(),
- Role: "admin",
- Permissions: []string{dataprovider.PermAdminAny},
- }
- require.Len(t, oidcMgr.tokens, 0)
- oidcMgr.addToken(token)
- require.Len(t, oidcMgr.tokens, 1)
- _, err = oidcMgr.getToken(xid.New().String())
- assert.Error(t, err)
- storedToken, err := oidcMgr.getToken(token.Cookie)
- assert.NoError(t, err)
- token.UsedAt = 0 // ensure we don't modify the stored token
- assert.Greater(t, storedToken.UsedAt, int64(0))
- token.UsedAt = storedToken.UsedAt
- assert.Equal(t, token, storedToken)
- // the usage will not be updated, it is recent
- oidcMgr.updateTokenUsage(storedToken)
- storedToken, err = oidcMgr.getToken(token.Cookie)
- assert.NoError(t, err)
- assert.Equal(t, token, storedToken)
- usedAt := util.GetTimeAsMsSinceEpoch(time.Now().Add(-5 * time.Minute))
- storedToken.UsedAt = usedAt
- oidcMgr.tokens[token.Cookie] = storedToken
- storedToken, err = oidcMgr.getToken(token.Cookie)
- assert.NoError(t, err)
- assert.Equal(t, usedAt, storedToken.UsedAt)
- token.UsedAt = storedToken.UsedAt
- assert.Equal(t, token, storedToken)
- oidcMgr.updateTokenUsage(storedToken)
- storedToken, err = oidcMgr.getToken(token.Cookie)
- assert.NoError(t, err)
- assert.Greater(t, storedToken.UsedAt, usedAt)
- token.UsedAt = storedToken.UsedAt
- assert.Equal(t, token, storedToken)
- storedToken.UsedAt = util.GetTimeAsMsSinceEpoch(time.Now()) - tokenDeleteInterval - 1
- oidcMgr.tokens[token.Cookie] = storedToken
- storedToken, err = oidcMgr.getToken(token.Cookie)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "token is too old")
- }
- oidcMgr.removeToken(xid.New().String())
- require.Len(t, oidcMgr.tokens, 1)
- oidcMgr.removeToken(token.Cookie)
- require.Len(t, oidcMgr.tokens, 0)
- oidcMgr.addToken(token)
- usedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-6 * time.Hour))
- token.UsedAt = usedAt
- oidcMgr.tokens[token.Cookie] = token
- newToken := oidcToken{
- Cookie: xid.New().String(),
- }
- oidcMgr.addToken(newToken)
- oidcMgr.cleanup()
- require.Len(t, oidcMgr.tokens, 1)
- _, err = oidcMgr.getToken(token.Cookie)
- assert.Error(t, err)
- _, err = oidcMgr.getToken(newToken.Cookie)
- assert.NoError(t, err)
- oidcMgr.removeToken(newToken.Cookie)
- require.Len(t, oidcMgr.tokens, 0)
- }
- func TestOIDCPreLoginHook(t *testing.T) {
- if runtime.GOOS == osWindows {
- t.Skip("this test is not available on Windows")
- }
- oidcMgr, ok := oidcMgr.(*memoryOIDCManager)
- require.True(t, ok)
- username := "test_oidc_user_prelogin"
- u := dataprovider.User{
- BaseUser: sdk.BaseUser{
- Username: username,
- HomeDir: filepath.Join(os.TempDir(), username),
- Status: 1,
- Permissions: map[string][]string{
- "/": {dataprovider.PermAny},
- },
- },
- }
- preLoginPath := filepath.Join(os.TempDir(), "prelogin.sh")
- providerConf := dataprovider.GetProviderConfig()
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
- assert.NoError(t, err)
- newProviderConf := providerConf
- newProviderConf.PreLoginHook = preLoginPath
- err = dataprovider.Initialize(newProviderConf, "..", true)
- assert.NoError(t, err)
- server := getTestOIDCServer()
- server.binding.OIDC.CustomFields = []string{"field1", "field2"}
- err = server.binding.OIDC.initialize()
- assert.NoError(t, err)
- server.initializeRouter()
- _, err = dataprovider.UserExists(username)
- _, ok = err.(*util.RecordNotFoundError)
- assert.True(t, ok)
- // now login with OIDC
- authReq := newOIDCPendingAuth(tokenAudienceWebClient)
- oidcMgr.addPendingAuth(authReq)
- token := &oauth2.Token{
- AccessToken: "1234",
- Expiry: time.Now().Add(5 * time.Minute),
- }
- token = token.WithExtra(map[string]any{
- "id_token": "id_token_val",
- })
- server.binding.OIDC.oauth2Config = &mockOAuth2Config{
- tokenSource: &mockTokenSource{},
- authCodeURL: webOIDCRedirectPath,
- token: token,
- }
- idToken := &oidc.IDToken{
- Nonce: authReq.Nonce,
- Expiry: time.Now().Add(5 * time.Minute),
- }
- setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+username+`"}`))
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: idToken,
- }
- rr := httptest.NewRecorder()
- r, err := http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientFilesPath, rr.Header().Get("Location"))
- _, err = dataprovider.UserExists(username)
- assert.NoError(t, err)
- err = dataprovider.DeleteUser(username, "", "")
- assert.NoError(t, err)
- err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, true), os.ModePerm)
- assert.NoError(t, err)
- authReq = newOIDCPendingAuth(tokenAudienceWebClient)
- oidcMgr.addPendingAuth(authReq)
- idToken = &oidc.IDToken{
- Nonce: authReq.Nonce,
- Expiry: time.Now().Add(5 * time.Minute),
- }
- setIDTokenClaims(idToken, []byte(`{"preferred_username":"`+username+`","field1":"value1","field2":"value2","field3":"value3"}`))
- server.binding.OIDC.verifier = &mockOIDCVerifier{
- err: nil,
- token: idToken,
- }
- rr = httptest.NewRecorder()
- r, err = http.NewRequest(http.MethodGet, webOIDCRedirectPath+"?state="+authReq.State, nil)
- assert.NoError(t, err)
- server.router.ServeHTTP(rr, r)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- _, err = dataprovider.UserExists(username)
- _, ok = err.(*util.RecordNotFoundError)
- assert.True(t, ok)
- if assert.Len(t, oidcMgr.tokens, 1) {
- for k := range oidcMgr.tokens {
- oidcMgr.removeToken(k)
- }
- }
- require.Len(t, oidcMgr.pendingAuths, 0)
- require.Len(t, oidcMgr.tokens, 0)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = dataprovider.Initialize(providerConf, "..", true)
- assert.NoError(t, err)
- err = os.Remove(preLoginPath)
- assert.NoError(t, err)
- }
- func TestOIDCIsAdmin(t *testing.T) {
- type test struct {
- input any
- want bool
- }
- emptySlice := make([]any, 0)
- tests := []test{
- {input: "admin", want: true},
- {input: append(emptySlice, "admin"), want: true},
- {input: append(emptySlice, "user", "admin"), want: true},
- {input: "user", want: false},
- {input: emptySlice, want: false},
- {input: append(emptySlice, 1), want: false},
- {input: 1, want: false},
- {input: nil, want: false},
- }
- for _, tc := range tests {
- token := oidcToken{
- Role: tc.input,
- }
- assert.Equal(t, tc.want, token.isAdmin(), "%v should return %t", tc.input, tc.want)
- }
- }
- func TestDbOIDCManager(t *testing.T) {
- if !isSharedProviderSupported() {
- t.Skip("this test it is not available with this provider")
- }
- mgr := newOIDCManager(1)
- pendingAuth := newOIDCPendingAuth(tokenAudienceWebAdmin)
- mgr.addPendingAuth(pendingAuth)
- authReq, err := mgr.getPendingAuth(pendingAuth.State)
- assert.NoError(t, err)
- assert.Equal(t, pendingAuth, authReq)
- pendingAuth.IssuedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-24 * time.Hour))
- mgr.addPendingAuth(pendingAuth)
- _, err = mgr.getPendingAuth(pendingAuth.State)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "auth request is too old")
- }
- mgr.removePendingAuth(pendingAuth.State)
- _, err = mgr.getPendingAuth(pendingAuth.State)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "unable to get the auth request for the specified state")
- }
- mgr.addPendingAuth(pendingAuth)
- _, err = mgr.getPendingAuth(pendingAuth.State)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "auth request is too old")
- }
- mgr.cleanup()
- _, err = mgr.getPendingAuth(pendingAuth.State)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "unable to get the auth request for the specified state")
- }
- token := oidcToken{
- Cookie: xid.New().String(),
- AccessToken: xid.New().String(),
- TokenType: "Bearer",
- RefreshToken: xid.New().String(),
- ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-2 * time.Minute)),
- SessionID: xid.New().String(),
- IDToken: xid.New().String(),
- Nonce: xid.New().String(),
- Username: xid.New().String(),
- Permissions: []string{dataprovider.PermAdminAny},
- Role: "admin",
- }
- mgr.addToken(token)
- tokenGet, err := mgr.getToken(token.Cookie)
- assert.NoError(t, err)
- assert.Greater(t, tokenGet.UsedAt, int64(0))
- token.UsedAt = tokenGet.UsedAt
- assert.Equal(t, token, tokenGet)
- time.Sleep(100 * time.Millisecond)
- mgr.updateTokenUsage(token)
- // no change
- tokenGet, err = mgr.getToken(token.Cookie)
- assert.NoError(t, err)
- assert.Equal(t, token.UsedAt, tokenGet.UsedAt)
- tokenGet.UsedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-24 * time.Hour))
- tokenGet.RefreshToken = xid.New().String()
- mgr.updateTokenUsage(tokenGet)
- tokenGet, err = mgr.getToken(token.Cookie)
- assert.NoError(t, err)
- assert.NotEmpty(t, tokenGet.RefreshToken)
- assert.NotEqual(t, token.RefreshToken, tokenGet.RefreshToken)
- assert.Greater(t, tokenGet.UsedAt, token.UsedAt)
- mgr.removeToken(token.Cookie)
- tokenGet, err = mgr.getToken(token.Cookie)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "unable to get the token for the specified session")
- }
- // add an expired token
- token.UsedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-24 * time.Hour))
- session := dataprovider.Session{
- Key: token.Cookie,
- Data: token,
- Type: dataprovider.SessionTypeOIDCToken,
- Timestamp: token.UsedAt + tokenDeleteInterval,
- }
- err = dataprovider.AddSharedSession(session)
- assert.NoError(t, err)
- _, err = mgr.getToken(token.Cookie)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "token is too old")
- }
- mgr.cleanup()
- _, err = mgr.getToken(token.Cookie)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "unable to get the token for the specified session")
- }
- // adding a session without a key should fail
- session.Key = ""
- err = dataprovider.AddSharedSession(session)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "unable to save a session with an empty key")
- }
- session.Key = xid.New().String()
- session.Type = 1000
- err = dataprovider.AddSharedSession(session)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "invalid session type")
- }
- dbMgr, ok := mgr.(*dbOIDCManager)
- if assert.True(t, ok) {
- _, err = dbMgr.decodePendingAuthData(2)
- assert.Error(t, err)
- _, err = dbMgr.decodeTokenData(true)
- assert.Error(t, err)
- }
- }
- func getTestOIDCServer() *httpdServer {
- return &httpdServer{
- binding: Binding{
- OIDC: OIDC{
- ClientID: "sftpgo-client",
- ClientSecret: "jRsmE0SWnuZjP7djBqNq0mrf8QN77j2c",
- ConfigURL: fmt.Sprintf("http://%v/auth/realms/sftpgo", oidcMockAddr),
- RedirectBaseURL: "http://127.0.0.1:8081/",
- UsernameField: "preferred_username",
- RoleField: "sftpgo_role",
- CustomFields: nil,
- },
- },
- enableWebAdmin: true,
- enableWebClient: true,
- }
- }
- func getPreLoginScriptContent(user dataprovider.User, nonJSONResponse bool) []byte {
- content := []byte("#!/bin/sh\n\n")
- if nonJSONResponse {
- content = append(content, []byte("echo 'text response'\n")...)
- return content
- }
- if len(user.Username) > 0 {
- u, _ := json.Marshal(user)
- content = append(content, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
- }
- return content
- }
|