2023-04-19 04:27:10 +00:00
|
|
|
package oidc_test
|
2021-05-04 22:06:05 +00:00
|
|
|
|
2022-04-12 03:02:12 +00:00
|
|
|
import (
|
|
|
|
"context"
|
2023-04-19 04:27:10 +00:00
|
|
|
"database/sql"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-04-12 03:02:12 +00:00
|
|
|
"testing"
|
2023-04-19 04:27:10 +00:00
|
|
|
"time"
|
2022-04-12 03:02:12 +00:00
|
|
|
|
2023-04-19 04:27:10 +00:00
|
|
|
"github.com/golang/mock/gomock"
|
|
|
|
"github.com/google/uuid"
|
2023-04-13 10:58:18 +00:00
|
|
|
"github.com/ory/fosite"
|
2022-04-12 03:02:12 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
2023-04-19 04:27:10 +00:00
|
|
|
"github.com/stretchr/testify/suite"
|
2022-04-12 03:02:12 +00:00
|
|
|
|
|
|
|
"github.com/authelia/authelia/v4/internal/authorization"
|
|
|
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
2023-04-19 04:27:10 +00:00
|
|
|
"github.com/authelia/authelia/v4/internal/mocks"
|
|
|
|
"github.com/authelia/authelia/v4/internal/model"
|
|
|
|
"github.com/authelia/authelia/v4/internal/oidc"
|
|
|
|
"github.com/authelia/authelia/v4/internal/storage"
|
2022-04-12 03:02:12 +00:00
|
|
|
)
|
2021-05-04 22:06:05 +00:00
|
|
|
|
|
|
|
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
2023-04-19 04:27:10 +00:00
|
|
|
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
2022-10-02 02:07:40 +00:00
|
|
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
2023-04-19 04:24:05 +00:00
|
|
|
IssuerPrivateKey: keyRSA2048,
|
2021-05-04 22:06:05 +00:00
|
|
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
|
|
|
{
|
2023-04-13 10:58:18 +00:00
|
|
|
ID: myclient,
|
|
|
|
Description: myclientdesc,
|
|
|
|
Policy: onefactor,
|
2023-04-19 04:27:10 +00:00
|
|
|
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
|
|
|
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
2021-05-04 22:06:05 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "myotherclient",
|
2023-04-13 10:58:18 +00:00
|
|
|
Description: myclientdesc,
|
|
|
|
Policy: twofactor,
|
2023-04-19 04:27:10 +00:00
|
|
|
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
|
|
|
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
2021-05-04 22:06:05 +00:00
|
|
|
},
|
|
|
|
},
|
2022-04-12 03:02:12 +00:00
|
|
|
}, nil)
|
2021-05-04 22:06:05 +00:00
|
|
|
|
2023-04-13 10:58:18 +00:00
|
|
|
policyOne := s.GetClientPolicy(myclient)
|
2021-05-04 22:06:05 +00:00
|
|
|
assert.Equal(t, authorization.OneFactor, policyOne)
|
|
|
|
|
|
|
|
policyTwo := s.GetClientPolicy("myotherclient")
|
|
|
|
assert.Equal(t, authorization.TwoFactor, policyTwo)
|
|
|
|
|
|
|
|
policyInvalid := s.GetClientPolicy("invalidclient")
|
|
|
|
assert.Equal(t, authorization.TwoFactor, policyInvalid)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
2023-04-19 04:27:10 +00:00
|
|
|
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
2022-10-02 02:07:40 +00:00
|
|
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
2023-04-19 04:24:05 +00:00
|
|
|
IssuerPrivateKey: keyRSA2048,
|
2021-05-04 22:06:05 +00:00
|
|
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
|
|
|
{
|
2023-04-13 10:58:18 +00:00
|
|
|
ID: myclient,
|
|
|
|
Description: myclientdesc,
|
|
|
|
Policy: onefactor,
|
2023-04-19 04:27:10 +00:00
|
|
|
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
|
|
|
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
2021-05-04 22:06:05 +00:00
|
|
|
},
|
|
|
|
},
|
2022-04-12 03:02:12 +00:00
|
|
|
}, nil)
|
2021-05-04 22:06:05 +00:00
|
|
|
|
|
|
|
client, err := s.GetClient(context.Background(), "myinvalidclient")
|
2023-04-08 04:48:55 +00:00
|
|
|
assert.EqualError(t, err, "invalid_client")
|
2021-05-04 22:06:05 +00:00
|
|
|
assert.Nil(t, client)
|
|
|
|
|
2023-04-13 10:58:18 +00:00
|
|
|
client, err = s.GetClient(context.Background(), myclient)
|
2021-05-04 22:06:05 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, client)
|
2023-04-13 10:58:18 +00:00
|
|
|
assert.Equal(t, myclient, client.GetID())
|
2021-05-04 22:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
2023-04-13 10:58:18 +00:00
|
|
|
id := myclient
|
|
|
|
|
2021-05-04 22:06:05 +00:00
|
|
|
c1 := schema.OpenIDConnectClientConfiguration{
|
2023-04-13 10:58:18 +00:00
|
|
|
ID: id,
|
|
|
|
Description: myclientdesc,
|
|
|
|
Policy: onefactor,
|
2023-04-19 04:27:10 +00:00
|
|
|
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
|
|
|
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
2021-05-04 22:06:05 +00:00
|
|
|
}
|
2021-11-23 09:45:38 +00:00
|
|
|
|
2023-04-19 04:27:10 +00:00
|
|
|
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
2022-10-02 02:07:40 +00:00
|
|
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
2023-04-19 04:24:05 +00:00
|
|
|
IssuerPrivateKey: keyRSA2048,
|
2022-10-02 02:07:40 +00:00
|
|
|
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
2022-04-12 03:02:12 +00:00
|
|
|
}, nil)
|
2021-05-04 22:06:05 +00:00
|
|
|
|
2023-04-13 10:58:18 +00:00
|
|
|
client, err := s.GetFullClient(id)
|
2021-05-04 22:06:05 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, client)
|
2023-04-13 10:58:18 +00:00
|
|
|
assert.Equal(t, id, client.GetID())
|
|
|
|
assert.Equal(t, myclientdesc, client.GetDescription())
|
|
|
|
assert.Equal(t, fosite.Arguments(c1.Scopes), client.GetScopes())
|
2023-04-19 04:27:10 +00:00
|
|
|
assert.Equal(t, fosite.Arguments([]string{oidc.GrantTypeAuthorizationCode}), client.GetGrantTypes())
|
|
|
|
assert.Equal(t, fosite.Arguments([]string{oidc.ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes())
|
2023-04-13 10:58:18 +00:00
|
|
|
assert.Equal(t, []string(nil), client.GetRedirectURIs())
|
|
|
|
assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy())
|
2023-04-19 04:27:10 +00:00
|
|
|
assert.Equal(t, "$plaintext$client-secret", client.GetSecret().Encode())
|
2021-05-04 22:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
|
|
|
c1 := schema.OpenIDConnectClientConfiguration{
|
2023-04-13 10:58:18 +00:00
|
|
|
ID: myclient,
|
|
|
|
Description: myclientdesc,
|
|
|
|
Policy: onefactor,
|
2023-04-19 04:27:10 +00:00
|
|
|
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
|
|
|
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
2021-05-04 22:06:05 +00:00
|
|
|
}
|
2021-11-23 09:45:38 +00:00
|
|
|
|
2023-04-19 04:27:10 +00:00
|
|
|
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
2022-10-02 02:07:40 +00:00
|
|
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
2023-04-19 04:24:05 +00:00
|
|
|
IssuerPrivateKey: keyRSA2048,
|
2022-10-02 02:07:40 +00:00
|
|
|
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
2022-04-12 03:02:12 +00:00
|
|
|
}, nil)
|
2021-05-04 22:06:05 +00:00
|
|
|
|
2022-04-07 05:33:53 +00:00
|
|
|
client, err := s.GetFullClient("another-client")
|
2021-05-04 22:06:05 +00:00
|
|
|
assert.Nil(t, client)
|
2023-04-08 04:48:55 +00:00
|
|
|
assert.EqualError(t, err, "invalid_client")
|
2021-05-04 22:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
2023-04-19 04:27:10 +00:00
|
|
|
s := oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
2022-10-02 02:07:40 +00:00
|
|
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
2023-04-19 04:24:05 +00:00
|
|
|
IssuerPrivateKey: keyRSA2048,
|
2021-05-04 22:06:05 +00:00
|
|
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
|
|
|
{
|
2023-04-13 10:58:18 +00:00
|
|
|
ID: myclient,
|
|
|
|
Description: myclientdesc,
|
|
|
|
Policy: onefactor,
|
2023-04-19 04:27:10 +00:00
|
|
|
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
|
|
|
|
Secret: tOpenIDConnectPlainTextClientSecret,
|
2021-05-04 22:06:05 +00:00
|
|
|
},
|
|
|
|
},
|
2022-04-12 03:02:12 +00:00
|
|
|
}, nil)
|
2021-05-04 22:06:05 +00:00
|
|
|
|
2023-04-13 10:58:18 +00:00
|
|
|
validClient := s.IsValidClientID(myclient)
|
2021-05-04 22:06:05 +00:00
|
|
|
invalidClient := s.IsValidClientID("myinvalidclient")
|
|
|
|
|
|
|
|
assert.True(t, validClient)
|
|
|
|
assert.False(t, invalidClient)
|
2022-04-12 03:02:12 +00:00
|
|
|
}
|
2023-04-19 04:27:10 +00:00
|
|
|
|
|
|
|
func TestStoreSuite(t *testing.T) {
|
|
|
|
suite.Run(t, &StoreSuite{})
|
|
|
|
}
|
|
|
|
|
|
|
|
type StoreSuite struct {
|
|
|
|
suite.Suite
|
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
ctrl *gomock.Controller
|
|
|
|
mock *mocks.MockStorage
|
|
|
|
store *oidc.Store
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StoreSuite) SetupTest() {
|
|
|
|
s.ctx = context.Background()
|
|
|
|
s.ctrl = gomock.NewController(s.T())
|
|
|
|
s.mock = mocks.NewMockStorage(s.ctrl)
|
|
|
|
s.store = oidc.NewStore(&schema.OpenIDConnectConfiguration{
|
|
|
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
|
|
|
{
|
|
|
|
ID: "hs256",
|
|
|
|
Secret: tOpenIDConnectPBKDF2ClientSecret,
|
|
|
|
Policy: authorization.OneFactor.String(),
|
|
|
|
RedirectURIs: []string{
|
|
|
|
"https://client.example.com",
|
|
|
|
},
|
|
|
|
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretJWT,
|
|
|
|
TokenEndpointAuthSigningAlg: oidc.SigningAlgHMACUsingSHA256,
|
|
|
|
},
|
|
|
|
}}, s.mock)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StoreSuite) TestGetSubject() {
|
|
|
|
s.T().Run("GenerateNew", func(t *testing.T) {
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadUserOpaqueIdentifierBySignature(s.ctx, "openid", "", "john").
|
|
|
|
Return(nil, nil)
|
|
|
|
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
SaveUserOpaqueIdentifier(s.ctx, gomock.Any()).
|
|
|
|
Return(nil)
|
|
|
|
|
|
|
|
opaqueID, err := s.store.GetSubject(s.ctx, "", "john")
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.NotEqual(t, uint32(0), opaqueID)
|
|
|
|
})
|
|
|
|
|
|
|
|
s.T().Run("ReturnDatabaseErrorOnLoad", func(t *testing.T) {
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadUserOpaqueIdentifierBySignature(s.ctx, "openid", "", "john").
|
|
|
|
Return(nil, fmt.Errorf("failed to load"))
|
|
|
|
|
|
|
|
opaqueID, err := s.store.GetSubject(s.ctx, "", "john")
|
|
|
|
|
|
|
|
assert.EqualError(t, err, "failed to load")
|
|
|
|
assert.Equal(t, uint32(0), opaqueID.ID())
|
|
|
|
})
|
|
|
|
|
|
|
|
s.T().Run("ReturnDatabaseErrorOnSave", func(t *testing.T) {
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadUserOpaqueIdentifierBySignature(s.ctx, "openid", "", "john").
|
|
|
|
Return(nil, nil)
|
|
|
|
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
SaveUserOpaqueIdentifier(s.ctx, gomock.Any()).
|
|
|
|
Return(fmt.Errorf("failed to save"))
|
|
|
|
|
|
|
|
opaqueID, err := s.store.GetSubject(s.ctx, "", "john")
|
|
|
|
|
|
|
|
assert.EqualError(t, err, "failed to save")
|
|
|
|
assert.Equal(t, uint32(0), opaqueID.ID())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StoreSuite) TestTx() {
|
|
|
|
gomock.InOrder(
|
|
|
|
s.mock.EXPECT().BeginTX(s.ctx).Return(s.ctx, nil),
|
|
|
|
s.mock.EXPECT().Commit(s.ctx).Return(nil),
|
|
|
|
s.mock.EXPECT().Rollback(s.ctx).Return(nil),
|
|
|
|
s.mock.EXPECT().BeginTX(s.ctx).Return(nil, fmt.Errorf("failed to begin")),
|
|
|
|
s.mock.EXPECT().Commit(s.ctx).Return(fmt.Errorf("failed to commit")),
|
|
|
|
s.mock.EXPECT().Rollback(s.ctx).Return(fmt.Errorf("failed to rollback")),
|
|
|
|
)
|
|
|
|
|
|
|
|
x, err := s.store.BeginTX(s.ctx)
|
|
|
|
s.Equal(s.ctx, x)
|
|
|
|
s.NoError(err)
|
|
|
|
s.NoError(s.store.Commit(s.ctx))
|
|
|
|
s.NoError(s.store.Rollback(s.ctx))
|
|
|
|
|
|
|
|
x, err = s.store.BeginTX(s.ctx)
|
|
|
|
s.Equal(nil, x)
|
|
|
|
s.EqualError(err, "failed to begin")
|
|
|
|
s.EqualError(s.store.Commit(s.ctx), "failed to commit")
|
|
|
|
s.EqualError(s.store.Rollback(s.ctx), "failed to rollback")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StoreSuite) TestClientAssertionJWTValid() {
|
|
|
|
gomock.InOrder(
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadOAuth2BlacklistedJTI(s.ctx, "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63").
|
|
|
|
Return(&model.OAuth2BlacklistedJTI{
|
|
|
|
ID: 1,
|
|
|
|
Signature: "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63",
|
|
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
|
|
}, nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadOAuth2BlacklistedJTI(s.ctx, "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068").
|
|
|
|
Return(&model.OAuth2BlacklistedJTI{
|
|
|
|
ID: 1,
|
|
|
|
Signature: "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068",
|
|
|
|
ExpiresAt: time.Now().Add(-time.Hour),
|
|
|
|
}, nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadOAuth2BlacklistedJTI(s.ctx, "f29ef0d85303a09411b76001c579980f1b1b7fc9deb1fa647875a724f4f231c6").
|
|
|
|
Return(nil, fmt.Errorf("failed to load")),
|
|
|
|
)
|
|
|
|
|
|
|
|
s.EqualError(s.store.ClientAssertionJWTValid(s.ctx, "066ee771-e156-4886-b99f-ee09b0d3edf4"), "jti_known")
|
|
|
|
s.NoError(s.store.ClientAssertionJWTValid(s.ctx, "5dad3ff7-e4f2-41b6-98a3-b73d872076ce"))
|
|
|
|
s.EqualError(s.store.ClientAssertionJWTValid(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202"), "failed to load")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StoreSuite) TestCreateSessions() {
|
|
|
|
challenge := uuid.Must(uuid.NewRandom())
|
|
|
|
session := &model.OpenIDSession{
|
|
|
|
ChallengeID: challenge,
|
|
|
|
}
|
|
|
|
sessionData, _ := json.Marshal(session)
|
|
|
|
|
|
|
|
gomock.InOrder(
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
|
|
|
|
Return(fmt.Errorf("duplicate key")),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
SaveOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, model.OAuth2Session{ChallengeID: challenge, RequestID: "abc", ClientID: "example", Signature: "abc", Active: true, Session: sessionData}).
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
SaveOAuth2PARContext(s.ctx, model.OAuth2PARContext{Signature: "abc", RequestID: "abc", ClientID: "example", Session: sessionData}).
|
|
|
|
Return(nil),
|
|
|
|
)
|
|
|
|
|
|
|
|
s.NoError(s.store.CreateAuthorizeCodeSession(s.ctx, "abc", &fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: session,
|
|
|
|
}))
|
|
|
|
|
|
|
|
s.EqualError(s.store.CreateAuthorizeCodeSession(s.ctx, "abc", &fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: session,
|
|
|
|
}), "duplicate key")
|
|
|
|
|
|
|
|
s.EqualError(s.store.CreateAuthorizeCodeSession(s.ctx, "abc", &fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: nil,
|
|
|
|
}), "can't convert type '<nil>' to an *OAuth2Session")
|
|
|
|
|
|
|
|
s.NoError(s.store.CreateAccessTokenSession(s.ctx, "abc", &fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: session,
|
|
|
|
}))
|
|
|
|
|
|
|
|
s.NoError(s.store.CreateRefreshTokenSession(s.ctx, "abc", &fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: session,
|
|
|
|
}))
|
|
|
|
|
|
|
|
s.NoError(s.store.CreateOpenIDConnectSession(s.ctx, "abc", &fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: session,
|
|
|
|
}))
|
|
|
|
|
|
|
|
s.NoError(s.store.CreatePKCERequestSession(s.ctx, "abc", &fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: session,
|
|
|
|
}))
|
|
|
|
|
|
|
|
s.NoError(s.store.CreatePARSession(s.ctx, "abc", &fosite.AuthorizeRequest{
|
|
|
|
Request: fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: session,
|
|
|
|
}}))
|
|
|
|
|
|
|
|
s.EqualError(s.store.CreatePARSession(s.ctx, "abc", &fosite.AuthorizeRequest{
|
|
|
|
Request: fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: nil,
|
|
|
|
}}), "can't convert type '<nil>' to an *OAuth2Session")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StoreSuite) TestRevokeSessions() {
|
|
|
|
gomock.InOrder(
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
DeactivateOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "abc1").
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
DeactivateOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "abc2").
|
|
|
|
Return(fmt.Errorf("not found")),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, "at_example1").
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, "at_example2").
|
|
|
|
Return(fmt.Errorf("not found")),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeAccessToken, "65471ccb-d650-4006-a95f-cb4f4e3d7200").
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeAccessToken, "65471ccb-d650-4006-a95f-cb4f4e3d7201").
|
|
|
|
Return(fmt.Errorf("not found")),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeAccessToken, "65471ccb-d650-4006-a95f-cb4f4e3d7202").
|
|
|
|
Return(sql.ErrNoRows),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, "rt_example1").
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, "rt_example2").
|
|
|
|
Return(fmt.Errorf("not found")),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7200").
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7201").
|
|
|
|
Return(fmt.Errorf("not found")),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7202").
|
|
|
|
Return(sql.ErrNoRows),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7200").
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7201").
|
|
|
|
Return(fmt.Errorf("not found")),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
DeactivateOAuth2SessionByRequestID(s.ctx, storage.OAuth2SessionTypeRefreshToken, "65471ccb-d650-4006-a95f-cb4f4e3d7202").
|
|
|
|
Return(sql.ErrNoRows),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, "pkce1").
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, "pkce2").
|
|
|
|
Return(fmt.Errorf("not found")),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, "ac_1").
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, "ac_2").
|
|
|
|
Return(fmt.Errorf("not found")),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2PARContext(s.ctx, "urn:par1").
|
|
|
|
Return(nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
RevokeOAuth2PARContext(s.ctx, "urn:par2").
|
|
|
|
Return(fmt.Errorf("not found")),
|
|
|
|
)
|
|
|
|
|
|
|
|
s.NoError(s.store.InvalidateAuthorizeCodeSession(s.ctx, "abc1"))
|
|
|
|
s.EqualError(s.store.InvalidateAuthorizeCodeSession(s.ctx, "abc2"), "not found")
|
|
|
|
|
|
|
|
s.NoError(s.store.DeleteAccessTokenSession(s.ctx, "at_example1"))
|
|
|
|
s.EqualError(s.store.DeleteAccessTokenSession(s.ctx, "at_example2"), "not found")
|
|
|
|
|
|
|
|
s.NoError(s.store.RevokeAccessToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7200"))
|
|
|
|
s.EqualError(s.store.RevokeAccessToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201"), "not found")
|
|
|
|
s.EqualError(s.store.RevokeAccessToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202"), "not_found")
|
|
|
|
|
|
|
|
s.NoError(s.store.DeleteRefreshTokenSession(s.ctx, "rt_example1"))
|
|
|
|
s.EqualError(s.store.DeleteRefreshTokenSession(s.ctx, "rt_example2"), "not found")
|
|
|
|
|
|
|
|
s.NoError(s.store.RevokeRefreshToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7200"))
|
|
|
|
s.EqualError(s.store.RevokeRefreshToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201"), "not found")
|
|
|
|
s.EqualError(s.store.RevokeRefreshToken(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202"), "sql: no rows in result set")
|
|
|
|
|
|
|
|
s.NoError(s.store.RevokeRefreshTokenMaybeGracePeriod(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7200", "1"))
|
|
|
|
s.EqualError(s.store.RevokeRefreshTokenMaybeGracePeriod(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201", "2"), "not found")
|
|
|
|
s.EqualError(s.store.RevokeRefreshTokenMaybeGracePeriod(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202", "3"), "sql: no rows in result set")
|
|
|
|
|
|
|
|
s.NoError(s.store.DeletePKCERequestSession(s.ctx, "pkce1"))
|
|
|
|
s.EqualError(s.store.DeletePKCERequestSession(s.ctx, "pkce2"), "not found")
|
|
|
|
|
|
|
|
s.NoError(s.store.DeleteOpenIDConnectSession(s.ctx, "ac_1"))
|
|
|
|
s.EqualError(s.store.DeleteOpenIDConnectSession(s.ctx, "ac_2"), "not found")
|
|
|
|
|
|
|
|
s.NoError(s.store.DeletePARSession(s.ctx, "urn:par1"))
|
|
|
|
s.EqualError(s.store.DeletePARSession(s.ctx, "urn:par2"), "not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StoreSuite) TestGetSessions() {
|
|
|
|
challenge := uuid.Must(uuid.NewRandom())
|
|
|
|
session := &model.OpenIDSession{
|
|
|
|
ChallengeID: challenge,
|
|
|
|
ClientID: "hs256",
|
|
|
|
}
|
|
|
|
sessionData, _ := json.Marshal(session)
|
|
|
|
|
|
|
|
sessionb := &model.OpenIDSession{
|
|
|
|
ChallengeID: challenge,
|
|
|
|
ClientID: "hs256",
|
|
|
|
}
|
|
|
|
sessionDatab, _ := json.Marshal(sessionb)
|
|
|
|
|
|
|
|
gomock.InOrder(
|
|
|
|
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_123").
|
|
|
|
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
|
|
|
|
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_456").
|
|
|
|
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: false}, nil),
|
|
|
|
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_aaa").
|
|
|
|
Return(nil, sql.ErrNoRows),
|
|
|
|
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_130").
|
|
|
|
Return(nil, fmt.Errorf("timeout")),
|
|
|
|
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAuthorizeCode, "ac_badclient").
|
|
|
|
Return(&model.OAuth2Session{ClientID: "no-client", Session: sessionDatab, Active: true}, nil),
|
|
|
|
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeAccessToken, "at").
|
|
|
|
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
|
|
|
|
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeRefreshToken, "rt").
|
|
|
|
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
|
|
|
|
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypePKCEChallenge, "pkce").
|
|
|
|
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
|
|
|
|
s.mock.EXPECT().LoadOAuth2Session(s.ctx, storage.OAuth2SessionTypeOpenIDConnect, "ot").
|
|
|
|
Return(&model.OAuth2Session{ClientID: "hs256", Session: sessionData, Active: true}, nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadOAuth2PARContext(s.ctx, "urn:par").
|
|
|
|
Return(&model.OAuth2PARContext{Signature: "abc", RequestID: "abc", ClientID: "hs256", Session: sessionData}, nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadOAuth2PARContext(s.ctx, "urn:par").
|
|
|
|
Return(nil, sql.ErrNoRows),
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
r fosite.Requester
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_123", &model.OpenIDSession{})
|
|
|
|
s.NotNil(r)
|
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_456", &model.OpenIDSession{})
|
|
|
|
s.NotNil(r)
|
|
|
|
s.EqualError(err, "Authorization code has ben invalidated")
|
|
|
|
|
|
|
|
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_aaa", &model.OpenIDSession{})
|
|
|
|
s.Nil(r)
|
|
|
|
s.EqualError(err, "not_found")
|
|
|
|
|
|
|
|
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_130", &model.OpenIDSession{})
|
|
|
|
s.Nil(r)
|
|
|
|
s.EqualError(err, "timeout")
|
|
|
|
|
|
|
|
r, err = s.store.GetAuthorizeCodeSession(s.ctx, "ac_badclient", &model.OpenIDSession{})
|
|
|
|
s.Nil(r)
|
|
|
|
s.EqualError(err, "invalid_client")
|
|
|
|
|
|
|
|
r, err = s.store.GetAccessTokenSession(s.ctx, "at", &model.OpenIDSession{})
|
|
|
|
s.NotNil(r)
|
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
r, err = s.store.GetRefreshTokenSession(s.ctx, "rt", &model.OpenIDSession{})
|
|
|
|
s.NotNil(r)
|
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
r, err = s.store.GetPKCERequestSession(s.ctx, "pkce", &model.OpenIDSession{})
|
|
|
|
s.NotNil(r)
|
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
r, err = s.store.GetOpenIDConnectSession(s.ctx, "ot", &fosite.Request{
|
|
|
|
ID: "abc",
|
|
|
|
Client: &oidc.BaseClient{
|
|
|
|
ID: "example",
|
|
|
|
},
|
|
|
|
Session: session,
|
|
|
|
})
|
|
|
|
s.NotNil(r)
|
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
r, err = s.store.GetPARSession(s.ctx, "urn:par")
|
|
|
|
s.NotNil(r)
|
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
r, err = s.store.GetPARSession(s.ctx, "urn:par")
|
|
|
|
s.Nil(r)
|
|
|
|
s.EqualError(err, "sql: no rows in result set")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StoreSuite) TestIsJWTUsed() {
|
|
|
|
gomock.InOrder(
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadOAuth2BlacklistedJTI(s.ctx, "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63").
|
|
|
|
Return(&model.OAuth2BlacklistedJTI{
|
|
|
|
ID: 1,
|
|
|
|
Signature: "3a240379e8286a7a8ff5e99d68567e0e5e34e80168b8feffa89d3d33dea95b63",
|
|
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
|
|
}, nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadOAuth2BlacklistedJTI(s.ctx, "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068").
|
|
|
|
Return(&model.OAuth2BlacklistedJTI{
|
|
|
|
ID: 1,
|
|
|
|
Signature: "e7f67ad76c80d57d34b19598462817932aec21d2806a08a786a8d4b9dd476068",
|
|
|
|
ExpiresAt: time.Now().Add(-time.Hour),
|
|
|
|
}, nil),
|
|
|
|
s.mock.
|
|
|
|
EXPECT().
|
|
|
|
LoadOAuth2BlacklistedJTI(s.ctx, "f29ef0d85303a09411b76001c579980f1b1b7fc9deb1fa647875a724f4f231c6").
|
|
|
|
Return(nil, fmt.Errorf("failed to load")),
|
|
|
|
)
|
|
|
|
|
|
|
|
used, err := s.store.IsJWTUsed(s.ctx, "066ee771-e156-4886-b99f-ee09b0d3edf4")
|
|
|
|
s.True(used)
|
|
|
|
s.EqualError(err, "jti_known")
|
|
|
|
|
|
|
|
used, err = s.store.IsJWTUsed(s.ctx, "5dad3ff7-e4f2-41b6-98a3-b73d872076ce")
|
|
|
|
s.False(used)
|
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
used, err = s.store.IsJWTUsed(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202")
|
|
|
|
s.True(used)
|
|
|
|
s.EqualError(err, "failed to load")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StoreSuite) TestMarkJWTUsedForTime() {
|
|
|
|
gomock.InOrder(
|
|
|
|
s.mock.EXPECT().
|
|
|
|
SaveOAuth2BlacklistedJTI(s.ctx, model.OAuth2BlacklistedJTI{Signature: "f29ef0d85303a09411b76001c579980f1b1b7fc9deb1fa647875a724f4f231c6", ExpiresAt: time.Unix(160000000, 0)}).
|
|
|
|
Return(nil),
|
|
|
|
s.mock.EXPECT().SaveOAuth2BlacklistedJTI(s.ctx, model.OAuth2BlacklistedJTI{Signature: "0dab0de97ed4e05da82763497448daf4f6b555c99218100e3ef5a81f36232940", ExpiresAt: time.Unix(160000000, 0)}).
|
|
|
|
Return(fmt.Errorf("already marked")),
|
|
|
|
)
|
|
|
|
|
|
|
|
s.NoError(s.store.MarkJWTUsedForTime(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7202", time.Unix(160000000, 0)))
|
|
|
|
s.EqualError(s.store.MarkJWTUsedForTime(s.ctx, "65471ccb-d650-4006-a95f-cb4f4e3d7201", time.Unix(160000000, 0)), "already marked")
|
|
|
|
}
|