mirror of https://github.com/grafana/grafana.git
[authn]: add GetIDClaims() to Requester (#91387)
* authn: add GetIDClaims() to Requester Co-Authored-By: Gabriel MABILLE <gamab@users.noreply.github.com> * authn: update StaticRequester Co-Authored-By: Gabriel MABILLE <gamab@users.noreply.github.com> * update auth/idtest/mock Co-Authored-By: Gabriel MABILLE <gamab@users.noreply.github.com> * Fix test Co-authored-by: Claudiu Dragalina-Paraipan <claudiu.dragalina@grafana.com> --------- Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: gamab <gabriel.mabille@grafana.com>
This commit is contained in:
parent
a940bb87be
commit
e2435f92f1
|
|
@ -3,6 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery
|
||||||
go 1.21.10
|
go 1.21.10
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
k8s.io/apimachinery v0.29.3
|
k8s.io/apimachinery v0.29.3
|
||||||
k8s.io/apiserver v0.29.2
|
k8s.io/apiserver v0.29.2
|
||||||
|
|
@ -12,6 +13,7 @@ require (
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
|
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.4 // indirect
|
github.com/go-openapi/jsonreference v0.20.4 // indirect
|
||||||
|
|
@ -25,10 +27,15 @@ require (
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
golang.org/x/crypto v0.24.0 // indirect
|
||||||
golang.org/x/net v0.26.0 // indirect
|
golang.org/x/net v0.26.0 // indirect
|
||||||
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
|
google.golang.org/grpc v1.65.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||||
|
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
|
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
|
||||||
|
|
@ -9,6 +10,7 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
||||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1 h1:EiaupmOnt6XF/LPxvagjTofWmByzYaf5VyMIF+w/71M=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
|
@ -16,12 +18,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
||||||
|
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
authnlib "github.com/grafana/authlib/authn"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -77,6 +78,8 @@ type Requester interface {
|
||||||
// GetIDToken returns a signed token representing the identity that can be forwarded to plugins and external services.
|
// GetIDToken returns a signed token representing the identity that can be forwarded to plugins and external services.
|
||||||
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
|
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
|
||||||
GetIDToken() string
|
GetIDToken() string
|
||||||
|
// GetIDClaims returns the claims of the ID token.
|
||||||
|
GetIDClaims() *authnlib.Claims[authnlib.IDTokenClaims]
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntIdentifier converts a string identifier to an int64.
|
// IntIdentifier converts a string identifier to an int64.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package identity
|
package identity
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
authnlib "github.com/grafana/authlib/authn"
|
||||||
|
)
|
||||||
|
|
||||||
var _ Requester = &StaticRequester{}
|
var _ Requester = &StaticRequester{}
|
||||||
|
|
||||||
|
|
@ -27,6 +31,7 @@ type StaticRequester struct {
|
||||||
// Permissions grouped by orgID and actions
|
// Permissions grouped by orgID and actions
|
||||||
Permissions map[int64]map[string][]string
|
Permissions map[int64]map[string][]string
|
||||||
IDToken string
|
IDToken string
|
||||||
|
IDTokenClaims *authnlib.Claims[authnlib.IDTokenClaims]
|
||||||
CacheKey string
|
CacheKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,3 +213,7 @@ func (u *StaticRequester) GetDisplayName() string {
|
||||||
func (u *StaticRequester) GetIDToken() string {
|
func (u *StaticRequester) GetIDToken() string {
|
||||||
return u.IDToken
|
return u.IDToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *StaticRequester) GetIDClaims() *authnlib.Claims[authnlib.IDTokenClaims] {
|
||||||
|
return u.IDTokenClaims
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
type IDService interface {
|
type IDService interface {
|
||||||
// SignIdentity signs a id token for provided identity that can be forwarded to plugins and external services
|
// SignIdentity signs a id token for provided identity that can be forwarded to plugins and external services
|
||||||
SignIdentity(ctx context.Context, identity identity.Requester) (string, error)
|
SignIdentity(ctx context.Context, id identity.Requester) (string, *authnlib.Claims[authnlib.IDTokenClaims], error)
|
||||||
|
|
||||||
// RemoveIDToken removes any locally stored id tokens for key
|
// RemoveIDToken removes any locally stored id tokens for key
|
||||||
RemoveIDToken(ctx context.Context, identity identity.Requester) error
|
RemoveIDToken(ctx context.Context, identity identity.Requester) error
|
||||||
|
|
|
||||||
|
|
@ -58,21 +58,30 @@ type Service struct {
|
||||||
nsMapper request.NamespaceMapper
|
nsMapper request.NamespaceMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (string, error) {
|
func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (string, *authnlib.Claims[authnlib.IDTokenClaims], error) {
|
||||||
defer func(t time.Time) {
|
defer func(t time.Time) {
|
||||||
s.metrics.tokenSigningDurationHistogram.Observe(time.Since(t).Seconds())
|
s.metrics.tokenSigningDurationHistogram.Observe(time.Since(t).Seconds())
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
cacheKey := prefixCacheKey(id.GetCacheKey())
|
cacheKey := prefixCacheKey(id.GetCacheKey())
|
||||||
|
|
||||||
result, err, _ := s.si.Do(cacheKey, func() (interface{}, error) {
|
type resultType struct {
|
||||||
|
token string
|
||||||
|
idClaims *auth.IDClaims
|
||||||
|
}
|
||||||
|
result, err, _ := s.si.Do(cacheKey, func() (any, error) {
|
||||||
namespace, identifier := id.GetTypedID()
|
namespace, identifier := id.GetTypedID()
|
||||||
|
|
||||||
cachedToken, err := s.cache.Get(ctx, cacheKey)
|
cachedToken, err := s.cache.Get(ctx, cacheKey)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.metrics.tokenSigningFromCacheCounter.Inc()
|
s.metrics.tokenSigningFromCacheCounter.Inc()
|
||||||
s.logger.FromContext(ctx).Debug("Cached token found", "namespace", namespace, "id", identifier)
|
s.logger.FromContext(ctx).Debug("Cached token found", "namespace", namespace, "id", identifier)
|
||||||
return string(cachedToken), nil
|
|
||||||
|
tokenClaims, err := s.extractTokenClaims(string(cachedToken))
|
||||||
|
if err != nil {
|
||||||
|
return resultType{}, err
|
||||||
|
}
|
||||||
|
return resultType{token: string(cachedToken), idClaims: tokenClaims}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.metrics.tokenSigningCounter.Inc()
|
s.metrics.tokenSigningCounter.Inc()
|
||||||
|
|
@ -104,21 +113,12 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
|
||||||
token, err := s.signer.SignIDToken(ctx, claims)
|
token, err := s.signer.SignIDToken(ctx, claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.metrics.failedTokenSigningCounter.Inc()
|
s.metrics.failedTokenSigningCounter.Inc()
|
||||||
return "", err
|
return resultType{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed, err := jwt.ParseSigned(token)
|
extracted, err := s.extractTokenClaims(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.metrics.failedTokenSigningCounter.Inc()
|
return resultType{}, err
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
extracted := auth.IDClaims{}
|
|
||||||
// We don't need to verify the signature here, we are only interested in checking
|
|
||||||
// when the token expires.
|
|
||||||
if err := parsed.UnsafeClaimsWithoutVerification(&extracted); err != nil {
|
|
||||||
s.metrics.failedTokenSigningCounter.Inc()
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expires := time.Until(extracted.Expiry.Time())
|
expires := time.Until(extracted.Expiry.Time())
|
||||||
|
|
@ -126,14 +126,14 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
|
||||||
s.logger.FromContext(ctx).Error("Failed to add id token to cache", "error", err)
|
s.logger.FromContext(ctx).Error("Failed to add id token to cache", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return token, nil
|
return resultType{token: token, idClaims: claims}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.(string), nil
|
return result.(resultType).token, result.(resultType).idClaims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) RemoveIDToken(ctx context.Context, id identity.Requester) error {
|
func (s *Service) RemoveIDToken(ctx context.Context, id identity.Requester) error {
|
||||||
|
|
@ -142,7 +142,7 @@ func (s *Service) RemoveIDToken(ctx context.Context, id identity.Requester) erro
|
||||||
|
|
||||||
func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
|
func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
|
||||||
// FIXME(kalleep): we should probably lazy load this
|
// FIXME(kalleep): we should probably lazy load this
|
||||||
token, err := s.SignIdentity(ctx, identity)
|
token, claims, err := s.SignIdentity(ctx, identity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if shouldLogErr(err) {
|
if shouldLogErr(err) {
|
||||||
namespace, id := identity.GetTypedID()
|
namespace, id := identity.GetTypedID()
|
||||||
|
|
@ -153,9 +153,28 @@ func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.R
|
||||||
}
|
}
|
||||||
|
|
||||||
identity.IDToken = token
|
identity.IDToken = token
|
||||||
|
identity.IDTokenClaims = claims
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) extractTokenClaims(token string) (*authnlib.Claims[authnlib.IDTokenClaims], error) {
|
||||||
|
parsed, err := jwt.ParseSigned(token)
|
||||||
|
if err != nil {
|
||||||
|
s.metrics.failedTokenSigningCounter.Inc()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extracted := authnlib.Claims[authnlib.IDTokenClaims]{}
|
||||||
|
// We don't need to verify the signature here, we are only interested in checking
|
||||||
|
// when the token expires.
|
||||||
|
if err := parsed.UnsafeClaimsWithoutVerification(&extracted); err != nil {
|
||||||
|
s.metrics.failedTokenSigningCounter.Inc()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extracted, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getAudience(orgID int64) jwt.Audience {
|
func getAudience(orgID int64) jwt.Audience {
|
||||||
return jwt.Audience{fmt.Sprintf("org:%d", orgID)}
|
return jwt.Audience{fmt.Sprintf("org:%d", orgID)}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ func TestService_SignIdentity(t *testing.T) {
|
||||||
featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding),
|
featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding),
|
||||||
&authntest.FakeService{}, nil,
|
&authntest.FakeService{}, nil,
|
||||||
)
|
)
|
||||||
token, err := s.SignIdentity(context.Background(), &authn.Identity{ID: identity.MustParseTypedID("user:1")})
|
token, _, err := s.SignIdentity(context.Background(), &authn.Identity{ID: identity.MustParseTypedID("user:1")})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, token)
|
require.NotEmpty(t, token)
|
||||||
})
|
})
|
||||||
|
|
@ -81,7 +81,7 @@ func TestService_SignIdentity(t *testing.T) {
|
||||||
featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding),
|
featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding),
|
||||||
&authntest.FakeService{}, nil,
|
&authntest.FakeService{}, nil,
|
||||||
)
|
)
|
||||||
token, err := s.SignIdentity(context.Background(), &authn.Identity{
|
token, _, err := s.SignIdentity(context.Background(), &authn.Identity{
|
||||||
ID: identity.MustParseTypedID("user:1"),
|
ID: identity.MustParseTypedID("user:1"),
|
||||||
AuthenticatedBy: login.AzureADAuthModule,
|
AuthenticatedBy: login.AzureADAuthModule,
|
||||||
Login: "U1",
|
Login: "U1",
|
||||||
|
|
@ -97,4 +97,22 @@ func TestService_SignIdentity(t *testing.T) {
|
||||||
assert.Equal(t, "U1", claims.Rest.Username)
|
assert.Equal(t, "U1", claims.Rest.Username)
|
||||||
assert.Equal(t, "user:edpu3nnt61se8e", claims.Rest.UID)
|
assert.Equal(t, "user:edpu3nnt61se8e", claims.Rest.UID)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should sign identity with authenticated by if user is externally authenticated", func(t *testing.T) {
|
||||||
|
s := ProvideService(
|
||||||
|
setting.NewCfg(), signer, remotecache.NewFakeCacheStorage(),
|
||||||
|
featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding),
|
||||||
|
&authntest.FakeService{}, nil,
|
||||||
|
)
|
||||||
|
_, gotClaims, err := s.SignIdentity(context.Background(), &authn.Identity{
|
||||||
|
ID: identity.MustParseTypedID("user:1"),
|
||||||
|
AuthenticatedBy: login.AzureADAuthModule,
|
||||||
|
Login: "U1",
|
||||||
|
UID: identity.NewTypedIDString(identity.TypeUser, "edpu3nnt61se8e")})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, login.AzureADAuthModule, gotClaims.Rest.AuthenticatedBy)
|
||||||
|
assert.Equal(t, "U1", gotClaims.Rest.Username)
|
||||||
|
assert.Equal(t, "user:edpu3nnt61se8e", gotClaims.Rest.UID)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package idtest
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
authnlib "github.com/grafana/authlib/authn"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/services/auth"
|
"github.com/grafana/grafana/pkg/services/auth"
|
||||||
)
|
)
|
||||||
|
|
@ -10,15 +12,15 @@ import (
|
||||||
var _ auth.IDService = (*MockService)(nil)
|
var _ auth.IDService = (*MockService)(nil)
|
||||||
|
|
||||||
type MockService struct {
|
type MockService struct {
|
||||||
SignIdentityFn func(ctx context.Context, identity identity.Requester) (string, error)
|
SignIdentityFn func(ctx context.Context, identity identity.Requester) (string, *authnlib.Claims[authnlib.IDTokenClaims], error)
|
||||||
RemoveIDTokenFn func(ctx context.Context, identity identity.Requester) error
|
RemoveIDTokenFn func(ctx context.Context, identity identity.Requester) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockService) SignIdentity(ctx context.Context, identity identity.Requester) (string, error) {
|
func (m *MockService) SignIdentity(ctx context.Context, identity identity.Requester) (string, *authnlib.Claims[authnlib.IDTokenClaims], error) {
|
||||||
if m.SignIdentityFn != nil {
|
if m.SignIdentityFn != nil {
|
||||||
return m.SignIdentityFn(ctx, identity)
|
return m.SignIdentityFn(ctx, identity)
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockService) RemoveIDToken(ctx context.Context, identity identity.Requester) error {
|
func (m *MockService) RemoveIDToken(ctx context.Context, identity identity.Requester) error {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/authlib/authn"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
|
|
@ -70,6 +71,7 @@ type Identity struct {
|
||||||
// IDToken is a signed token representing the identity that can be forwarded to plugins and external services.
|
// IDToken is a signed token representing the identity that can be forwarded to plugins and external services.
|
||||||
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
|
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
|
||||||
IDToken string
|
IDToken string
|
||||||
|
IDTokenClaims *authn.Claims[authn.IDTokenClaims]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRawIdentifier implements Requester.
|
// GetRawIdentifier implements Requester.
|
||||||
|
|
@ -156,6 +158,10 @@ func (i *Identity) GetIDToken() string {
|
||||||
return i.IDToken
|
return i.IDToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Identity) GetIDClaims() *authn.Claims[authn.IDTokenClaims] {
|
||||||
|
return i.IDTokenClaims
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Identity) GetIsGrafanaAdmin() bool {
|
func (i *Identity) GetIsGrafanaAdmin() bool {
|
||||||
return i.IsGrafanaAdmin != nil && *i.IsGrafanaAdmin
|
return i.IsGrafanaAdmin != nil && *i.IsGrafanaAdmin
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
authnlib "github.com/grafana/authlib/authn"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -40,9 +42,11 @@ type SignedInUser struct {
|
||||||
Teams []int64
|
Teams []int64
|
||||||
// Permissions grouped by orgID and actions
|
// Permissions grouped by orgID and actions
|
||||||
Permissions map[int64]map[string][]string `json:"-"`
|
Permissions map[int64]map[string][]string `json:"-"`
|
||||||
|
|
||||||
// IDToken is a signed token representing the identity that can be forwarded to plugins and external services.
|
// IDToken is a signed token representing the identity that can be forwarded to plugins and external services.
|
||||||
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
|
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
|
||||||
IDToken string `json:"-" xorm:"-"`
|
IDToken string `json:"-" xorm:"-"`
|
||||||
|
IDTokenClaims *authnlib.Claims[authnlib.IDTokenClaims] `json:"-" xorm:"-"`
|
||||||
|
|
||||||
// When other settings are not deterministic, this value is used
|
// When other settings are not deterministic, this value is used
|
||||||
FallbackType identity.IdentityType
|
FallbackType identity.IdentityType
|
||||||
|
|
@ -309,3 +313,7 @@ func (u *SignedInUser) GetDisplayName() string {
|
||||||
func (u *SignedInUser) GetIDToken() string {
|
func (u *SignedInUser) GetIDToken() string {
|
||||||
return u.IDToken
|
return u.IDToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *SignedInUser) GetIDClaims() *authnlib.Claims[authnlib.IDTokenClaims] {
|
||||||
|
return u.IDTokenClaims
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue