mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -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>
377 lines
12 KiB
Go
377 lines
12 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/go-jose/go-jose/v3"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/services/oauthserver"
|
|
)
|
|
|
|
func TestStore_RegisterAndGetClient(t *testing.T) {
|
|
s := &store{db: db.InitTestDB(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagExternalServiceAuth}})}
|
|
tests := []struct {
|
|
name string
|
|
client oauthserver.ExternalService
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "register and get",
|
|
client: oauthserver.ExternalService{
|
|
Name: "The Worst App Ever",
|
|
ClientID: "ANonRandomClientID",
|
|
Secret: "ICouldKeepSecrets",
|
|
GrantTypes: "clients_credentials",
|
|
PublicPem: []byte(`------BEGIN FAKE PUBLIC KEY-----
|
|
VGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBO
|
|
b3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNB
|
|
IEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhp
|
|
cyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3Qg
|
|
QW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtl
|
|
eS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJ
|
|
cyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4g
|
|
UlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4g
|
|
VGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBO
|
|
b3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNB
|
|
IEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhp
|
|
cyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3Qg
|
|
QW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtl
|
|
eS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJ
|
|
cyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4g
|
|
UlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4g
|
|
VGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBO
|
|
b3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNB
|
|
IEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4gVGhp
|
|
cyBJcyBOb3QgQW4gUlNBIEtleS4gVGhpcyBJcyBOb3QgQW4gUlNBIEtleS4uLi4gSXQgSXMgSnVz
|
|
dCBBIFJlZ3VsYXIgQmFzZTY0IEVuY29kZWQgU3RyaW5nLi4uCg==
|
|
------END FAKE PUBLIC KEY-----`),
|
|
ServiceAccountID: 2,
|
|
SelfPermissions: nil,
|
|
ImpersonatePermissions: nil,
|
|
RedirectURI: "/whereto",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "register with impersonate permissions and get",
|
|
client: oauthserver.ExternalService{
|
|
Name: "The Best App Ever",
|
|
ClientID: "AnAlmostRandomClientID",
|
|
Secret: "ICannotKeepSecrets",
|
|
GrantTypes: "clients_credentials",
|
|
PublicPem: []byte(`test`),
|
|
ServiceAccountID: 2,
|
|
SelfPermissions: nil,
|
|
ImpersonatePermissions: []accesscontrol.Permission{
|
|
{Action: "dashboards:create", Scope: "folders:*"},
|
|
{Action: "dashboards:read", Scope: "folders:*"},
|
|
{Action: "dashboards:read", Scope: "dashboards:*"},
|
|
{Action: "dashboards:write", Scope: "folders:*"},
|
|
{Action: "dashboards:write", Scope: "dashboards:*"},
|
|
},
|
|
RedirectURI: "/whereto",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "register with audiences and get",
|
|
client: oauthserver.ExternalService{
|
|
Name: "The Most Normal App Ever",
|
|
ClientID: "AnAlmostRandomClientIDAgain",
|
|
Secret: "ICanKeepSecretsEventually",
|
|
GrantTypes: "clients_credentials",
|
|
PublicPem: []byte(`test`),
|
|
ServiceAccountID: 2,
|
|
SelfPermissions: nil,
|
|
Audiences: "https://oauth.test/,https://sub.oauth.test/",
|
|
RedirectURI: "/whereto",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
err := s.RegisterExternalService(ctx, &tt.client)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
// Compare results
|
|
compareClientToStored(t, s, &tt.client)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStore_SaveExternalService(t *testing.T) {
|
|
client1 := oauthserver.ExternalService{
|
|
Name: "my-external-service",
|
|
ClientID: "ClientID",
|
|
Secret: "Secret",
|
|
GrantTypes: "client_credentials",
|
|
PublicPem: []byte("test"),
|
|
ServiceAccountID: 2,
|
|
ImpersonatePermissions: []accesscontrol.Permission{},
|
|
RedirectURI: "/whereto",
|
|
}
|
|
client1WithPerm := client1
|
|
client1WithPerm.ImpersonatePermissions = []accesscontrol.Permission{
|
|
{Action: "dashboards:read", Scope: "folders:*"},
|
|
{Action: "dashboards:read", Scope: "dashboards:*"},
|
|
}
|
|
client1WithNewSecrets := client1
|
|
client1WithNewSecrets.ClientID = "NewClientID"
|
|
client1WithNewSecrets.Secret = "NewSecret"
|
|
client1WithNewSecrets.PublicPem = []byte("newtest")
|
|
|
|
client1WithAud := client1
|
|
client1WithAud.Audiences = "https://oauth.test/,https://sub.oauth.test/"
|
|
|
|
tests := []struct {
|
|
name string
|
|
runs []oauthserver.ExternalService
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "error no name",
|
|
runs: []oauthserver.ExternalService{{}},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "simple register",
|
|
runs: []oauthserver.ExternalService{client1},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "no update",
|
|
runs: []oauthserver.ExternalService{client1, client1},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "add permissions",
|
|
runs: []oauthserver.ExternalService{client1, client1WithPerm},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "remove permissions",
|
|
runs: []oauthserver.ExternalService{client1WithPerm, client1},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "update id and secrets",
|
|
runs: []oauthserver.ExternalService{client1, client1WithNewSecrets},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "update audience",
|
|
runs: []oauthserver.ExternalService{client1, client1WithAud},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
s := &store{db: db.InitTestDB(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagExternalServiceAuth}})}
|
|
for i := range tt.runs {
|
|
err := s.SaveExternalService(context.Background(), &tt.runs[i])
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
compareClientToStored(t, s, &tt.runs[i])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStore_GetExternalServiceByName(t *testing.T) {
|
|
client1 := oauthserver.ExternalService{
|
|
Name: "my-external-service",
|
|
ClientID: "ClientID",
|
|
Secret: "Secret",
|
|
GrantTypes: "client_credentials",
|
|
PublicPem: []byte("test"),
|
|
ServiceAccountID: 2,
|
|
ImpersonatePermissions: []accesscontrol.Permission{},
|
|
RedirectURI: "/whereto",
|
|
}
|
|
client2 := oauthserver.ExternalService{
|
|
Name: "my-external-service-2",
|
|
ClientID: "ClientID2",
|
|
Secret: "Secret2",
|
|
GrantTypes: "client_credentials,urn:ietf:params:grant-type:jwt-bearer",
|
|
PublicPem: []byte("test2"),
|
|
ServiceAccountID: 3,
|
|
Audiences: "https://oauth.test/,https://sub.oauth.test/",
|
|
ImpersonatePermissions: []accesscontrol.Permission{
|
|
{Action: "dashboards:read", Scope: "folders:*"},
|
|
{Action: "dashboards:read", Scope: "dashboards:*"},
|
|
},
|
|
RedirectURI: "/whereto",
|
|
}
|
|
s := &store{db: db.InitTestDB(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagExternalServiceAuth}})}
|
|
require.NoError(t, s.SaveExternalService(context.Background(), &client1))
|
|
require.NoError(t, s.SaveExternalService(context.Background(), &client2))
|
|
|
|
tests := []struct {
|
|
name string
|
|
search string
|
|
want *oauthserver.ExternalService
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "no name provided",
|
|
search: "",
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "not found",
|
|
search: "unknown-external-service",
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "search client 1 by name",
|
|
search: "my-external-service",
|
|
want: &client1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "search client 2 by name",
|
|
search: "my-external-service-2",
|
|
want: &client2,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
stored, err := s.GetExternalServiceByName(context.Background(), tt.search)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
compareClients(t, stored, tt.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStore_GetExternalServicePublicKey(t *testing.T) {
|
|
clientID := "ClientID"
|
|
createClient := func(clientID string, publicPem string) *oauthserver.ExternalService {
|
|
return &oauthserver.ExternalService{
|
|
Name: "my-external-service",
|
|
ClientID: clientID,
|
|
Secret: "Secret",
|
|
GrantTypes: "client_credentials",
|
|
PublicPem: []byte(publicPem),
|
|
ServiceAccountID: 2,
|
|
ImpersonatePermissions: []accesscontrol.Permission{},
|
|
RedirectURI: "/whereto",
|
|
}
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
client *oauthserver.ExternalService
|
|
clientID string
|
|
want *jose.JSONWebKey
|
|
wantKeyType string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "should return an error when clientID is empty",
|
|
clientID: "",
|
|
client: createClient(clientID, ""),
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should return an error when the client was not found",
|
|
clientID: "random",
|
|
client: createClient(clientID, ""),
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should return an error when PublicPem is not valid",
|
|
clientID: clientID,
|
|
client: createClient(clientID, ""),
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should return the JSON Web Key ES256",
|
|
clientID: clientID,
|
|
client: createClient(clientID, `-----BEGIN PUBLIC KEY-----
|
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbsGtoGJTopAIbhqy49/vyCJuDot+
|
|
mgGaC8vUIigFQVsVB+v/HZ4yG1Rcvysig+tyNk1dZQpozpFc2dGmzHlGhw==
|
|
-----END PUBLIC KEY-----`),
|
|
wantKeyType: oauthserver.ES256,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "should return the JSON Web Key RS256",
|
|
clientID: clientID,
|
|
client: createClient(clientID, `-----BEGIN RSA PUBLIC KEY-----
|
|
MIIBCgKCAQEAxkly/cHvsxd6EcShGUlFAB5lIMlIbGRocCVWbIM26f6pnGr+gCNv
|
|
s365DQdQ/jUjF8bSEQM+EtjGlv2Y7Jm7dQROpPzX/1M+53Us/Gl138UtAEgL5ZKe
|
|
SKN5J/f9Nx4wkgb99v2Bt0nz6xv+kSJwgR0o8zi8shDR5n7a5mTdlQe2NOixzWlT
|
|
vnpp6Tm+IE+XyXXcrCr01I9Rf+dKuYOPSJ1K3PDgFmmGvsLcjRCCK9EftfY0keU+
|
|
IP+sh8ewNxc6KcaLBXm3Tadb1c/HyuMi6FyYw7s9m8tyAvI1CMBAcXqLIEaRgNrc
|
|
vuO8AU0bVoUmYMKhozkcCYHudkeS08hEjQIDAQAB
|
|
-----END RSA PUBLIC KEY-----`),
|
|
wantKeyType: oauthserver.RS256,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
s := &store{db: db.InitTestDB(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagExternalServiceAuth}})}
|
|
require.NoError(t, s.SaveExternalService(context.Background(), tc.client))
|
|
|
|
webKey, err := s.GetExternalServicePublicKey(context.Background(), tc.clientID)
|
|
if tc.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tc.wantKeyType, webKey.Algorithm)
|
|
})
|
|
}
|
|
}
|
|
|
|
func compareClientToStored(t *testing.T, s *store, wanted *oauthserver.ExternalService) {
|
|
ctx := context.Background()
|
|
stored, err := s.GetExternalService(ctx, wanted.ClientID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, stored)
|
|
|
|
compareClients(t, stored, wanted)
|
|
}
|
|
|
|
func compareClients(t *testing.T, stored *oauthserver.ExternalService, wanted *oauthserver.ExternalService) {
|
|
// Reset ID so we can compare
|
|
require.NotZero(t, stored.ID)
|
|
stored.ID = 0
|
|
|
|
// Compare permissions separately
|
|
wantedPerms := wanted.ImpersonatePermissions
|
|
storedPerms := stored.ImpersonatePermissions
|
|
wanted.ImpersonatePermissions = nil
|
|
stored.ImpersonatePermissions = nil
|
|
require.EqualValues(t, *wanted, *stored)
|
|
require.ElementsMatch(t, wantedPerms, storedPerms)
|
|
}
|