Files
grafana/pkg/services/oauthserver/external_service.go
Gabriel MABILLE edf1775d49 AuthN: Embed an OAuth2 server for external service authentication (#68086)
* 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 526e48ad4550fed7e2b753b9d0a0cc6097155f58

* 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>
2023-05-25 15:38:30 +02:00

162 lines
5.1 KiB
Go

package oauthserver
import (
"context"
"strconv"
"strings"
"github.com/ory/fosite"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/user"
)
type KeyResult struct {
URL string `json:"url,omitempty"`
PrivatePem string `json:"private,omitempty"`
PublicPem string `json:"public,omitempty"`
Generated bool `json:"generated,omitempty"`
}
type ExternalServiceDTO struct {
Name string `json:"name"`
ID string `json:"clientId"`
Secret string `json:"clientSecret"`
RedirectURI string `json:"redirectUri,omitempty"` // Not used yet (code flow)
GrantTypes string `json:"grantTypes"` // CSV value
Audiences string `json:"audiences"` // CSV value
KeyResult *KeyResult `json:"key,omitempty"`
}
type ExternalService struct {
ID int64 `xorm:"id pk autoincr"`
Name string `xorm:"name"`
ClientID string `xorm:"client_id"`
Secret string `xorm:"secret"`
RedirectURI string `xorm:"redirect_uri"` // Not used yet (code flow)
GrantTypes string `xorm:"grant_types"` // CSV value
Audiences string `xorm:"audiences"` // CSV value
PublicPem []byte `xorm:"public_pem"`
ServiceAccountID int64 `xorm:"service_account_id"`
// SelfPermissions are the registered service account permissions (registered and managed permissions)
SelfPermissions []ac.Permission
// ImpersonatePermissions is the restriction set of permissions while impersonating
ImpersonatePermissions []ac.Permission
// SignedInUser refers to the current Service Account identity/user
SignedInUser *user.SignedInUser
Scopes []string
ImpersonateScopes []string
}
func (c *ExternalService) ToDTO() *ExternalServiceDTO {
c2 := ExternalServiceDTO{
Name: c.Name,
ID: c.ClientID,
Secret: c.Secret,
GrantTypes: c.GrantTypes,
Audiences: c.Audiences,
RedirectURI: c.RedirectURI,
}
if len(c.PublicPem) > 0 {
c2.KeyResult = &KeyResult{PublicPem: string(c.PublicPem)}
}
return &c2
}
func (c *ExternalService) LogID() string {
return "{name: " + c.Name + ", clientID: " + c.ClientID + "}"
}
// GetID returns the client ID.
func (c *ExternalService) GetID() string { return c.ClientID }
// GetHashedSecret returns the hashed secret as it is stored in the store.
func (c *ExternalService) GetHashedSecret() []byte {
// Hashed version is stored in the secret field
return []byte(c.Secret)
}
// GetRedirectURIs returns the client's allowed redirect URIs.
func (c *ExternalService) GetRedirectURIs() []string {
return []string{c.RedirectURI}
}
// GetGrantTypes returns the client's allowed grant types.
func (c *ExternalService) GetGrantTypes() fosite.Arguments {
return strings.Split(c.GrantTypes, ",")
}
// GetResponseTypes returns the client's allowed response types.
// All allowed combinations of response types have to be listed, each combination having
// response types of the combination separated by a space.
func (c *ExternalService) GetResponseTypes() fosite.Arguments {
return fosite.Arguments{"code"}
}
// GetScopes returns the scopes this client is allowed to request on its own behalf.
func (c *ExternalService) GetScopes() fosite.Arguments {
if c.Scopes != nil {
return c.Scopes
}
ret := []string{"profile", "email", "groups", "entitlements"}
if c.SignedInUser != nil && c.SignedInUser.Permissions != nil {
perms := c.SignedInUser.Permissions[TmpOrgID]
for action := range perms {
// Add all actions that the plugin is allowed to request
ret = append(ret, action)
}
}
c.Scopes = ret
return ret
}
// GetScopes returns the scopes this client is allowed to request on a specific user.
func (c *ExternalService) GetScopesOnUser(ctx context.Context, accessControl ac.AccessControl, userID int64) []string {
ev := ac.EvalPermission(ac.ActionUsersImpersonate, ac.Scope("users", "id", strconv.FormatInt(userID, 10)))
hasAccess, errAccess := accessControl.Evaluate(ctx, c.SignedInUser, ev)
if errAccess != nil || !hasAccess {
return nil
}
if c.ImpersonateScopes != nil {
return c.ImpersonateScopes
}
ret := []string{}
if c.ImpersonatePermissions != nil {
perms := c.ImpersonatePermissions
for i := range perms {
if perms[i].Action == ac.ActionUsersRead && perms[i].Scope == ScopeGlobalUsersSelf {
ret = append(ret, "profile", "email", ac.ActionUsersRead)
continue
}
if perms[i].Action == ac.ActionUsersPermissionsRead && perms[i].Scope == ScopeUsersSelf {
ret = append(ret, "entitlements", ac.ActionUsersPermissionsRead)
continue
}
if perms[i].Action == ac.ActionTeamsRead && perms[i].Scope == ScopeTeamsSelf {
ret = append(ret, "groups", ac.ActionTeamsRead)
continue
}
// Add all actions that the plugin is allowed to request
ret = append(ret, perms[i].Action)
}
}
c.ImpersonateScopes = ret
return ret
}
// IsPublic returns true, if this client is marked as public.
func (c *ExternalService) IsPublic() bool {
return false
}
// GetAudience returns the allowed audience(s) for this client.
func (c *ExternalService) GetAudience() fosite.Arguments {
return strings.Split(c.Audiences, ",")
}