feat(op): dynamic issuer depending on request / host (#278)
* feat(op): dynamic issuer depending on request / host BREAKING CHANGE: The OpenID Provider package is now able to handle multiple issuers with a single storage implementation. The issuer will be selected from the host of the request and passed into the context, where every function can read it from if necessary. This results in some fundamental changes: - `Configuration` interface: - `Issuer() string` has been changed to `IssuerFromRequest(r *http.Request) string` - `Insecure() bool` has been added - OpenIDProvider interface and dependants: - `Issuer` has been removed from Config struct - `NewOpenIDProvider` now takes an additional parameter `issuer` and returns a pointer to the public/default implementation and not an OpenIDProvider interface: `NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opOpts ...Option) (OpenIDProvider, error)` changed to `NewOpenIDProvider(ctx context.Context, issuer string, config *Config, storage Storage, opOpts ...Option) (*Provider, error)` - therefore the parameter type Option changed to the public type as well: `Option func(o *Provider) error` - `AuthCallbackURL(o OpenIDProvider) func(string) string` has been changed to `AuthCallbackURL(o OpenIDProvider) func(context.Context, string) string` - `IDTokenHintVerifier() IDTokenHintVerifier` (Authorizer, OpenIDProvider, SessionEnder interfaces), `AccessTokenVerifier() AccessTokenVerifier` (Introspector, OpenIDProvider, Revoker, UserinfoProvider interfaces) and `JWTProfileVerifier() JWTProfileVerifier` (IntrospectorJWTProfile, JWTAuthorizationGrantExchanger, OpenIDProvider, RevokerJWTProfile interfaces) now take a context.Context parameter `IDTokenHintVerifier(context.Context) IDTokenHintVerifier`, `AccessTokenVerifier(context.Context) AccessTokenVerifier` and `JWTProfileVerifier(context.Context) JWTProfileVerifier` - `OidcDevMode` (CAOS_OIDC_DEV) environment variable check has been removed, use `WithAllowInsecure()` Option - Signing: the signer is not kept in memory anymore, but created on request from the loaded key: - `Signer` interface and func `NewSigner` have been removed - `ReadySigner(s Signer) ProbesFn` has been removed - `CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfiguration` has been changed to `CreateDiscoveryConfig(r *http.Request, config Configuration, storage DiscoverStorage) *oidc.DiscoveryConfiguration` - `Storage` interface: - `GetSigningKey(context.Context, chan<- jose.SigningKey)` has been changed to `SigningKey(context.Context) (SigningKey, error)` - `KeySet(context.Context) ([]Key, error)` has been added - `GetKeySet(context.Context) (*jose.JSONWebKeySet, error)` has been changed to `KeySet(context.Context) ([]Key, error)` - `SigAlgorithms(s Signer) []string` has been changed to `SigAlgorithms(ctx context.Context, storage DiscoverStorage) []string` - KeyProvider interface: `GetKeySet(context.Context) (*jose.JSONWebKeySet, error)` has been changed to `KeySet(context.Context) ([]Key, error)` - `CreateIDToken`: the Signer parameter has been removed * move example * fix examples * fix mocks * update readme * fix examples and update usage * update go module version to v2 * build branch * fix(module): rename caos to zitadel * fix: add state in access token response (implicit flow) * fix: encode auth response correctly (when using query in redirect uri) * fix query param handling * feat: add all optional claims of the introspection response * fix: use default redirect uri when not passed * fix: exchange cors library and add `X-Requested-With` to Access-Control-Request-Headers (#261) * feat(op): add support for client credentials * fix mocks and test * feat: allow to specify token type of JWT Profile Grant * document JWTProfileTokenStorage * cleanup * rp: fix integration test test username needed to be suffixed by issuer domain * chore(deps): bump golang.org/x/text from 0.5.0 to 0.6.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * op: mock: cleanup commented code * op: remove duplicate code code duplication caused by merge conflict selections --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Livio Amstutz <livio.a@gmail.com> Co-authored-by: adlerhurst <silvan.reusser@gmail.com> Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
a34d7a1630
commit
1165d88c69
77 changed files with 2349 additions and 913 deletions
|
@ -3,8 +3,8 @@ package storage
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/pkg/oidc"
|
||||
"github.com/zitadel/oidc/pkg/op"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -113,14 +113,14 @@ func (c *Client) IsScopeAllowed(scope string) bool {
|
|||
|
||||
// IDTokenUserinfoClaimsAssertion allows specifying if claims of scope profile, email, phone and address are asserted into the id_token
|
||||
// even if an access token if issued which violates the OIDC Core spec
|
||||
//(5.4. Requesting Claims using Scope Values: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims)
|
||||
// (5.4. Requesting Claims using Scope Values: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims)
|
||||
// some clients though require that e.g. email is always in the id_token when requested even if an access_token is issued
|
||||
func (c *Client) IDTokenUserinfoClaimsAssertion() bool {
|
||||
return c.idTokenUserinfoClaimsAssertion
|
||||
}
|
||||
|
||||
// ClockSkew enables clients to instruct the OP to apply a clock skew on the various times and expirations
|
||||
//(subtract from issued_at, add to expiration, ...)
|
||||
// (subtract from issued_at, add to expiration, ...)
|
||||
func (c *Client) ClockSkew() time.Duration {
|
||||
return c.clockSkew
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ func RegisterClients(registerClients ...*Client) {
|
|||
// user-defined redirectURIs may include:
|
||||
// - http://localhost without port specification (e.g. http://localhost/auth/callback)
|
||||
// - custom protocol (e.g. custom://auth/callback)
|
||||
//(the examples will be used as default, if none is provided)
|
||||
// (the examples will be used as default, if none is provided)
|
||||
func NativeClient(id string, redirectURIs ...string) *Client {
|
||||
if len(redirectURIs) == 0 {
|
||||
redirectURIs = []string{
|
||||
|
@ -168,7 +168,7 @@ func NativeClient(id string, redirectURIs ...string) *Client {
|
|||
// WebClient will create a client of type web, which will always use Basic Auth and allow the use of refresh tokens
|
||||
// user-defined redirectURIs may include:
|
||||
// - http://localhost with port specification (e.g. http://localhost:9999/auth/callback)
|
||||
//(the example will be used as default, if none is provided)
|
||||
// (the example will be used as default, if none is provided)
|
||||
func WebClient(id, secret string, redirectURIs ...string) *Client {
|
||||
if len(redirectURIs) == 0 {
|
||||
redirectURIs = []string{
|
||||
|
|
|
@ -5,9 +5,8 @@ import (
|
|||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/oidc/pkg/op"
|
||||
|
||||
"github.com/zitadel/oidc/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/pkg/oidc"
|
||||
"github.com/zitadel/oidc/pkg/op"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
)
|
||||
|
||||
// serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant
|
||||
|
@ -45,9 +45,41 @@ type Storage struct {
|
|||
}
|
||||
|
||||
type signingKey struct {
|
||||
ID string
|
||||
Algorithm string
|
||||
Key *rsa.PrivateKey
|
||||
id string
|
||||
algorithm jose.SignatureAlgorithm
|
||||
key *rsa.PrivateKey
|
||||
}
|
||||
|
||||
func (s *signingKey) SignatureAlgorithm() jose.SignatureAlgorithm {
|
||||
return s.algorithm
|
||||
}
|
||||
|
||||
func (s *signingKey) Key() interface{} {
|
||||
return s.key
|
||||
}
|
||||
|
||||
func (s *signingKey) ID() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
type publicKey struct {
|
||||
signingKey
|
||||
}
|
||||
|
||||
func (s *publicKey) ID() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s *publicKey) Algorithm() jose.SignatureAlgorithm {
|
||||
return s.algorithm
|
||||
}
|
||||
|
||||
func (s *publicKey) Use() string {
|
||||
return "sig"
|
||||
}
|
||||
|
||||
func (s *publicKey) Key() interface{} {
|
||||
return &s.key.PublicKey
|
||||
}
|
||||
|
||||
func NewStorage(userStore UserStore) *Storage {
|
||||
|
@ -67,9 +99,9 @@ func NewStorage(userStore UserStore) *Storage {
|
|||
},
|
||||
},
|
||||
signingKey: signingKey{
|
||||
ID: "id",
|
||||
Algorithm: "RS256",
|
||||
Key: key,
|
||||
id: uuid.NewString(),
|
||||
algorithm: jose.RS256,
|
||||
key: key,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -288,41 +320,29 @@ func (s *Storage) RevokeToken(ctx context.Context, tokenIDOrToken string, userID
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetSigningKey implements the op.Storage interface
|
||||
// SigningKey implements the op.Storage interface
|
||||
// it will be called when creating the OpenID Provider
|
||||
func (s *Storage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey) {
|
||||
func (s *Storage) SigningKey(ctx context.Context) (op.SigningKey, error) {
|
||||
// in this example the signing key is a static rsa.PrivateKey and the algorithm used is RS256
|
||||
// you would obviously have a more complex implementation and store / retrieve the key from your database as well
|
||||
//
|
||||
// the idea of the signing key channel is, that you can (with what ever mechanism) rotate your signing key and
|
||||
// switch the key of the signer via this channel
|
||||
keyCh <- jose.SigningKey{
|
||||
Algorithm: jose.SignatureAlgorithm(s.signingKey.Algorithm), // always tell the signer with algorithm to use
|
||||
Key: jose.JSONWebKey{
|
||||
KeyID: s.signingKey.ID, // always give the key an id so, that it will include it in the token header as `kid` claim
|
||||
Key: s.signingKey.Key,
|
||||
},
|
||||
}
|
||||
return &s.signingKey, nil
|
||||
}
|
||||
|
||||
// GetKeySet implements the op.Storage interface
|
||||
// SignatureAlgorithms implements the op.Storage interface
|
||||
// it will be called to get the sign
|
||||
func (s *Storage) SignatureAlgorithms(context.Context) ([]jose.SignatureAlgorithm, error) {
|
||||
return []jose.SignatureAlgorithm{s.signingKey.algorithm}, nil
|
||||
}
|
||||
|
||||
// KeySet implements the op.Storage interface
|
||||
// it will be called to get the current (public) keys, among others for the keys_endpoint or for validating access_tokens on the userinfo_endpoint, ...
|
||||
func (s *Storage) GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error) {
|
||||
func (s *Storage) KeySet(ctx context.Context) ([]op.Key, error) {
|
||||
// as mentioned above, this example only has a single signing key without key rotation,
|
||||
// so it will directly use its public key
|
||||
//
|
||||
// when using key rotation you typically would store the public keys alongside the private keys in your database
|
||||
// and give both of them an expiration date, with the public key having a longer lifetime (e.g. rotate private key every
|
||||
return &jose.JSONWebKeySet{
|
||||
Keys: []jose.JSONWebKey{
|
||||
{
|
||||
KeyID: s.signingKey.ID,
|
||||
Algorithm: s.signingKey.Algorithm,
|
||||
Use: oidc.KeyUseSignature,
|
||||
Key: &s.signingKey.Key.PublicKey,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
//and give both of them an expiration date, with the public key having a longer lifetime
|
||||
return []op.Key{&publicKey{s.signingKey}}, nil
|
||||
}
|
||||
|
||||
// GetClientByClientID implements the op.Storage interface
|
||||
|
|
260
example/server/storage/storage_dynamic.go
Normal file
260
example/server/storage/storage_dynamic.go
Normal file
|
@ -0,0 +1,260 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
)
|
||||
|
||||
type multiStorage struct {
|
||||
issuers map[string]*Storage
|
||||
}
|
||||
|
||||
// NewMultiStorage implements the op.Storage interface by wrapping multiple storage structs
|
||||
// and selecting them by the calling issuer
|
||||
func NewMultiStorage(issuers []string) *multiStorage {
|
||||
s := make(map[string]*Storage)
|
||||
for _, issuer := range issuers {
|
||||
s[issuer] = NewStorage(NewUserStore(issuer))
|
||||
}
|
||||
return &multiStorage{issuers: s}
|
||||
}
|
||||
|
||||
// CheckUsernamePassword implements the `authenticate` interface of the login
|
||||
func (s *multiStorage) CheckUsernamePassword(ctx context.Context, username, password, id string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.CheckUsernamePassword(username, password, id)
|
||||
}
|
||||
|
||||
// CreateAuthRequest implements the op.Storage interface
|
||||
// it will be called after parsing and validation of the authentication request
|
||||
func (s *multiStorage) CreateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, userID string) (op.AuthRequest, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.CreateAuthRequest(ctx, authReq, userID)
|
||||
}
|
||||
|
||||
// AuthRequestByID implements the op.Storage interface
|
||||
// it will be called after the Login UI redirects back to the OIDC endpoint
|
||||
func (s *multiStorage) AuthRequestByID(ctx context.Context, id string) (op.AuthRequest, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.AuthRequestByID(ctx, id)
|
||||
}
|
||||
|
||||
// AuthRequestByCode implements the op.Storage interface
|
||||
// it will be called after parsing and validation of the token request (in an authorization code flow)
|
||||
func (s *multiStorage) AuthRequestByCode(ctx context.Context, code string) (op.AuthRequest, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.AuthRequestByCode(ctx, code)
|
||||
}
|
||||
|
||||
// SaveAuthCode implements the op.Storage interface
|
||||
// it will be called after the authentication has been successful and before redirecting the user agent to the redirect_uri
|
||||
// (in an authorization code flow)
|
||||
func (s *multiStorage) SaveAuthCode(ctx context.Context, id string, code string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.SaveAuthCode(ctx, id, code)
|
||||
}
|
||||
|
||||
// DeleteAuthRequest implements the op.Storage interface
|
||||
// it will be called after creating the token response (id and access tokens) for a valid
|
||||
// - authentication request (in an implicit flow)
|
||||
// - token request (in an authorization code flow)
|
||||
func (s *multiStorage) DeleteAuthRequest(ctx context.Context, id string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.DeleteAuthRequest(ctx, id)
|
||||
}
|
||||
|
||||
// CreateAccessToken implements the op.Storage interface
|
||||
// it will be called for all requests able to return an access token (Authorization Code Flow, Implicit Flow, JWT Profile, ...)
|
||||
func (s *multiStorage) CreateAccessToken(ctx context.Context, request op.TokenRequest) (string, time.Time, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
return storage.CreateAccessToken(ctx, request)
|
||||
}
|
||||
|
||||
// CreateAccessAndRefreshTokens implements the op.Storage interface
|
||||
// it will be called for all requests able to return an access and refresh token (Authorization Code Flow, Refresh Token Request)
|
||||
func (s *multiStorage) CreateAccessAndRefreshTokens(ctx context.Context, request op.TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return "", "", time.Time{}, err
|
||||
}
|
||||
return storage.CreateAccessAndRefreshTokens(ctx, request, currentRefreshToken)
|
||||
}
|
||||
|
||||
// TokenRequestByRefreshToken implements the op.Storage interface
|
||||
// it will be called after parsing and validation of the refresh token request
|
||||
func (s *multiStorage) TokenRequestByRefreshToken(ctx context.Context, refreshToken string) (op.RefreshTokenRequest, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.TokenRequestByRefreshToken(ctx, refreshToken)
|
||||
}
|
||||
|
||||
// TerminateSession implements the op.Storage interface
|
||||
// it will be called after the user signed out, therefore the access and refresh token of the user of this client must be removed
|
||||
func (s *multiStorage) TerminateSession(ctx context.Context, userID string, clientID string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.TerminateSession(ctx, userID, clientID)
|
||||
}
|
||||
|
||||
// RevokeToken implements the op.Storage interface
|
||||
// it will be called after parsing and validation of the token revocation request
|
||||
func (s *multiStorage) RevokeToken(ctx context.Context, token string, userID string, clientID string) *oidc.Error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.RevokeToken(ctx, token, userID, clientID)
|
||||
}
|
||||
|
||||
// SigningKey implements the op.Storage interface
|
||||
// it will be called when creating the OpenID Provider
|
||||
func (s *multiStorage) SigningKey(ctx context.Context) (op.SigningKey, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.SigningKey(ctx)
|
||||
}
|
||||
|
||||
// SignatureAlgorithms implements the op.Storage interface
|
||||
// it will be called to get the sign
|
||||
func (s *multiStorage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.SignatureAlgorithms(ctx)
|
||||
}
|
||||
|
||||
// KeySet implements the op.Storage interface
|
||||
// it will be called to get the current (public) keys, among others for the keys_endpoint or for validating access_tokens on the userinfo_endpoint, ...
|
||||
func (s *multiStorage) KeySet(ctx context.Context) ([]op.Key, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.KeySet(ctx)
|
||||
}
|
||||
|
||||
// GetClientByClientID implements the op.Storage interface
|
||||
// it will be called whenever information (type, redirect_uris, ...) about the client behind the client_id is needed
|
||||
func (s *multiStorage) GetClientByClientID(ctx context.Context, clientID string) (op.Client, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.GetClientByClientID(ctx, clientID)
|
||||
}
|
||||
|
||||
// AuthorizeClientIDSecret implements the op.Storage interface
|
||||
// it will be called for validating the client_id, client_secret on token or introspection requests
|
||||
func (s *multiStorage) AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.AuthorizeClientIDSecret(ctx, clientID, clientSecret)
|
||||
}
|
||||
|
||||
// SetUserinfoFromScopes implements the op.Storage interface
|
||||
// it will be called for the creation of an id_token, so we'll just pass it to the private function without any further check
|
||||
func (s *multiStorage) SetUserinfoFromScopes(ctx context.Context, userinfo oidc.UserInfoSetter, userID, clientID string, scopes []string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.SetUserinfoFromScopes(ctx, userinfo, userID, clientID, scopes)
|
||||
}
|
||||
|
||||
// SetUserinfoFromToken implements the op.Storage interface
|
||||
// it will be called for the userinfo endpoint, so we read the token and pass the information from that to the private function
|
||||
func (s *multiStorage) SetUserinfoFromToken(ctx context.Context, userinfo oidc.UserInfoSetter, tokenID, subject, origin string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.SetUserinfoFromToken(ctx, userinfo, tokenID, subject, origin)
|
||||
}
|
||||
|
||||
// SetIntrospectionFromToken implements the op.Storage interface
|
||||
// it will be called for the introspection endpoint, so we read the token and pass the information from that to the private function
|
||||
func (s *multiStorage) SetIntrospectionFromToken(ctx context.Context, introspection oidc.IntrospectionResponse, tokenID, subject, clientID string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.SetIntrospectionFromToken(ctx, introspection, tokenID, subject, clientID)
|
||||
}
|
||||
|
||||
// GetPrivateClaimsFromScopes implements the op.Storage interface
|
||||
// it will be called for the creation of a JWT access token to assert claims for custom scopes
|
||||
func (s *multiStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (claims map[string]interface{}, err error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.GetPrivateClaimsFromScopes(ctx, userID, clientID, scopes)
|
||||
}
|
||||
|
||||
// GetKeyByIDAndUserID implements the op.Storage interface
|
||||
// it will be called to validate the signatures of a JWT (JWT Profile Grant and Authentication)
|
||||
func (s *multiStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.GetKeyByIDAndUserID(ctx, keyID, userID)
|
||||
}
|
||||
|
||||
// ValidateJWTProfileScopes implements the op.Storage interface
|
||||
// it will be called to validate the scopes of a JWT Profile Authorization Grant request
|
||||
func (s *multiStorage) ValidateJWTProfileScopes(ctx context.Context, userID string, scopes []string) ([]string, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.ValidateJWTProfileScopes(ctx, userID, scopes)
|
||||
}
|
||||
|
||||
// Health implements the op.Storage interface
|
||||
func (s *multiStorage) Health(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *multiStorage) storageFromContext(ctx context.Context) (*Storage, *oidc.Error) {
|
||||
storage, ok := s.issuers[op.IssuerFromContext(ctx)]
|
||||
if !ok {
|
||||
return nil, oidc.ErrInvalidRequest().WithDescription("invalid issuer")
|
||||
}
|
||||
return storage, nil
|
||||
}
|
|
@ -2,6 +2,7 @@ package storage
|
|||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
@ -33,12 +34,13 @@ type userStore struct {
|
|||
users map[string]*User
|
||||
}
|
||||
|
||||
func NewUserStore() UserStore {
|
||||
func NewUserStore(issuer string) UserStore {
|
||||
hostname := strings.Split(strings.Split(issuer, "://")[1], ":")[0]
|
||||
return userStore{
|
||||
users: map[string]*User{
|
||||
"id1": {
|
||||
ID: "id1",
|
||||
Username: "test-user",
|
||||
Username: "test-user@" + hostname,
|
||||
Password: "verysecure",
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue