* 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>
220 lines
6.4 KiB
Go
220 lines
6.4 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/rsa"
|
|
"errors"
|
|
|
|
"gopkg.in/square/go-jose.v2"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/services/oauthserver"
|
|
"github.com/grafana/grafana/pkg/services/oauthserver/utils"
|
|
"github.com/grafana/grafana/pkg/util/errutil"
|
|
)
|
|
|
|
type store struct {
|
|
db db.DB
|
|
}
|
|
|
|
func NewStore(db db.DB) oauthserver.Store {
|
|
return &store{db: db}
|
|
}
|
|
|
|
func createImpersonatePermissions(sess *db.Session, client *oauthserver.ExternalService) error {
|
|
if len(client.ImpersonatePermissions) == 0 {
|
|
return nil
|
|
}
|
|
|
|
insertPermQuery := make([]interface{}, 1, len(client.ImpersonatePermissions)*3+1)
|
|
insertPermStmt := `INSERT INTO oauth_impersonate_permission (client_id, action, scope) VALUES `
|
|
for _, perm := range client.ImpersonatePermissions {
|
|
insertPermStmt += "(?, ?, ?),"
|
|
insertPermQuery = append(insertPermQuery, client.ClientID, perm.Action, perm.Scope)
|
|
}
|
|
insertPermQuery[0] = insertPermStmt[:len(insertPermStmt)-1]
|
|
_, err := sess.Exec(insertPermQuery...)
|
|
return err
|
|
}
|
|
|
|
func registerExternalService(sess *db.Session, client *oauthserver.ExternalService) error {
|
|
insertQuery := []interface{}{
|
|
`INSERT INTO oauth_client (name, client_id, secret, grant_types, audiences, service_account_id, public_pem, redirect_uri) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
client.Name,
|
|
client.ClientID,
|
|
client.Secret,
|
|
client.GrantTypes,
|
|
client.Audiences,
|
|
client.ServiceAccountID,
|
|
client.PublicPem,
|
|
client.RedirectURI,
|
|
}
|
|
if _, err := sess.Exec(insertQuery...); err != nil {
|
|
return err
|
|
}
|
|
|
|
return createImpersonatePermissions(sess, client)
|
|
}
|
|
|
|
func (s *store) RegisterExternalService(ctx context.Context, client *oauthserver.ExternalService) error {
|
|
return s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
|
return registerExternalService(sess, client)
|
|
})
|
|
}
|
|
|
|
func recreateImpersonatePermissions(sess *db.Session, client *oauthserver.ExternalService, prevClientID string) error {
|
|
deletePermQuery := `DELETE FROM oauth_impersonate_permission WHERE client_id = ?`
|
|
if _, errDelPerm := sess.Exec(deletePermQuery, prevClientID); errDelPerm != nil {
|
|
return errDelPerm
|
|
}
|
|
|
|
if len(client.ImpersonatePermissions) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return createImpersonatePermissions(sess, client)
|
|
}
|
|
|
|
func updateExternalService(sess *db.Session, client *oauthserver.ExternalService, prevClientID string) error {
|
|
updateQuery := []interface{}{
|
|
`UPDATE oauth_client SET client_id = ?, secret = ?, grant_types = ?, audiences = ?, service_account_id = ?, public_pem = ?, redirect_uri = ? WHERE name = ?`,
|
|
client.ClientID,
|
|
client.Secret,
|
|
client.GrantTypes,
|
|
client.Audiences,
|
|
client.ServiceAccountID,
|
|
client.PublicPem,
|
|
client.RedirectURI,
|
|
client.Name,
|
|
}
|
|
if _, err := sess.Exec(updateQuery...); err != nil {
|
|
return err
|
|
}
|
|
|
|
return recreateImpersonatePermissions(sess, client, prevClientID)
|
|
}
|
|
|
|
func (s *store) SaveExternalService(ctx context.Context, client *oauthserver.ExternalService) error {
|
|
if client.Name == "" {
|
|
return oauthserver.ErrClientRequiredName
|
|
}
|
|
return s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
|
previous, errFetchExtSvc := getExternalServiceByName(sess, client.Name)
|
|
if errFetchExtSvc != nil {
|
|
var srcError errutil.Error
|
|
if errors.As(errFetchExtSvc, &srcError) {
|
|
if srcError.MessageID != oauthserver.ErrClientNotFoundMessageID {
|
|
return errFetchExtSvc
|
|
}
|
|
}
|
|
}
|
|
if previous == nil {
|
|
return registerExternalService(sess, client)
|
|
}
|
|
return updateExternalService(sess, client, previous.ClientID)
|
|
})
|
|
}
|
|
|
|
func (s *store) GetExternalService(ctx context.Context, id string) (*oauthserver.ExternalService, error) {
|
|
res := &oauthserver.ExternalService{}
|
|
if id == "" {
|
|
return nil, oauthserver.ErrClientRequiredID
|
|
}
|
|
|
|
err := s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
|
getClientQuery := `SELECT
|
|
id, name, client_id, secret, grant_types, audiences, service_account_id, public_pem, redirect_uri
|
|
FROM oauth_client
|
|
WHERE client_id = ?`
|
|
found, err := sess.SQL(getClientQuery, id).Get(res)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return oauthserver.ErrClientNotFound(id)
|
|
}
|
|
|
|
impersonatePermQuery := `SELECT action, scope FROM oauth_impersonate_permission WHERE client_id = ?`
|
|
return sess.SQL(impersonatePermQuery, id).Find(&res.ImpersonatePermissions)
|
|
})
|
|
|
|
return res, err
|
|
}
|
|
|
|
// GetPublicKey returns public key, issued by 'issuer', and assigned for subject. Public key is used to check
|
|
// signature of jwt assertion in authorization grants.
|
|
func (s *store) GetExternalServicePublicKey(ctx context.Context, clientID string) (*jose.JSONWebKey, error) {
|
|
res := &oauthserver.ExternalService{}
|
|
if clientID == "" {
|
|
return nil, oauthserver.ErrClientRequiredID
|
|
}
|
|
|
|
if err := s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
|
getKeyQuery := `SELECT public_pem FROM oauth_client WHERE client_id = ?`
|
|
found, err := sess.SQL(getKeyQuery, clientID).Get(res)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return oauthserver.ErrClientNotFound(clientID)
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key, errParseKey := utils.ParsePublicKeyPem(res.PublicPem)
|
|
if errParseKey != nil {
|
|
return nil, errParseKey
|
|
}
|
|
|
|
var alg string
|
|
switch key.(type) {
|
|
case *rsa.PublicKey:
|
|
alg = oauthserver.RS256
|
|
case *ecdsa.PublicKey:
|
|
alg = oauthserver.ES256
|
|
}
|
|
|
|
return &jose.JSONWebKey{
|
|
Algorithm: alg,
|
|
Key: key,
|
|
}, nil
|
|
}
|
|
|
|
func (s *store) GetExternalServiceByName(ctx context.Context, name string) (*oauthserver.ExternalService, error) {
|
|
res := &oauthserver.ExternalService{}
|
|
if name == "" {
|
|
return nil, oauthserver.ErrClientRequiredName
|
|
}
|
|
|
|
err := s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
|
var errGetByName error
|
|
res, errGetByName = getExternalServiceByName(sess, name)
|
|
return errGetByName
|
|
})
|
|
|
|
return res, err
|
|
}
|
|
|
|
func getExternalServiceByName(sess *db.Session, name string) (*oauthserver.ExternalService, error) {
|
|
res := &oauthserver.ExternalService{}
|
|
getClientQuery := `SELECT
|
|
id, name, client_id, secret, grant_types, audiences, service_account_id, public_pem, redirect_uri
|
|
FROM oauth_client
|
|
WHERE name = ?`
|
|
found, err := sess.SQL(getClientQuery, name).Get(res)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !found {
|
|
return nil, oauthserver.ErrClientNotFound(name)
|
|
}
|
|
|
|
impersonatePermQuery := `SELECT action, scope FROM oauth_impersonate_permission WHERE client_id = ?`
|
|
errPerm := sess.SQL(impersonatePermQuery, res.ClientID).Find(&res.ImpersonatePermissions)
|
|
|
|
return res, errPerm
|
|
}
|