Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth: Extended JWT client for OBO and Service Authentication #83814

Merged
merged 44 commits into from Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b711c2b
reenable ext-jwt-client
Jguer Mar 4, 2024
a1a3939
fixup settings struct
Jguer Mar 4, 2024
6c0390e
add user and service auth
Jguer Mar 4, 2024
9fe9ca3
lint up
Jguer Mar 4, 2024
a29b546
add user auth to grafana ext
Jguer Mar 4, 2024
cd17a97
Merge remote-tracking branch 'origin/main' into jguer/repurpose-ext-j…
Jguer Mar 4, 2024
6d76d7f
fixes
Jguer Mar 4, 2024
952c3b4
Populate token permissions
gamab Mar 5, 2024
005bf9f
fix tests
Jguer Mar 6, 2024
fb3966f
fix lint
Jguer Mar 6, 2024
c8b1f0d
small prealloc
Jguer Mar 6, 2024
14702cd
small prealloc
Jguer Mar 6, 2024
5a9eae1
use special namespace for access policies
Jguer Mar 6, 2024
135749f
fix access policy auth
Jguer Mar 8, 2024
dfe274e
fix tests
Jguer Mar 8, 2024
4158a07
Merge remote-tracking branch 'origin/main' into jguer/repurpose-ext-j…
Jguer Mar 11, 2024
8f83de8
fix uncalled settings expander
Jguer Mar 11, 2024
f9779c5
Merge remote-tracking branch 'origin/main' into jguer/repurpose-ext-j…
Jguer Mar 13, 2024
58a2a74
add feature toggle
Jguer Mar 13, 2024
18d71ff
Merge remote-tracking branch 'origin/main' into jguer/repurpose-ext-j…
Jguer Mar 18, 2024
a0f19d1
small feedback fixes
Jguer Mar 18, 2024
82c8f3b
rename entitlements to permissions
Jguer Mar 18, 2024
4a0c064
Merge remote-tracking branch 'origin/main' into jguer/repurpose-ext-j…
Jguer Mar 25, 2024
710fa9e
merge origin/main
gamab Mar 29, 2024
e0fdf55
add authlibn
Jguer Mar 29, 2024
33aa8a5
allow viewing the signed in user info for non user namespace
Jguer Mar 29, 2024
9b397f7
fix invalid namespacedID
Jguer Mar 29, 2024
43447d1
use authlib as verifier for tokens
Jguer Mar 29, 2024
e19dfd0
Merge remote-tracking branch 'origin/main' into jguer/repurpose-ext-j…
Jguer Mar 29, 2024
295afc6
Update pkg/services/authn/clients/ext_jwt.go
Jguer Apr 2, 2024
231db44
Update pkg/services/authn/clients/ext_jwt_test.go
Jguer Apr 2, 2024
b7c9dec
fix parameter names
Jguer Apr 2, 2024
35f7352
change asserts to normal package
Jguer Apr 2, 2024
d97605b
add rule for assert
Jguer Apr 2, 2024
6b747d6
Merge remote-tracking branch 'origin/main' into jguer/repurpose-ext-j…
Jguer Apr 2, 2024
af7740b
fix ownerships
Jguer Apr 2, 2024
4361ff4
Local diff
gamab Apr 2, 2024
295d046
test and lint
gamab Apr 2, 2024
3fba2a8
Fix test
gamab Apr 2, 2024
47e8cea
Fix ac test
gamab Apr 2, 2024
720691f
Merge remote-tracking branch 'origin/main' into jguer/repurpose-ext-j…
gamab Apr 2, 2024
de73e0c
Fix pluginproxy test
gamab Apr 2, 2024
480b4a6
Revert testdata changes
gamab Apr 2, 2024
cd6b274
Force revert on test data
gamab Apr 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .golangci.toml
Expand Up @@ -17,6 +17,7 @@ deny = [
{ pkg = "github.com/pkg/errors", desc = "Deprecated: Go 1.13 supports the functionality provided by pkg/errors in the standard library." },
{ pkg = "github.com/xorcare/pointer", desc = "Use pkg/util.Pointer instead, which is a generic one-liner alternative" },
{ pkg = "github.com/gofrs/uuid", desc = "Use github.com/google/uuid instead, which we already depend on." },
{ pkg = "github.com/bmizerany/assert", desc = "Use github.com/stretchr/testify/assert instead, which we already depend on." },
]

