Files
grafana/pkg/services/accesscontrol/accesscontrol.go

428 lines
13 KiB
Go
Raw Normal View History

package accesscontrol
import (
"context"
"fmt"
"strings"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
)
type AccessControl interface {
// Evaluate evaluates access to the given resources.
Evaluate(ctx context.Context, user identity.Requester, evaluator Evaluator) (bool, error)
// RegisterScopeAttributeResolver allows the caller to register a scope resolver for a
// specific scope prefix (ex: datasources:name:)
RegisterScopeAttributeResolver(prefix string, resolver ScopeAttributeResolver)
}
type Service interface {
registry.ProvidesUsageStats
// GetUserPermissions returns user permissions with only action and scope fields set.
GetUserPermissions(ctx context.Context, user identity.Requester, options Options) ([]Permission, error)
// SearchUsersPermissions returns all users' permissions filtered by an action prefix
SearchUsersPermissions(ctx context.Context, user identity.Requester, options SearchOptions) (map[int64][]Permission, error)
// ClearUserPermissionCache removes the permission cache entry for the given user
ClearUserPermissionCache(user identity.Requester)
// SearchUserPermissions returns single user's permissions filtered by an action prefix or an action
SearchUserPermissions(ctx context.Context, orgID int64, filterOptions SearchOptions) ([]Permission, error)
// DeleteUserPermissions removes all permissions user has in org and all permission to that user
// If orgID is set to 0 remove permissions from all orgs
DeleteUserPermissions(ctx context.Context, orgID, userID int64) error
// DeclareFixedRoles allows the caller to declare, to the service, fixed roles and their
// assignments to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin"
DeclareFixedRoles(registrations ...RoleRegistration) error
// SaveExternalServiceRole creates or updates an external service's role and assigns it to a given service account id.
SaveExternalServiceRole(ctx context.Context, cmd SaveExternalServiceRoleCommand) error
// DeleteExternalServiceRole removes an external service's role and its assignment.
DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error
// SyncUserRoles adds provided roles to user
SyncUserRoles(ctx context.Context, orgID int64, cmd SyncUserRolesCommand) error
}
type RoleRegistry interface {
// RegisterFixedRoles registers all roles declared to AccessControl
RegisterFixedRoles(ctx context.Context) error
}
type Options struct {
ReloadCache bool
}
type SearchOptions struct {
ActionPrefix string // Needed for the PoC v1, it's probably going to be removed.
Action string
Scope string
UserLogin string // Login for which to return information, if none is specified information is returned for all users.
UserID int64 // ID for the user for which to return information, if none is specified information is returned for all users.
wildcards Wildcards // private field computed based on the Scope
}
// Wildcards computes the wildcard scopes that include the scope
func (s *SearchOptions) Wildcards() []string {
if s.wildcards != nil {
return s.wildcards
}
if s.Scope == "" {
s.wildcards = []string{}
return s.wildcards
}
s.wildcards = WildcardsFromPrefix(ScopePrefix(s.Scope))
return s.wildcards
}
func (s *SearchOptions) ResolveUserLogin(ctx context.Context, userSvc user.Service) error {
if s.UserLogin == "" {
return nil
}
// Resolve userLogin -> userID
dbUsr, err := userSvc.GetByLogin(ctx, &user.GetUserByLoginQuery{LoginOrEmail: s.UserLogin})
if err != nil {
return err
}
s.UserID = dbUsr.ID
return nil
}
type SyncUserRolesCommand struct {
UserID int64
// name of roles the user should have
RolesToAdd []string
// name of roles the user should not have
RolesToRemove []string
}
type TeamPermissionsService interface {
GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]ResourcePermission, error)
SetUserPermission(ctx context.Context, orgID int64, user User, resourceID, permission string) (*ResourcePermission, error)
}
type FolderPermissionsService interface {
PermissionsService
}
type DashboardPermissionsService interface {
PermissionsService
}
type DatasourcePermissionsService interface {
PermissionsService
}
type ServiceAccountPermissionsService interface {
PermissionsService
}
type PermissionsService interface {
// GetPermissions returns all permissions for given resourceID
GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]ResourcePermission, error)
// SetUserPermission sets permission on resource for a user
SetUserPermission(ctx context.Context, orgID int64, user User, resourceID, permission string) (*ResourcePermission, error)
// SetTeamPermission sets permission on resource for a team
SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*ResourcePermission, error)
// SetBuiltInRolePermission sets permission on resource for a built-in role (Admin, Editor, Viewer)
SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole string, resourceID string, permission string) (*ResourcePermission, error)
// SetPermissions sets several permissions on resource for either built-in role, team or user
SetPermissions(ctx context.Context, orgID int64, resourceID string, commands ...SetResourcePermissionCommand) ([]ResourcePermission, error)
Access control: Use access control for dashboard and folder (#44702) * Add actions and scopes * add resource service for dashboard and folder * Add dashboard guardian with fgac permission evaluation * Add CanDelete function to guardian interface * Add CanDelete property to folder and dashboard dto and set values * change to correct function name * Add accesscontrol to folder endpoints * add access control to dashboard endpoints * check access for nav links * Add fixed roles for dashboard and folders * use correct package * add hack to override guardian Constructor if accesscontrol is enabled * Add services * Add function to handle api backward compatability * Add permissionServices to HttpServer * Set permission when new dashboard is created * Add default permission when creating new dashboard * Set default permission when creating folder and dashboard * Add access control filter for dashboard search * Add to accept list * Add accesscontrol to dashboardimport * Disable access control in tests * Add check to see if user is allow to create a dashboard * Use SetPermissions * Use function to set several permissions at once * remove permissions for folder and dashboard on delete * update required permission * set permission for provisioning * Add CanCreate to dashboard guardian and set correct permisisons for provisioning * Dont set admin on folder / dashboard creation * Add dashboard and folder permission migrations * Add tests for CanCreate * Add roles and update descriptions * Solve uid to id for dashboard and folder permissions * Add folder and dashboard actions to permission filter * Handle viewer_can_edit flag * set folder and dashboard permissions services * Add dashboard permissions when importing a new dashboard * Set access control permissions on provisioning * Pass feature flags and only set permissions if access control is enabled * only add default permissions for folders and dashboards without folders * Batch create permissions in migrations * Remove `dashboards:edit` action * Remove unused function from interface * Update pkg/services/guardian/accesscontrol_guardian_test.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-03-03 15:05:47 +01:00
// MapActions will map actions for a ResourcePermissions to it's "friendly" name configured in PermissionsToActions map.
MapActions(permission ResourcePermission) string
// DeleteResourcePermissions removes all permissions for a resource
DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error
}
type User struct {
ID int64
IsExternal bool
}
AccessControl: FGAC permissions for orgs endpoint on frontend (#41050) * AccessControl: FGAC permissions for orgs endpoint on frontend Protect org update endpoints add or refactor missing right messages cover org page * removing scopes from orgs * Perform permission control with global org * Perform the error handling in case of 403 * Simplify frontend code by requiring read access for sure * Remove roles I added to decrease the number of changes * Remove the check for server admin to reduce the number of changes * change error message * Cleaning todos * Remove unecessary changes * Fix tests * Update test snapshot * Update pkg/api/roles.go Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update public/app/features/admin/AdminEditOrgPage.tsx Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Format AdminEditOrgPage for linting * Update public/app/features/admin/AdminEditOrgPage.tsx Co-authored-by: Vardan Torosyan <vardants@gmail.com> * Update public/app/features/admin/AdminEditOrgPage.tsx Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> * Update public/app/features/admin/AdminListOrgsPage.tsx Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> * Commit suggestions * Commit suggestion canRead canWrite * fix typo Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Vardan Torosyan <vardants@gmail.com> Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
2021-11-18 14:10:38 +01:00
// HasGlobalAccess checks user access with globally assigned permissions only
func HasGlobalAccess(ac AccessControl, service Service, c *contextmodel.ReqContext) func(evaluator Evaluator) bool {
return func(evaluator Evaluator) bool {
var targetOrgID int64 = GlobalOrgID
tmpUser, err := makeTmpUser(c.Req.Context(), service, nil, nil, c.SignedInUser, targetOrgID)
if err != nil {
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
}
hasAccess, err := ac.Evaluate(c.Req.Context(), tmpUser, evaluator)
AccessControl: FGAC permissions for orgs endpoint on frontend (#41050) * AccessControl: FGAC permissions for orgs endpoint on frontend Protect org update endpoints add or refactor missing right messages cover org page * removing scopes from orgs * Perform permission control with global org * Perform the error handling in case of 403 * Simplify frontend code by requiring read access for sure * Remove roles I added to decrease the number of changes * Remove the check for server admin to reduce the number of changes * change error message * Cleaning todos * Remove unecessary changes * Fix tests * Update test snapshot * Update pkg/api/roles.go Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update public/app/features/admin/AdminEditOrgPage.tsx Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Format AdminEditOrgPage for linting * Update public/app/features/admin/AdminEditOrgPage.tsx Co-authored-by: Vardan Torosyan <vardants@gmail.com> * Update public/app/features/admin/AdminEditOrgPage.tsx Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> * Update public/app/features/admin/AdminListOrgsPage.tsx Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> * Commit suggestions * Commit suggestion canRead canWrite * fix typo Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Vardan Torosyan <vardants@gmail.com> Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
2021-11-18 14:10:38 +01:00
if err != nil {
c.Logger.Error("Error from access control system", "error", err)
return false
}
// set on user so we don't fetch global permissions every time this is called
c.SignedInUser.Permissions[tmpUser.GetOrgID()] = tmpUser.GetPermissions()
AccessControl: FGAC permissions for orgs endpoint on frontend (#41050) * AccessControl: FGAC permissions for orgs endpoint on frontend Protect org update endpoints add or refactor missing right messages cover org page * removing scopes from orgs * Perform permission control with global org * Perform the error handling in case of 403 * Simplify frontend code by requiring read access for sure * Remove roles I added to decrease the number of changes * Remove the check for server admin to reduce the number of changes * change error message * Cleaning todos * Remove unecessary changes * Fix tests * Update test snapshot * Update pkg/api/roles.go Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Update public/app/features/admin/AdminEditOrgPage.tsx Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Format AdminEditOrgPage for linting * Update public/app/features/admin/AdminEditOrgPage.tsx Co-authored-by: Vardan Torosyan <vardants@gmail.com> * Update public/app/features/admin/AdminEditOrgPage.tsx Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> * Update public/app/features/admin/AdminListOrgsPage.tsx Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> * Commit suggestions * Commit suggestion canRead canWrite * fix typo Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Vardan Torosyan <vardants@gmail.com> Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
2021-11-18 14:10:38 +01:00
return hasAccess
}
}
func HasAccess(ac AccessControl, c *contextmodel.ReqContext) func(evaluator Evaluator) bool {
return func(evaluator Evaluator) bool {
hasAccess, err := ac.Evaluate(c.Req.Context(), c.SignedInUser, evaluator)
if err != nil {
c.Logger.Error("Error from access control system", "error", err)
return false
}
return hasAccess
}
}
var ReqSignedIn = func(c *contextmodel.ReqContext) bool {
Access control: Use access control for dashboard and folder (#44702) * Add actions and scopes * add resource service for dashboard and folder * Add dashboard guardian with fgac permission evaluation * Add CanDelete function to guardian interface * Add CanDelete property to folder and dashboard dto and set values * change to correct function name * Add accesscontrol to folder endpoints * add access control to dashboard endpoints * check access for nav links * Add fixed roles for dashboard and folders * use correct package * add hack to override guardian Constructor if accesscontrol is enabled * Add services * Add function to handle api backward compatability * Add permissionServices to HttpServer * Set permission when new dashboard is created * Add default permission when creating new dashboard * Set default permission when creating folder and dashboard * Add access control filter for dashboard search * Add to accept list * Add accesscontrol to dashboardimport * Disable access control in tests * Add check to see if user is allow to create a dashboard * Use SetPermissions * Use function to set several permissions at once * remove permissions for folder and dashboard on delete * update required permission * set permission for provisioning * Add CanCreate to dashboard guardian and set correct permisisons for provisioning * Dont set admin on folder / dashboard creation * Add dashboard and folder permission migrations * Add tests for CanCreate * Add roles and update descriptions * Solve uid to id for dashboard and folder permissions * Add folder and dashboard actions to permission filter * Handle viewer_can_edit flag * set folder and dashboard permissions services * Add dashboard permissions when importing a new dashboard * Set access control permissions on provisioning * Pass feature flags and only set permissions if access control is enabled * only add default permissions for folders and dashboards without folders * Batch create permissions in migrations * Remove `dashboards:edit` action * Remove unused function from interface * Update pkg/services/guardian/accesscontrol_guardian_test.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-03-03 15:05:47 +01:00
return c.IsSignedIn
}
var ReqGrafanaAdmin = func(c *contextmodel.ReqContext) bool {
return c.SignedInUser.GetIsGrafanaAdmin()
}
// ReqHasRole generates a fallback to check whether the user has a role
// ReqHasRole(org.RoleAdmin) will always return true for Grafana server admins, eg, a Grafana Admin / Viewer role combination
func ReqHasRole(role org.RoleType) func(c *contextmodel.ReqContext) bool {
return func(c *contextmodel.ReqContext) bool { return c.SignedInUser.HasRole(role) }
}
func BuildPermissionsMap(permissions []Permission) map[string]bool {
permissionsMap := make(map[string]bool)
for _, p := range permissions {
permissionsMap[p.Action] = true
}
return permissionsMap
}
// GroupScopesByAction will group scopes on action
func GroupScopesByAction(permissions []Permission) map[string][]string {
m := make(map[string][]string)
2022-09-20 12:03:08 +02:00
for i := range permissions {
m[permissions[i].Action] = append(m[permissions[i].Action], permissions[i].Scope)
}
return m
}
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:<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
// Reduce will reduce a list of permissions to its minimal form, grouping scopes by action
func Reduce(ps []Permission) map[string][]string {
reduced := make(map[string][]string)
scopesByAction := make(map[string]map[string]bool)
wildcardsByAction := make(map[string]map[string]bool)
// helpers
add := func(scopesByAction map[string]map[string]bool, action, scope string) {
if _, ok := scopesByAction[action]; !ok {
scopesByAction[action] = map[string]bool{scope: true}
return
}
scopesByAction[action][scope] = true
}
includes := func(wildcardsSet map[string]bool, scope string) bool {
for wildcard := range wildcardsSet {
if wildcard == "*" || strings.HasPrefix(scope, wildcard[:len(wildcard)-1]) {
return true
}
}
return false
}
// Sort permissions (scopeless, wildcard, specific)
for i := range ps {
if ps[i].Scope == "" {
if _, ok := reduced[ps[i].Action]; !ok {
reduced[ps[i].Action] = nil
}
continue
}
if isWildcard(ps[i].Scope) {
add(wildcardsByAction, ps[i].Action, ps[i].Scope)
continue
}
add(scopesByAction, ps[i].Action, ps[i].Scope)
}
// Reduce wildcards
for action, wildcards := range wildcardsByAction {
for wildcard := range wildcards {
if wildcard == "*" {
reduced[action] = []string{wildcard}
break
}
if includes(wildcards, wildcard[:len(wildcard)-2]) {
continue
}
reduced[action] = append(reduced[action], wildcard)
}
}
// Reduce specific
for action, scopes := range scopesByAction {
for scope := range scopes {
if includes(wildcardsByAction[action], scope) {
continue
}
reduced[action] = append(reduced[action], scope)
}
}
return reduced
}
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:<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
// intersectScopes computes the minimal list of scopes common to two slices.
func intersectScopes(s1, s2 []string) []string {
if len(s1) == 0 || len(s2) == 0 {
return []string{}
}
// helpers
splitScopes := func(s []string) (map[string]bool, map[string]bool) {
scopes := make(map[string]bool)
wildcards := make(map[string]bool)
for _, s := range s {
if isWildcard(s) {
wildcards[s] = true
} else {
scopes[s] = true
}
}
return scopes, wildcards
}
includes := func(wildcardsSet map[string]bool, scope string) bool {
for wildcard := range wildcardsSet {
if wildcard == "*" || strings.HasPrefix(scope, wildcard[:len(wildcard)-1]) {
return true
}
}
return false
}
res := make([]string, 0)
// split input into scopes and wildcards
s1Scopes, s1Wildcards := splitScopes(s1)
s2Scopes, s2Wildcards := splitScopes(s2)
// intersect wildcards
wildcards := make(map[string]bool)
for s := range s1Wildcards {
// if s1 wildcard is included in s2 wildcards
// then it is included in the intersection
if includes(s2Wildcards, s) {
wildcards[s] = true
continue
}
}
for s := range s2Wildcards {
// if s2 wildcard is included in s1 wildcards
// then it is included in the intersection
if includes(s1Wildcards, s) {
wildcards[s] = true
}
}
// intersect scopes
scopes := make(map[string]bool)
for s := range s1Scopes {
// if s1 scope is included in s2 wilcards or s2 scopes
// then it is included in the intersection
if includes(s2Wildcards, s) || s2Scopes[s] {
scopes[s] = true
}
}
for s := range s2Scopes {
// if s2 scope is included in s1 wilcards
// then it is included in the intersection
if includes(s1Wildcards, s) {
scopes[s] = true
}
}
// merge wildcards and scopes
for w := range wildcards {
res = append(res, w)
}
for s := range scopes {
res = append(res, s)
}
return res
}
// Intersect returns the intersection of two slices of permissions, grouping scopes by action.
func Intersect(p1, p2 []Permission) map[string][]string {
if len(p1) == 0 || len(p2) == 0 {
return map[string][]string{}
}
res := make(map[string][]string)
p1m := Reduce(p1)
p2m := Reduce(p2)
// Loop over the smallest map
if len(p1m) > len(p2m) {
p1m, p2m = p2m, p1m
}
for a1, s1 := range p1m {
if s2, ok := p2m[a1]; ok {
res[a1] = intersectScopes(s1, s2)
}
}
return res
}
func ValidateScope(scope string) bool {
prefix, last := scope[:len(scope)-1], scope[len(scope)-1]
// verify that last char is either ':' or '/' if last character of scope is '*'
if len(prefix) > 0 && last == '*' {
lastChar := prefix[len(prefix)-1]
if lastChar != ':' && lastChar != '/' {
return false
}
}
return !strings.ContainsAny(prefix, "*?")
}
func ManagedUserRoleName(userID int64) string {
return fmt.Sprintf("managed:users:%d:permissions", userID)
}
func ManagedTeamRoleName(teamID int64) string {
return fmt.Sprintf("managed:teams:%d:permissions", teamID)
}
func ManagedBuiltInRoleName(builtInRole string) string {
return fmt.Sprintf("managed:builtins:%s:permissions", strings.ToLower(builtInRole))
}
// GetOrgRoles returns legacy org roles for a user
func GetOrgRoles(user identity.Requester) []string {
roles := []string{string(user.GetOrgRole())}
if user.GetIsGrafanaAdmin() {
if user.GetOrgID() == GlobalOrgID {
// A server admin is the admin of the global organization
return []string{RoleGrafanaAdmin, string(org.RoleAdmin)}
}
roles = append(roles, RoleGrafanaAdmin)
}
return roles
}
func BackgroundUser(name string, orgID int64, role org.RoleType, permissions []Permission) identity.Requester {
return &user.SignedInUser{
OrgID: orgID,
OrgRole: role,
Login: "grafana_" + name,
Permissions: map[int64]map[string][]string{
orgID: GroupScopesByAction(permissions),
},
}
}