mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 10:03:33 -06:00
* Moving POC files from #64283 to a new branch
Co-authored-by: Mihály Gyöngyösi <mgyongyosi@users.noreply.github.com>
* Adding missing permission definition
Co-authored-by: Mihály Gyöngyösi <mgyongyosi@users.noreply.github.com>
* Force the service instantiation while client isn't merged
Co-authored-by: Mihály Gyöngyösi <mgyongyosi@users.noreply.github.com>
* Merge conf with main
Co-authored-by: Mihály Gyöngyösi <mgyongyosi@users.noreply.github.com>
* Leave go-sqlite3 version unchanged
Co-authored-by: Mihály Gyöngyösi <mgyongyosi@users.noreply.github.com>
* tidy
Co-authored-by: Mihály Gyöngyösi <mgyongyosi@users.noreply.github.com>
* User SearchUserPermissions instead of SearchUsersPermissions
* Replace DummyKeyService with signingkeys.Service
* Use user🆔<id> as subject
* Fix introspection endpoint issue
* Add X-Grafana-Org-Id to get_resources.bash script
* Regenerate toggles_gen.go
* Fix basic.go
* Add GetExternalService tests
* Add GetPublicKeyScopes tests
* Add GetScopesOnUser tests
* Add GetScopes tests
* Add ParsePublicKeyPem tests
* Add database test for GetByName
* re-add comments
* client tests added
* Add GetExternalServicePublicKey tests
* Add other test case to GetExternalServicePublicKey
* client_credentials grant test
* Add test to jwtbearer grant
* Test Comments
* Add handleKeyOptions tests
* Add RSA key generation test
* Add ECDSA by default to EmbeddedSigningKeysService
* Clean up org id scope and audiences
* Add audiences to the DB
* Fix check on Audience
* Fix double import
* Add AC Store mock and align oauthserver tests
* Fix test after rebase
* Adding missing store function to mock
* Fix double import
* Add CODEOWNER
* Fix some linting errors
* errors don't need type assertion
* Typo codeowners
* use mockery for oauthserver store
* Add feature toggle check
* Fix db tests to handle the feature flag
* Adding call to DeleteExternalServiceRole
* Fix flaky test
* Re-organize routes comments and plan futur work
* Add client_id check to Extended JWT client
* Clean up
* Fix
* Remove background service registry instantiation of the OAuth server
* Comment cleanup
* Remove unused client function
* Update go.mod to use the latest ory/fosite commit
* Remove oauth2_server related configs from defaults.ini
* Add audiences to DTO
* Fix flaky test
* Remove registration endpoint and demo scripts. Document code
* Rename packages
* Remove the OAuthService vs OAuthServer confusion
* fix incorrect import ext_jwt_test
* Comments and order
* Comment basic auth
* Remove unecessary todo
* Clean api
* Moving ParsePublicKeyPem to utils
* re ordering functions in service.go
* Fix comment
* comment on the redirect uri
* Add RBAC actions, not only scopes
* Fix tests
* re-import featuremgmt in migrations
* Fix wire
* Fix scopes in test
* Fix flaky test
* Remove todo, the intersection should always return the minimal set
* Remove unecessary check from intersection code
* Allow env overrides on settings
* remove the term app name
* Remove app keyword for client instead and use Name instead of ExternalServiceName
* LogID remove ExternalService ref
* Use Name instead of ExternalServiceName
* Imports order
* Inline
* Using ExternalService and ExternalServiceDTO
* Remove xorm tags
* comment
* Rename client files
* client -> external service
* comments
* Move test to correct package
* slimmer test
* cachedUser -> cachedExternalService
* Fix aggregate store test
* PluginAuthSession -> AuthSession
* Revert the nil cehcks
* Remove unecessary extra
* Removing custom session
* fix typo in test
* Use constants for tests
* Simplify HandleToken tests
* Refactor the HandleTokenRequest test
* test message
* Review test
* Prevent flacky test on client as well
* go imports
* Revert changes from 526e48ad45
* AuthN: Change the External Service registration form (#68649)
* AuthN: change the External Service registration form
* Gen default permissions
* Change demo script registration form
* Remove unecessary comment
* Nit.
* Reduce cyclomatic complexity
* Remove demo_scripts
* Handle case with no service account
* Comments
* Group key gen
* Nit.
* Check the SaveExternalService test
* Rename cachedUser to cachedClient in test
* One more test case to database test
* Comments
* Remove last org scope
Co-authored-by: Mihály Gyöngyösi <mgyongyosi@users.noreply.github.com>
* Update pkg/services/oauthserver/utils/utils_test.go
* Update pkg/services/sqlstore/migrations/oauthserver/migrations.go
Remove comment
* Update pkg/setting/setting.go
Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
---------
Co-authored-by: Mihály Gyöngyösi <mgyongyosi@users.noreply.github.com>
546 lines
21 KiB
Go
546 lines
21 KiB
Go
package oasimpl
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ory/fosite"
|
|
"github.com/ory/fosite/storage"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/exp/slices"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/localcache"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/models/roletype"
|
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/services/oauthserver"
|
|
"github.com/grafana/grafana/pkg/services/oauthserver/oastest"
|
|
sa "github.com/grafana/grafana/pkg/services/serviceaccounts"
|
|
satests "github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
|
|
"github.com/grafana/grafana/pkg/services/team/teamtest"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/services/user/usertest"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
const (
|
|
AppURL = "https://oauth.test/"
|
|
TokenURL = AppURL + "oauth2/token"
|
|
)
|
|
|
|
var (
|
|
pk, _ = rsa.GenerateKey(rand.Reader, 4096)
|
|
Client1Key, _ = rsa.GenerateKey(rand.Reader, 4096)
|
|
)
|
|
|
|
type TestEnv struct {
|
|
S *OAuth2ServiceImpl
|
|
Cfg *setting.Cfg
|
|
AcStore *actest.MockStore
|
|
OAuthStore *oastest.MockStore
|
|
UserService *usertest.FakeUserService
|
|
TeamService *teamtest.FakeService
|
|
SAService *satests.MockServiceAccountService
|
|
}
|
|
|
|
func setupTestEnv(t *testing.T) *TestEnv {
|
|
t.Helper()
|
|
|
|
cfg := setting.NewCfg()
|
|
cfg.AppURL = AppURL
|
|
|
|
config := &fosite.Config{
|
|
AccessTokenLifespan: time.Hour,
|
|
TokenURL: TokenURL,
|
|
AccessTokenIssuer: AppURL,
|
|
IDTokenIssuer: AppURL,
|
|
ScopeStrategy: fosite.WildcardScopeStrategy,
|
|
}
|
|
|
|
fmgt := featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAuth)
|
|
|
|
env := &TestEnv{
|
|
Cfg: cfg,
|
|
AcStore: &actest.MockStore{},
|
|
OAuthStore: &oastest.MockStore{},
|
|
UserService: usertest.NewUserServiceFake(),
|
|
TeamService: teamtest.NewFakeService(),
|
|
SAService: &satests.MockServiceAccountService{},
|
|
}
|
|
env.S = &OAuth2ServiceImpl{
|
|
cache: localcache.New(cacheExpirationTime, cacheCleanupInterval),
|
|
cfg: cfg,
|
|
accessControl: acimpl.ProvideAccessControl(cfg),
|
|
acService: acimpl.ProvideOSSService(cfg, env.AcStore, localcache.New(0, 0), fmgt),
|
|
memstore: storage.NewMemoryStore(),
|
|
sqlstore: env.OAuthStore,
|
|
logger: log.New("oauthserver.test"),
|
|
userService: env.UserService,
|
|
saService: env.SAService,
|
|
teamService: env.TeamService,
|
|
publicKey: &pk.PublicKey,
|
|
}
|
|
env.S.oauthProvider = newProvider(config, env.S, pk)
|
|
|
|
return env
|
|
}
|
|
|
|
func TestOAuth2ServiceImpl_SaveExternalService(t *testing.T) {
|
|
const serviceName = "my-ext-service"
|
|
|
|
sa1 := sa.ServiceAccountDTO{Id: 1, Name: serviceName, Login: serviceName, OrgId: oauthserver.TmpOrgID, IsDisabled: false, Role: "Viewer"}
|
|
sa1Profile := sa.ServiceAccountProfileDTO{Id: 1, Name: serviceName, Login: serviceName, OrgId: oauthserver.TmpOrgID, IsDisabled: false, Role: "Viewer"}
|
|
prevSaID := int64(3)
|
|
// Using a function to prevent modifying the same object in the tests
|
|
client1 := func() *oauthserver.ExternalService {
|
|
return &oauthserver.ExternalService{
|
|
Name: serviceName,
|
|
ClientID: "RANDOMID",
|
|
Secret: "RANDOMSECRET",
|
|
GrantTypes: "client_credentials",
|
|
PublicPem: []byte("-----BEGIN PUBLIC KEY-----"),
|
|
ServiceAccountID: prevSaID,
|
|
SelfPermissions: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}},
|
|
}
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
init func(*TestEnv)
|
|
cmd *oauthserver.ExternalServiceRegistration
|
|
mockChecks func(*testing.T, *TestEnv)
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "should create a new client without permissions",
|
|
init: func(env *TestEnv) {
|
|
// No client at the beginning
|
|
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName))
|
|
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
|
},
|
|
cmd: &oauthserver.ExternalServiceRegistration{
|
|
Name: serviceName,
|
|
Key: &oauthserver.KeyOption{Generate: true},
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
env.OAuthStore.AssertCalled(t, "GetExternalServiceByName", mock.Anything, mock.MatchedBy(func(name string) bool {
|
|
return name == serviceName
|
|
}))
|
|
env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.ExternalService) bool {
|
|
return client.Name == serviceName && client.ClientID != "" && client.Secret != "" &&
|
|
len(client.GrantTypes) == 0 && len(client.PublicPem) > 0 && client.ServiceAccountID == 0 &&
|
|
len(client.ImpersonatePermissions) == 0
|
|
}))
|
|
},
|
|
},
|
|
{
|
|
name: "should create a service account",
|
|
init: func(env *TestEnv) {
|
|
// No client at the beginning
|
|
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName))
|
|
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
|
// Service account and permission creation
|
|
env.SAService.On("CreateServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1, nil)
|
|
env.AcStore.On("SaveExternalServiceRole", mock.Anything, mock.Anything).Return(nil)
|
|
},
|
|
cmd: &oauthserver.ExternalServiceRegistration{
|
|
Name: serviceName,
|
|
Key: &oauthserver.KeyOption{Generate: true},
|
|
Self: oauthserver.SelfCfg{
|
|
Enabled: true,
|
|
Permissions: []ac.Permission{{Action: "users:read", Scope: "users:*"}},
|
|
},
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
// Check that the client has a service account and the correct grant type
|
|
env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.ExternalService) bool {
|
|
return client.Name == serviceName &&
|
|
client.GrantTypes == "client_credentials" && client.ServiceAccountID == sa1.Id
|
|
}))
|
|
// Check that the service account is created in the correct org with the correct role
|
|
env.SAService.AssertCalled(t, "CreateServiceAccount", mock.Anything,
|
|
mock.MatchedBy(func(orgID int64) bool { return orgID == oauthserver.TmpOrgID }),
|
|
mock.MatchedBy(func(cmd *sa.CreateServiceAccountForm) bool {
|
|
return cmd.Name == serviceName && *cmd.Role == roletype.RoleViewer
|
|
}),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "should delete the service account",
|
|
init: func(env *TestEnv) {
|
|
// Existing client (with a service account hence a role)
|
|
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(client1(), nil)
|
|
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
|
env.SAService.On("RetrieveServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1Profile, nil)
|
|
// No permission anymore will trigger deletion of the service account and its role
|
|
env.SAService.On("DeleteServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
env.AcStore.On("DeleteExternalServiceRole", mock.Anything, mock.Anything).Return(nil)
|
|
},
|
|
cmd: &oauthserver.ExternalServiceRegistration{
|
|
Name: serviceName,
|
|
Key: &oauthserver.KeyOption{Generate: true},
|
|
Self: oauthserver.SelfCfg{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
// Check that the service has no service account anymore
|
|
env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.ExternalService) bool {
|
|
return client.Name == serviceName && client.ServiceAccountID == oauthserver.NoServiceAccountID
|
|
}))
|
|
// Check that the service account is retrieved with the correct ID
|
|
env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything,
|
|
mock.MatchedBy(func(orgID int64) bool { return orgID == oauthserver.TmpOrgID }),
|
|
mock.MatchedBy(func(saID int64) bool { return saID == prevSaID }))
|
|
// Check that the service account is deleted in the correct org
|
|
env.SAService.AssertCalled(t, "DeleteServiceAccount", mock.Anything,
|
|
mock.MatchedBy(func(orgID int64) bool { return orgID == oauthserver.TmpOrgID }),
|
|
mock.MatchedBy(func(saID int64) bool { return saID == sa1.Id }))
|
|
// Check that the associated role is deleted
|
|
env.AcStore.AssertCalled(t, "DeleteExternalServiceRole", mock.Anything,
|
|
mock.MatchedBy(func(extSvcName string) bool { return extSvcName == serviceName }))
|
|
},
|
|
},
|
|
{
|
|
name: "should update the service account",
|
|
init: func(env *TestEnv) {
|
|
// Existing client (with a service account hence a role)
|
|
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(client1(), nil)
|
|
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
|
env.SAService.On("RetrieveServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1Profile, nil)
|
|
// Update the service account permissions
|
|
env.AcStore.On("SaveExternalServiceRole", mock.Anything, mock.Anything).Return(nil)
|
|
},
|
|
cmd: &oauthserver.ExternalServiceRegistration{
|
|
Name: serviceName,
|
|
Key: &oauthserver.KeyOption{Generate: true},
|
|
Self: oauthserver.SelfCfg{
|
|
Enabled: true,
|
|
Permissions: []ac.Permission{{Action: "dashboards:create", Scope: "folders:uid:general"}},
|
|
},
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
// Ensure new permissions are in place
|
|
env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything,
|
|
mock.MatchedBy(func(cmd ac.SaveExternalServiceRoleCommand) bool {
|
|
return cmd.ServiceAccountID == sa1.Id && cmd.ExternalServiceID == client1().Name &&
|
|
cmd.OrgID == int64(ac.GlobalOrgID) && len(cmd.Permissions) == 1 &&
|
|
cmd.Permissions[0] == ac.Permission{Action: "dashboards:create", Scope: "folders:uid:general"}
|
|
}))
|
|
},
|
|
},
|
|
{
|
|
name: "should allow jwt bearer grant and set default permissions",
|
|
init: func(env *TestEnv) {
|
|
// No client at the beginning
|
|
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName))
|
|
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
|
// The service account needs to be created with a permission to impersonate users
|
|
env.SAService.On("CreateServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1, nil)
|
|
env.AcStore.On("SaveExternalServiceRole", mock.Anything, mock.Anything).Return(nil)
|
|
},
|
|
cmd: &oauthserver.ExternalServiceRegistration{
|
|
Name: serviceName,
|
|
Key: &oauthserver.KeyOption{Generate: true},
|
|
Impersonation: oauthserver.ImpersonationCfg{
|
|
Enabled: true,
|
|
Groups: true,
|
|
Permissions: []ac.Permission{{Action: "dashboards:read", Scope: "dashboards:*"}},
|
|
},
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
// Check that the external service impersonate permissions contains the default permissions required to populate the access token
|
|
env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.ExternalService) bool {
|
|
impPerm := client.ImpersonatePermissions
|
|
return slices.Contains(impPerm, ac.Permission{Action: "dashboards:read", Scope: "dashboards:*"}) &&
|
|
slices.Contains(impPerm, ac.Permission{Action: ac.ActionUsersRead, Scope: oauthserver.ScopeGlobalUsersSelf}) &&
|
|
slices.Contains(impPerm, ac.Permission{Action: ac.ActionUsersPermissionsRead, Scope: oauthserver.ScopeUsersSelf}) &&
|
|
slices.Contains(impPerm, ac.Permission{Action: ac.ActionTeamsRead, Scope: oauthserver.ScopeTeamsSelf})
|
|
}))
|
|
// Check that despite no credential_grants the service account still has a permission to impersonate users
|
|
env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything,
|
|
mock.MatchedBy(func(cmd ac.SaveExternalServiceRoleCommand) bool {
|
|
return len(cmd.Permissions) == 1 && cmd.Permissions[0] == ac.Permission{Action: ac.ActionUsersImpersonate, Scope: ac.ScopeUsersAll}
|
|
}))
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
env := setupTestEnv(t)
|
|
if tt.init != nil {
|
|
tt.init(env)
|
|
}
|
|
|
|
dto, err := env.S.SaveExternalService(context.Background(), tt.cmd)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
// Check that we generated client ID and secret
|
|
require.NotEmpty(t, dto.ID)
|
|
require.NotEmpty(t, dto.Secret)
|
|
|
|
// Check that we have generated keys and that we correctly return them
|
|
if tt.cmd.Key != nil && tt.cmd.Key.Generate {
|
|
require.NotNil(t, dto.KeyResult)
|
|
require.True(t, dto.KeyResult.Generated)
|
|
require.NotEmpty(t, dto.KeyResult.PublicPem)
|
|
require.NotEmpty(t, dto.KeyResult.PrivatePem)
|
|
}
|
|
|
|
// Check that we computed grant types and created or updated the service account
|
|
if tt.cmd.Self.Enabled {
|
|
require.NotNil(t, dto.GrantTypes)
|
|
require.Contains(t, dto.GrantTypes, fosite.GrantTypeClientCredentials, "grant types should contain client_credentials")
|
|
} else {
|
|
require.NotContains(t, dto.GrantTypes, fosite.GrantTypeClientCredentials, "grant types should not contain client_credentials")
|
|
}
|
|
// Check that we updated grant types
|
|
if tt.cmd.Impersonation.Enabled {
|
|
require.NotNil(t, dto.GrantTypes)
|
|
require.Contains(t, dto.GrantTypes, fosite.GrantTypeJWTBearer, "grant types should contain JWT Bearer grant")
|
|
} else {
|
|
require.NotContains(t, dto.GrantTypes, fosite.GrantTypeJWTBearer, "grant types should not contain JWT Bearer grant")
|
|
}
|
|
|
|
// Check that mocks were called as expected
|
|
env.OAuthStore.AssertExpectations(t)
|
|
env.SAService.AssertExpectations(t)
|
|
env.AcStore.AssertExpectations(t)
|
|
|
|
// Additional checks performed
|
|
if tt.mockChecks != nil {
|
|
tt.mockChecks(t, env)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOAuth2ServiceImpl_GetExternalService(t *testing.T) {
|
|
const serviceName = "my-ext-service"
|
|
|
|
dummyClient := func() *oauthserver.ExternalService {
|
|
return &oauthserver.ExternalService{
|
|
Name: serviceName,
|
|
ClientID: "RANDOMID",
|
|
Secret: "RANDOMSECRET",
|
|
GrantTypes: "client_credentials",
|
|
PublicPem: []byte("-----BEGIN PUBLIC KEY-----"),
|
|
ServiceAccountID: 1,
|
|
}
|
|
}
|
|
cachedClient := &oauthserver.ExternalService{
|
|
Name: serviceName,
|
|
ClientID: "RANDOMID",
|
|
Secret: "RANDOMSECRET",
|
|
GrantTypes: "client_credentials",
|
|
PublicPem: []byte("-----BEGIN PUBLIC KEY-----"),
|
|
ServiceAccountID: 1,
|
|
SelfPermissions: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}},
|
|
SignedInUser: &user.SignedInUser{
|
|
UserID: 1,
|
|
Permissions: map[int64]map[string][]string{
|
|
1: {
|
|
"users:impersonate": {"users:*"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
testCases := []struct {
|
|
name string
|
|
init func(*TestEnv)
|
|
mockChecks func(*testing.T, *TestEnv)
|
|
wantPerm []ac.Permission
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "should hit the cache",
|
|
init: func(env *TestEnv) {
|
|
env.S.cache.Set(serviceName, *cachedClient, time.Minute)
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
env.OAuthStore.AssertNotCalled(t, "GetExternalService", mock.Anything, mock.Anything)
|
|
},
|
|
wantPerm: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}},
|
|
},
|
|
{
|
|
name: "should return error when the client was not found",
|
|
init: func(env *TestEnv) {
|
|
env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName))
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should return error when the service account was not found",
|
|
init: func(env *TestEnv) {
|
|
env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(dummyClient(), nil)
|
|
env.SAService.On("RetrieveServiceAccount", mock.Anything, int64(1), int64(1)).Return(&sa.ServiceAccountProfileDTO{}, sa.ErrServiceAccountNotFound)
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything)
|
|
env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, 1, 1)
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should return error when the service account has no permissions",
|
|
init: func(env *TestEnv) {
|
|
env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(dummyClient(), nil)
|
|
env.SAService.On("RetrieveServiceAccount", mock.Anything, int64(1), int64(1)).Return(&sa.ServiceAccountProfileDTO{}, nil)
|
|
env.AcStore.On("GetUserPermissions", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("some error"))
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything)
|
|
env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, 1, 1)
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should return correctly",
|
|
init: func(env *TestEnv) {
|
|
env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(dummyClient(), nil)
|
|
env.SAService.On("RetrieveServiceAccount", mock.Anything, int64(1), int64(1)).Return(&sa.ServiceAccountProfileDTO{Id: 1}, nil)
|
|
env.AcStore.On("GetUserPermissions", mock.Anything, mock.Anything).Return([]ac.Permission{{Action: ac.ActionUsersImpersonate, Scope: ac.ScopeUsersAll}}, nil)
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything)
|
|
env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, int64(1), int64(1))
|
|
},
|
|
wantPerm: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}},
|
|
},
|
|
{
|
|
name: "should return correctly when the client has no service account",
|
|
init: func(env *TestEnv) {
|
|
client := &oauthserver.ExternalService{
|
|
Name: serviceName,
|
|
ClientID: "RANDOMID",
|
|
Secret: "RANDOMSECRET",
|
|
GrantTypes: "client_credentials",
|
|
PublicPem: []byte("-----BEGIN PUBLIC KEY-----"),
|
|
ServiceAccountID: oauthserver.NoServiceAccountID,
|
|
}
|
|
env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(client, nil)
|
|
},
|
|
mockChecks: func(t *testing.T, env *TestEnv) {
|
|
env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything)
|
|
},
|
|
wantPerm: []ac.Permission{},
|
|
},
|
|
}
|
|
for _, tt := range testCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
env := setupTestEnv(t)
|
|
if tt.init != nil {
|
|
tt.init(env)
|
|
}
|
|
|
|
client, err := env.S.GetExternalService(context.Background(), serviceName)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
if tt.mockChecks != nil {
|
|
tt.mockChecks(t, env)
|
|
}
|
|
|
|
require.Equal(t, serviceName, client.Name)
|
|
require.ElementsMatch(t, client.SelfPermissions, tt.wantPerm)
|
|
assertArrayInMap(t, client.SignedInUser.Permissions[1], ac.GroupScopesByAction(tt.wantPerm))
|
|
|
|
env.OAuthStore.AssertExpectations(t)
|
|
env.SAService.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertArrayInMap[K comparable, V string](t *testing.T, m1 map[K][]V, m2 map[K][]V) {
|
|
for k, v := range m1 {
|
|
require.Contains(t, m2, k)
|
|
require.ElementsMatch(t, v, m2[k])
|
|
}
|
|
}
|
|
|
|
func TestTestOAuth2ServiceImpl_handleKeyOptions(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
keyOption *oauthserver.KeyOption
|
|
expectedResult *oauthserver.KeyResult
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "should return error when the key option is nil",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should return error when the key option is empty",
|
|
keyOption: &oauthserver.KeyOption{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should return successfully when PublicPEM is specified",
|
|
keyOption: &oauthserver.KeyOption{
|
|
PublicPEM: base64.StdEncoding.EncodeToString([]byte(`-----BEGIN PUBLIC KEY-----
|
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbsGtoGJTopAIbhqy49/vyCJuDot+
|
|
mgGaC8vUIigFQVsVB+v/HZ4yG1Rcvysig+tyNk1dZQpozpFc2dGmzHlGhw==
|
|
-----END PUBLIC KEY-----`)),
|
|
},
|
|
wantErr: false,
|
|
expectedResult: &oauthserver.KeyResult{
|
|
PublicPem: `-----BEGIN PUBLIC KEY-----
|
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbsGtoGJTopAIbhqy49/vyCJuDot+
|
|
mgGaC8vUIigFQVsVB+v/HZ4yG1Rcvysig+tyNk1dZQpozpFc2dGmzHlGhw==
|
|
-----END PUBLIC KEY-----`,
|
|
Generated: false,
|
|
PrivatePem: "",
|
|
URL: "",
|
|
},
|
|
},
|
|
}
|
|
env := setupTestEnv(t)
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := env.S.handleKeyOptions(context.Background(), tc.keyOption)
|
|
if tc.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedResult, result)
|
|
})
|
|
}
|
|
|
|
t.Run("should generate an ECDSA key pair (default) when generate key option is specified", func(t *testing.T) {
|
|
result, err := env.S.handleKeyOptions(context.Background(), &oauthserver.KeyOption{Generate: true})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result.PrivatePem)
|
|
require.NotNil(t, result.PublicPem)
|
|
require.True(t, result.Generated)
|
|
})
|
|
|
|
t.Run("should generate an RSA key pair when generate key option is specified", func(t *testing.T) {
|
|
env.S.cfg.OAuth2ServerGeneratedKeyTypeForClient = "RSA"
|
|
result, err := env.S.handleKeyOptions(context.Background(), &oauthserver.KeyOption{Generate: true})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result.PrivatePem)
|
|
require.NotNil(t, result.PublicPem)
|
|
require.True(t, result.Generated)
|
|
})
|
|
}
|