[linters-settings.depguard.rules.coreplugins]
Expand Down
16 changes: 8 additions & 8 deletions go.mod
Expand Up @@ -20,7 +20,7 @@ require (
cuelang.org/go v0.6.0-0.dev // @grafana/grafana-as-code
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // @grafana/partner-datasources
github.com/Azure/go-autorest/autorest v0.11.29 // @grafana/backend-platform
github.com/BurntSushi/toml v1.3.2 // @grafana/grafana-authnz-team
github.com/BurntSushi/toml v1.3.2 // @grafana/identity-access-team
github.com/Masterminds/semver v1.5.0 // @grafana/backend-platform
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // @grafana/backend-platform
github.com/aws/aws-sdk-go v1.50.8 // @grafana/aws-datasources
Expand All @@ -29,10 +29,10 @@ require (
github.com/blang/semver/v4 v4.0.0 // @grafana/grafana-release-guild
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // @grafana/backend-platform
github.com/centrifugal/centrifuge v0.30.2 // @grafana/grafana-app-platform-squad
github.com/crewjam/saml v0.4.13 // @grafana/grafana-authnz-team
github.com/crewjam/saml v0.4.13 // @grafana/identity-access-team
github.com/fatih/color v1.15.0 // @grafana/backend-platform
github.com/gchaincl/sqlhooks v1.3.0 // @grafana/backend-platform
github.com/go-ldap/ldap/v3 v3.4.4 // @grafana/grafana-authnz-team
github.com/go-ldap/ldap/v3 v3.4.4 // @grafana/identity-access-team
github.com/go-openapi/strfmt v0.22.0 // @grafana/alerting-squad-backend
github.com/go-redis/redis/v8 v8.11.5 // @grafana/backend-platform
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // @grafana/backend-platform
Expand Down Expand Up @@ -96,7 +96,7 @@ require (
golang.org/x/crypto v0.21.0 // @grafana/backend-platform
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // @grafana/alerting-squad-backend
golang.org/x/net v0.22.0 // @grafana/oss-big-tent @grafana/partner-datasources
golang.org/x/oauth2 v0.18.0 // @grafana/grafana-authnz-team
golang.org/x/oauth2 v0.18.0 // @grafana/identity-access-team
golang.org/x/sync v0.6.0 // @grafana/alerting-squad-backend
golang.org/x/time v0.5.0 // @grafana/backend-platform
golang.org/x/tools v0.17.0 // @grafana/grafana-as-code
Expand Down Expand Up @@ -241,7 +241,7 @@ require (
github.com/Masterminds/semver/v3 v3.1.1 // @grafana/grafana-release-guild
github.com/alicebob/miniredis/v2 v2.30.1 // @grafana/alerting-squad-backend
github.com/dave/dst v0.27.2 // @grafana/grafana-as-code
github.com/go-jose/go-jose/v3 v3.0.3 // @grafana/grafana-authnz-team
github.com/go-jose/go-jose/v3 v3.0.3 // @grafana/identity-access-team
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
github.com/grafana/dataplane/sdata v0.0.7 // @grafana/observability-metrics
github.com/grafana/tempo v1.5.1-0.20230524121406-1dc1bfe7085b // @grafana/observability-traces-and-profiling
Expand Down Expand Up @@ -326,7 +326,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-ieproxy v0.0.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 //@grafana/grafana-authnz-team
github.com/mitchellh/mapstructure v1.5.0 //@grafana/identity-access-team
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-squad-backend
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
Expand Down Expand Up @@ -384,7 +384,7 @@ require (
require (
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
filippo.io/age v1.1.1 // @grafana/grafana-authnz-team
filippo.io/age v1.1.1 // @grafana/identity-access-team
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
Expand Down Expand Up @@ -473,7 +473,7 @@ require github.com/jackc/pgx/v5 v5.5.5 // @grafana/oss-big-tent

require github.com/getkin/kin-openapi v0.120.0 // @grafana/grafana-as-code

require github.com/grafana/authlib v0.0.0-20240319083410-9d4a6e3861e5 // @grafana/grafana-app-platform-squad
require github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 // @grafana/identity-access-team

require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -2161,8 +2161,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20240322221449-89ae4e299bf8 h1:ndBSFAHmJRWqln2uNys7lV0+9U8tlW6ZuNz8ETW60Us=
github.com/grafana/alerting v0.0.0-20240322221449-89ae4e299bf8/go.mod h1:0nHKO0w8OTemvZ3eh7+s1EqGGhgbs0kvkTeLU1FrbTw=
github.com/grafana/authlib v0.0.0-20240319083410-9d4a6e3861e5 h1:A13Z8Hy60BfIduM819kpk0njrRKjbAVbVRhE+R+AF/8=
github.com/grafana/authlib v0.0.0-20240319083410-9d4a6e3861e5/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4=
github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 h1:lGEuhD/KhhN1OiPrvwQejl9Lg8MvaHdj3lHZNref4is=
github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4=
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ=
Expand Down
6 changes: 6 additions & 0 deletions go.work.sum
Expand Up @@ -351,6 +351,7 @@ github.com/go-fonts/stix v0.1.0 h1:UlZlgrvvmT/58o573ot7NFw0vZasZ5I6bcIft/oMdgg=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4=
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2YY30qV3gDcVBGtFgVsV3+/i+mKQ=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
Expand Down Expand Up @@ -405,6 +406,9 @@ github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/grafana/authlib v0.0.0-20240319083410-9d4a6e3861e5/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4=
github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 h1:lGEuhD/KhhN1OiPrvwQejl9Lg8MvaHdj3lHZNref4is=
github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4=
github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b h1:Ha+kSIoTutf4ytlVw/SaEclDUloYx0+FXDKJWKhNbE4=
github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b/go.mod h1:3UsooRp7yW5/NJQBlXcTsAHOoykEhNUYXkQ3r6ehEEY=
github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586 h1:/of8Z8taCPftShATouOrBVy6GaTTjgQd/VfNiZp/VXQ=
Expand Down Expand Up @@ -661,6 +665,7 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJ
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAHVdPR3IjfmN8T1h2iczJLynhLybf8=
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/substrait-io/substrait-go v0.4.2 h1:buDnjsb3qAqTaNbOR7VKmNgXf4lYQxWEcnSGUWBtmN8=
github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg=
Expand Down Expand Up @@ -771,6 +776,7 @@ go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
Expand Down
1 change: 1 addition & 0 deletions packages/grafana-data/src/types/featureToggles.gen.ts
Expand Up @@ -173,6 +173,7 @@ export interface FeatureToggles {
expressionParser?: boolean;
groupByVariable?: boolean;
betterPageScrolling?: boolean;
authAPIAccessTokenAuth?: boolean;
scopeFilters?: boolean;
ssoSettingsSAML?: boolean;
usePrometheusFrontendPackage?: boolean;
Expand Down
3 changes: 2 additions & 1 deletion pkg/api/pluginproxy/ds_proxy_test.go
Expand Up @@ -513,7 +513,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t,
&contextmodel.ReqContext{
SignedInUser: &user.SignedInUser{
Login: "test_user",
Login: "test_user",
NamespacedID: "user:1",
},
},
&setting.Cfg{SendUserHeader: true},
Expand Down
3 changes: 2 additions & 1 deletion pkg/api/pluginproxy/pluginproxy_test.go
Expand Up @@ -76,7 +76,8 @@ func TestPluginProxy(t *testing.T) {
secretsService,
&contextmodel.ReqContext{
SignedInUser: &user.SignedInUser{
Login: "test_user",
Login: "test_user",
NamespacedID: "user:1",
},
Context: &web.Context{
Req: httpReq,
Expand Down
19 changes: 16 additions & 3 deletions pkg/api/user.go
Expand Up @@ -31,10 +31,23 @@ import (
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) GetSignedInUser(c *contextmodel.ReqContext) response.Response {
userID, errResponse := getUserID(c)
if errResponse != nil {
return errResponse
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser {
return response.JSON(http.StatusOK, user.UserProfileDTO{
IsGrafanaAdmin: c.SignedInUser.GetIsGrafanaAdmin(),
OrgID: c.SignedInUser.GetOrgID(),
UID: strings.Join([]string{namespace, identifier}, ":"),
Name: c.SignedInUser.NameOrFallback(),
Email: c.SignedInUser.GetEmail(),
Login: c.SignedInUser.GetLogin(),
})
}

userID, err := identity.IntIdentifier(namespace, identifier)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to parse user id", err)
}

return hs.getUserUserProfile(c, userID)
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/services/accesscontrol/accesscontrol.go
Expand Up @@ -24,6 +24,8 @@ type AccessControl interface {

type Service interface {
registry.ProvidesUsageStats
// GetRoleByName returns a role by name
GetRoleByName(ctx context.Context, orgID int64, roleName string) (*RoleDTO, error)
// GetUserPermissions returns user permissions with only action and scope fields set.
GetUserPermissions(ctx context.Context, user identity.Requester, options Options) ([]Permission, error)
// GetUserPermissionsInOrg return user permission in a specific organization
Expand Down
23 changes: 23 additions & 0 deletions pkg/services/accesscontrol/acimpl/service.go
Expand Up @@ -504,3 +504,26 @@ func (s *Service) DeleteExternalServiceRole(ctx context.Context, externalService
func (*Service) SyncUserRoles(ctx context.Context, orgID int64, cmd accesscontrol.SyncUserRolesCommand) error {
return nil
}

func (s *Service) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*accesscontrol.RoleDTO, error) {
err := accesscontrol.ErrRoleNotFound
if _, ok := s.roles[roleName]; ok {
return nil, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we don't want to allow basic roles? Side note, this check could actually be skipped as we won't find basic roles in the registration list.

}

var role *accesscontrol.RoleDTO
s.registrations.Range(func(registration accesscontrol.RoleRegistration) bool {
if registration.Role.Name == roleName {
role = &accesscontrol.RoleDTO{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to not clone the entire accesscontrol.RoleDTO{} into the return value, rather than only selecting Name and Permissions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these are not persisted in the DB we don't have all the fields, I've added the ones we actually have

Name: registration.Role.Name,
Permissions: registration.Role.Permissions,
DisplayName: registration.Role.DisplayName,
Description: registration.Role.Description,
}
err = nil
return false
}
return true
})
return role, err
}
13 changes: 9 additions & 4 deletions pkg/services/accesscontrol/acimpl/service_test.go
Expand Up @@ -754,8 +754,9 @@ func TestPermissionCacheKey(t *testing.T) {
{
name: "should return correct key for user",
signedInUser: &user.SignedInUser{
OrgID: 1,
UserID: 1,
OrgID: 1,
UserID: 1,
NamespacedID: "user:1",
},
expected: "rbac-permissions-1-user-1",
},
Expand All @@ -765,6 +766,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1,
ApiKeyID: 1,
IsServiceAccount: false,
NamespacedID: "user:1",
},
expected: "rbac-permissions-1-api-key-1",
},
Expand All @@ -774,6 +776,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1,
UserID: 1,
IsServiceAccount: true,
NamespacedID: "serviceaccount:1",
},
expected: "rbac-permissions-1-service-account-1",
},
Expand All @@ -783,14 +786,16 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1,
UserID: -1,
IsServiceAccount: true,
NamespacedID: "serviceaccount:-1",
},
expected: "rbac-permissions-1-service-account--1",
},
{
name: "should use org role if no unique id",
signedInUser: &user.SignedInUser{
OrgID: 1,
OrgRole: org.RoleNone,
OrgID: 1,
OrgRole: org.RoleNone,
NamespacedID: "user:1",
},
expected: "rbac-permissions-1-user-None",
},
Expand Down
10 changes: 10 additions & 0 deletions pkg/services/accesscontrol/mock/mock.go
Expand Up @@ -20,6 +20,7 @@ type fullAccessControl interface {

type Calls struct {
Evaluate []interface{}
GetRoleByName []interface{}
GetUserPermissions []interface{}
GetUserPermissionsInOrg []interface{}
ClearUserPermissionCache []interface{}
Expand Down Expand Up @@ -47,6 +48,7 @@ type Mock struct {

// Override functions
EvaluateFunc func(context.Context, identity.Requester, accesscontrol.Evaluator) (bool, error)
GetRoleByNameFunc func(context.Context, int64, string) (*accesscontrol.RoleDTO, error)
GetUserPermissionsFunc func(context.Context, identity.Requester, accesscontrol.Options) ([]accesscontrol.Permission, error)
GetUserPermissionsInOrgFunc func(context.Context, identity.Requester, int64) ([]accesscontrol.Permission, error)
ClearUserPermissionCacheFunc func(identity.Requester)
Expand Down Expand Up @@ -81,6 +83,14 @@ func New() *Mock {
return mock
}

func (m *Mock) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*accesscontrol.RoleDTO, error) {
m.Calls.GetRoleByName = append(m.Calls.GetRoleByName, []interface{}{ctx, orgID, roleName})
if m.GetRoleByNameFunc != nil {
return m.GetRoleByNameFunc(ctx, orgID, roleName)
}
return nil, nil
}

func (m *Mock) GetUsageStats(ctx context.Context) map[string]interface{} {
return make(map[string]interface{})
}
Expand Down
1 change: 1 addition & 0 deletions pkg/services/auth/identity/requester.go
Expand Up @@ -14,6 +14,7 @@ const (
NamespaceServiceAccount = "service-account"
NamespaceAnonymous = "anonymous"
NamespaceRenderService = "render"
NamespaceAccessPolicy = "access-policy"
)

var ErrNotIntIdentifier = errors.New("identifier is not an int64")
Expand Down
9 changes: 9 additions & 0 deletions pkg/services/authn/authn.go
Expand Up @@ -57,6 +57,15 @@ type ClientParams struct {
LookUpParams login.UserLookupParams
// SyncPermissions ensure that permissions are loaded from DB and added to the identity
SyncPermissions bool
// FetchPermissionsParams are the arguments used to fetch permissions from the DB
FetchPermissionsParams FetchPermissionsParams
}

type FetchPermissionsParams struct {
// ActionsLookup will restrict the permissions to only these actions
ActionsLookup []string
// Roles permissions will be directly added to the identity permissions
Roles []string
}

type PostAuthHookFn func(ctx context.Context, identity *Identity, r *Request) error
Expand Down
7 changes: 3 additions & 4 deletions pkg/services/authn/authnimpl/service.go
Expand Up @@ -135,10 +135,9 @@ func ProvideService(
s.RegisterClient(clients.ProvideJWT(jwtService, cfg))
}

// FIXME (gamab): Commenting that out for now as we want to re-use the client for external service auth
// if s.cfg.ExtendedJWTAuthEnabled && features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth) {
// s.RegisterClient(clients.ProvideExtendedJWT(userService, cfg, signingKeysService, oauthServer))
// }
if s.cfg.ExtJWTAuth.Enabled && features.IsEnabledGlobally(featuremgmt.FlagAuthAPIAccessTokenAuth) {
s.RegisterClient(clients.ProvideExtendedJWT(userService, cfg, signingKeysService))
}

for name := range socialService.GetOAuthProviders() {
clientName := authn.ClientWithPrefix(name)
Expand Down
49 changes: 44 additions & 5 deletions pkg/services/authn/authnimpl/sync/rbac_sync.go
Expand Up @@ -2,6 +2,7 @@ package sync

import (
"context"
"errors"

"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
Expand Down Expand Up @@ -34,19 +35,57 @@ func (s *RBACSync) SyncPermissionsHook(ctx context.Context, ident *authn.Identit
return nil
}

permissions, err := s.ac.GetUserPermissions(ctx, ident, accesscontrol.Options{ReloadCache: false})
// Populate permissions from roles
permissions, err := s.fetchPermissions(ctx, ident)
if err != nil {
s.log.FromContext(ctx).Error("Failed to fetch permissions from db", "error", err, "id", ident.ID)
return errSyncPermissionsForbidden
return err
}

if ident.Permissions == nil {
ident.Permissions = make(map[int64]map[string][]string)
ident.Permissions = make(map[int64]map[string][]string, 1)
}
grouped := accesscontrol.GroupScopesByAction(permissions)

// Restrict access to the list of actions
actionsLookup := ident.ClientParams.FetchPermissionsParams.ActionsLookup
Jguer marked this conversation as resolved.
Show resolved Hide resolved
if len(actionsLookup) > 0 {
filtered := make(map[string][]string, len(actionsLookup))
for _, action := range actionsLookup {
if scopes, ok := grouped[action]; ok {
filtered[action] = scopes
}
}
grouped = filtered
}
ident.Permissions[ident.OrgID] = accesscontrol.GroupScopesByAction(permissions)

ident.Permissions[ident.OrgID] = grouped
return nil
}

func (s *RBACSync) fetchPermissions(ctx context.Context, ident *authn.Identity) ([]accesscontrol.Permission, error) {
permissions := make([]accesscontrol.Permission, 0, 8)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious how you landed on 8 for the initial capacity - typically in these kind of dynamic slices I don't see a capacity set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normally set an octet multiple for these, we're normally sure to get around this value

roles := ident.ClientParams.FetchPermissionsParams.Roles
if len(roles) > 0 {
for _, role := range roles {
roleDTO, err := s.ac.GetRoleByName(ctx, ident.GetOrgID(), role)
if err != nil && !errors.Is(err, accesscontrol.ErrRoleNotFound) {
s.log.FromContext(ctx).Error("Failed to fetch role from db", "error", err, "role", role)
return nil, errSyncPermissionsForbidden
}
permissions = append(permissions, roleDTO.Permissions...)
}

return permissions, nil
}

permissions, err := s.ac.GetUserPermissions(ctx, ident, accesscontrol.Options{ReloadCache: false})
if err != nil {
s.log.FromContext(ctx).Error("Failed to fetch permissions from db", "error", err, "id", ident.ID)
return nil, errSyncPermissionsForbidden
}
return permissions, nil
}

var fixedCloudRoles = map[org.RoleType]string{
org.RoleViewer: accesscontrol.FixedCloudViewerRole,
org.RoleEditor: accesscontrol.FixedCloudEditorRole,
Expand Down