Initial QSfera import
This commit is contained in:
+533
@@ -0,0 +1,533 @@
|
||||
/*
|
||||
* Copyright 2017-2019 Kopano and its licensors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/libregraph/oidc-go"
|
||||
"github.com/rs/cors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
|
||||
konnect "github.com/libregraph/lico"
|
||||
"github.com/libregraph/lico/identity"
|
||||
"github.com/libregraph/lico/identity/clients"
|
||||
identityManagers "github.com/libregraph/lico/identity/managers"
|
||||
"github.com/libregraph/lico/managers"
|
||||
konnectoidc "github.com/libregraph/lico/oidc"
|
||||
"github.com/libregraph/lico/oidc/code"
|
||||
"github.com/libregraph/lico/utils"
|
||||
)
|
||||
|
||||
// Provider defines an OIDC provider with the handlers for the OIDC endpoints.
|
||||
type Provider struct {
|
||||
Config *Config
|
||||
|
||||
issuerIdentifier string
|
||||
metadata *oidc.WellKnown
|
||||
|
||||
wellKnownPath string
|
||||
jwksPath string
|
||||
authorizationPath string
|
||||
tokenPath string
|
||||
userInfoPath string
|
||||
endSessionPath string
|
||||
checkSessionIframePath string
|
||||
registrationPath string
|
||||
|
||||
identityManager identity.Manager
|
||||
guestManager identity.Manager
|
||||
codeManager code.Manager
|
||||
encryptionManager *identityManagers.EncryptionManager
|
||||
clients *clients.Registry
|
||||
|
||||
signingKeys map[jwt.SigningMethod]*SigningKey
|
||||
signingMethodDefault jwt.SigningMethod
|
||||
validationKeys map[string]crypto.PublicKey
|
||||
certificates map[string][]*x509.Certificate
|
||||
|
||||
browserStateCookiePath string
|
||||
browserStateCookieName string
|
||||
browserStateCookieSameSite http.SameSite
|
||||
|
||||
sessionCookiePath string
|
||||
sessionCookieName string
|
||||
sessionCookieSameSite http.SameSite
|
||||
|
||||
accessTokenDuration time.Duration
|
||||
idTokenDuration time.Duration
|
||||
refreshTokenDuration time.Duration
|
||||
|
||||
logger logrus.FieldLogger
|
||||
}
|
||||
|
||||
// NewProvider returns a new Provider.
|
||||
func NewProvider(c *Config) (*Provider, error) {
|
||||
p := &Provider{
|
||||
Config: c,
|
||||
|
||||
issuerIdentifier: c.IssuerIdentifier,
|
||||
wellKnownPath: c.WellKnownPath,
|
||||
jwksPath: c.JwksPath,
|
||||
authorizationPath: c.AuthorizationPath,
|
||||
tokenPath: c.TokenPath,
|
||||
userInfoPath: c.UserInfoPath,
|
||||
endSessionPath: c.EndSessionPath,
|
||||
checkSessionIframePath: c.CheckSessionIframePath,
|
||||
registrationPath: c.RegistrationPath,
|
||||
|
||||
signingKeys: make(map[jwt.SigningMethod]*SigningKey),
|
||||
validationKeys: make(map[string]crypto.PublicKey),
|
||||
certificates: make(map[string][]*x509.Certificate),
|
||||
|
||||
browserStateCookiePath: c.BrowserStateCookiePath,
|
||||
browserStateCookieName: c.BrowserStateCookieName,
|
||||
browserStateCookieSameSite: c.BrowserStateCookieSameSite,
|
||||
|
||||
sessionCookiePath: c.SessionCookiePath,
|
||||
sessionCookieName: c.SessionCookieName,
|
||||
sessionCookieSameSite: c.SessionCookieSameSite,
|
||||
|
||||
accessTokenDuration: c.AccessTokenDuration,
|
||||
idTokenDuration: c.IDTokenDuration,
|
||||
refreshTokenDuration: c.RefreshTokenDuration,
|
||||
|
||||
logger: c.Config.Logger,
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// RegisterManagers registers the provided managers from the
|
||||
func (p *Provider) RegisterManagers(mgrs *managers.Managers) error {
|
||||
p.identityManager = mgrs.Must("identity").(identity.Manager)
|
||||
p.codeManager = mgrs.Must("code").(code.Manager)
|
||||
p.encryptionManager = mgrs.Must("encryption").(*identityManagers.EncryptionManager)
|
||||
p.clients = mgrs.Must("clients").(*clients.Registry)
|
||||
|
||||
// Register callback to cleanup our cookie whenever the identity is unset or
|
||||
// set.
|
||||
onSetLogon := func(ctx context.Context, rw http.ResponseWriter, user identity.User) error {
|
||||
// NOTE(longsleep): This leaves room for optionmization. In theory it
|
||||
// should be possible to set the new browser state here directly since
|
||||
// the relevant information should be available in the identity manager
|
||||
// and thus avoiding a potentially unset refresh and client side
|
||||
// redirects whenever the same user signs in again.
|
||||
return p.removeBrowserStateCookie(rw)
|
||||
}
|
||||
onUnsetLogon := func(ctx context.Context, rw http.ResponseWriter) error {
|
||||
var err error
|
||||
|
||||
// Remove browser state cookie.
|
||||
if errBsc := p.removeBrowserStateCookie(rw); errBsc != nil {
|
||||
err = errBsc
|
||||
}
|
||||
// Remove OIDC client session cookie.
|
||||
if errSc := p.removeSessionCookie(rw); errSc != nil {
|
||||
err = errSc
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
p.identityManager.OnSetLogon(onSetLogon)
|
||||
p.identityManager.OnUnsetLogon(onUnsetLogon)
|
||||
|
||||
// Add guest manager if any can be found.
|
||||
if guestManager, _ := mgrs.Get("guest"); guestManager != nil {
|
||||
p.guestManager = guestManager.(identity.Manager)
|
||||
p.guestManager.OnSetLogon(onSetLogon)
|
||||
p.guestManager.OnUnsetLogon(onUnsetLogon)
|
||||
}
|
||||
|
||||
if p.Config.RegistrationPath != "" {
|
||||
// NOTE(longsleep): This is hackish. Find a better way to propagate our
|
||||
// provides JWT stuff to the client registry.
|
||||
p.clients.StatelessCreator = p.makeJWT
|
||||
p.clients.StatelessValidator = p.validateJWT
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) makeIssURL(path string) string {
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
u, _ := url.Parse(p.issuerIdentifier)
|
||||
u.Path = "" // Strip path from issuer, whatever prefix must already be applied.
|
||||
|
||||
return fmt.Sprintf("%s%s", u.String(), path)
|
||||
}
|
||||
|
||||
// SetSigningMethod sets the provided signing method as default signing method
|
||||
// of the associated provider.
|
||||
func (p *Provider) SetSigningMethod(signingMethod jwt.SigningMethod) error {
|
||||
p.logger.WithField("alg", signingMethod.Alg()).Infoln("set provider signing alg")
|
||||
p.signingMethodDefault = signingMethod
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSigningKey sets the provided signer as key for token signing with the
|
||||
// provided id as key id. The public key of the provided signer is also added
|
||||
// as validation key with the same key id.
|
||||
func (p *Provider) SetSigningKey(id string, key crypto.Signer) error {
|
||||
var signingMethod jwt.SigningMethod
|
||||
|
||||
// Auto select signingMethod based on the signer.
|
||||
switch s := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
signingMethod = jwt.SigningMethodPS256
|
||||
case *ecdsa.PrivateKey:
|
||||
signingMethod = jwt.SigningMethodES256
|
||||
case ed25519.PrivateKey:
|
||||
signingMethod = jwt.SigningMethodEdDSA
|
||||
default:
|
||||
return fmt.Errorf("unsupported signer type: %v", s)
|
||||
}
|
||||
|
||||
if p.signingMethodDefault == nil {
|
||||
if err := p.SetSigningMethod(signingMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p.logger.WithFields(logrus.Fields{
|
||||
"type": fmt.Sprintf("%T", key),
|
||||
"id": id,
|
||||
"method": fmt.Sprintf("%T", signingMethod),
|
||||
}).Infoln("set provider signing key")
|
||||
|
||||
switch signingMethod.(type) {
|
||||
case *jwt.SigningMethodECDSA:
|
||||
// Add all other supported ECDSA signing methods as well.
|
||||
p.signingKeys[jwt.SigningMethodES256] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodES256,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodES384] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodES384,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodES512] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodES512,
|
||||
}
|
||||
case *jwt.SigningMethodRSA:
|
||||
// Add all supported RSA and RSAPSS signing methods as well.
|
||||
p.signingKeys[jwt.SigningMethodRS256] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodRS256,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodRS384] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodRS384,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodRS512] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodRS512,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodPS256] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodPS256,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodPS384] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodPS384,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodPS512] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodPS512,
|
||||
}
|
||||
case *jwt.SigningMethodRSAPSS:
|
||||
// Add all supported RSA and RSAPSS signing methods as well.
|
||||
p.signingKeys[jwt.SigningMethodRS256] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodRS256,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodRS384] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodRS384,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodRS512] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodRS512,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodPS256] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodPS256,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodPS384] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodPS384,
|
||||
}
|
||||
p.signingKeys[jwt.SigningMethodPS512] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: jwt.SigningMethodPS512,
|
||||
}
|
||||
case *jwt.SigningMethodEd25519:
|
||||
p.signingKeys[signingMethod] = &SigningKey{
|
||||
ID: id,
|
||||
PrivateKey: key,
|
||||
SigningMethod: signingMethod,
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported signing method type")
|
||||
}
|
||||
|
||||
if _, ok := p.signingKeys[signingMethod]; !ok {
|
||||
return fmt.Errorf("unsupported signing method")
|
||||
}
|
||||
|
||||
p.SetValidationKey(id, key.Public())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigningKey returns a matching signing key for the provided signing method.
|
||||
func (p *Provider) GetSigningKey(signingMethod jwt.SigningMethod) (*SigningKey, bool) {
|
||||
return p.getSigningKey(signingMethod)
|
||||
}
|
||||
|
||||
func (p *Provider) getSigningKey(signingMethod jwt.SigningMethod) (*SigningKey, bool) {
|
||||
if signingMethod == nil {
|
||||
// Use default signign method if none given.
|
||||
signingMethod = p.signingMethodDefault
|
||||
}
|
||||
|
||||
sk, ok := p.signingKeys[signingMethod]
|
||||
return sk, ok
|
||||
}
|
||||
|
||||
// SetValidationKey sets the provider public key as validation key for token
|
||||
// validation for tokens with the provided key.
|
||||
func (p *Provider) SetValidationKey(id string, key crypto.PublicKey) error {
|
||||
p.logger.WithFields(logrus.Fields{
|
||||
"type": fmt.Sprintf("%T", key),
|
||||
"id": id,
|
||||
}).Infoln("set provider validation key")
|
||||
|
||||
p.validationKeys[id] = key
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValidationKey returns the validation key for the provided id.
|
||||
func (p *Provider) GetValidationKey(id string) (crypto.PublicKey, bool) {
|
||||
return p.getValidationKey(id)
|
||||
}
|
||||
|
||||
func (p *Provider) getValidationKey(id string) (crypto.PublicKey, bool) {
|
||||
vk, ok := p.validationKeys[id]
|
||||
return vk, ok
|
||||
}
|
||||
|
||||
// SetCertificate sets the provider certificate chain for a particular key.
|
||||
func (p *Provider) SetCertificate(id string, certificates []*x509.Certificate) error {
|
||||
p.logger.WithFields(logrus.Fields{
|
||||
"chain_size": len(certificates),
|
||||
"id": id,
|
||||
}).Infoln("set provider certificate")
|
||||
|
||||
p.certificates[id] = certificates
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitializeMetadata creates the accociated providers meta data document. Call
|
||||
// this once all other settings at the provider have been done.
|
||||
func (p *Provider) InitializeMetadata() error {
|
||||
// Create well-known document.
|
||||
p.metadata = &oidc.WellKnown{
|
||||
Issuer: p.issuerIdentifier,
|
||||
AuthorizationEndpoint: p.makeIssURL(p.authorizationPath),
|
||||
TokenEndpoint: p.makeIssURL(p.tokenPath),
|
||||
UserInfoEndpoint: p.makeIssURL(p.userInfoPath),
|
||||
EndSessionEndpoint: p.makeIssURL(p.endSessionPath),
|
||||
CheckSessionIframe: p.makeIssURL(p.checkSessionIframePath),
|
||||
JwksURI: p.makeIssURL(p.jwksPath),
|
||||
RegistrationEndpoint: p.makeIssURL(p.registrationPath),
|
||||
ScopesSupported: uniqueStrings(append([]string{
|
||||
oidc.ScopeOpenID,
|
||||
}, p.identityManager.ScopesSupported(nil)...)),
|
||||
ResponseTypesSupported: []string{
|
||||
oidc.ResponseTypeIDTokenToken,
|
||||
oidc.ResponseTypeIDToken,
|
||||
oidc.ResponseTypeCodeIDToken,
|
||||
oidc.ResponseTypeCodeIDTokenToken,
|
||||
},
|
||||
SubjectTypesSupported: []string{
|
||||
oidc.SubjectIDPublic,
|
||||
},
|
||||
ClaimsParameterSupported: true,
|
||||
ClaimsSupported: uniqueStrings(append([]string{
|
||||
oidc.IssuerIdentifierClaim,
|
||||
oidc.SubjectIdentifierClaim,
|
||||
oidc.AudienceClaim,
|
||||
oidc.ExpirationClaim,
|
||||
oidc.IssuedAtClaim,
|
||||
}, p.identityManager.ClaimsSupported(nil)...)),
|
||||
RequestParameterSupported: true,
|
||||
RequestURIParameterSupported: false,
|
||||
}
|
||||
|
||||
p.metadata.IDTokenSigningAlgValuesSupported = make([]string, 0)
|
||||
for alg := range p.signingKeys {
|
||||
p.metadata.IDTokenSigningAlgValuesSupported = append(p.metadata.IDTokenSigningAlgValuesSupported, alg.Alg())
|
||||
}
|
||||
p.metadata.UserInfoSigningAlgValuesSupported = p.metadata.IDTokenSigningAlgValuesSupported
|
||||
p.metadata.RequestObjectSigningAlgValuesSupported = []string{
|
||||
jwt.SigningMethodES256.Alg(),
|
||||
jwt.SigningMethodES384.Alg(),
|
||||
jwt.SigningMethodES512.Alg(),
|
||||
jwt.SigningMethodRS256.Alg(),
|
||||
jwt.SigningMethodRS384.Alg(),
|
||||
jwt.SigningMethodRS512.Alg(),
|
||||
jwt.SigningMethodPS256.Alg(),
|
||||
jwt.SigningMethodPS384.Alg(),
|
||||
jwt.SigningMethodPS512.Alg(),
|
||||
jwt.SigningMethodNone.Alg(),
|
||||
jwt.SigningMethodEdDSA.Alg(),
|
||||
}
|
||||
p.metadata.TokenEndpointAuthMethodsSupported = []string{
|
||||
oidc.AuthMethodClientSecretBasic,
|
||||
oidc.AuthMethodNone,
|
||||
}
|
||||
p.metadata.TokenEndpointAuthSigningAlgValuesSupported = p.metadata.IDTokenSigningAlgValuesSupported
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerHTTP implements the http.HandlerFunc interface.
|
||||
func (p *Provider) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
switch path := req.URL.Path; {
|
||||
case path == p.wellKnownPath:
|
||||
cors.Default().ServeHTTP(rw, req, p.WellKnownHandler)
|
||||
case path == p.jwksPath:
|
||||
cors.Default().ServeHTTP(rw, req, p.JwksHandler)
|
||||
case path == p.authorizationPath:
|
||||
p.AuthorizeHandler(rw, req)
|
||||
case path == p.tokenPath:
|
||||
cors.Default().ServeHTTP(rw, req, p.TokenHandler)
|
||||
case path == p.userInfoPath:
|
||||
// TODO(longsleep): Use more strict CORS.
|
||||
cors.AllowAll().ServeHTTP(rw, req, p.UserInfoHandler)
|
||||
case path == p.endSessionPath:
|
||||
p.EndSessionHandler(rw, req)
|
||||
case path == p.checkSessionIframePath:
|
||||
p.CheckSessionIframeHandler(rw, req)
|
||||
case path == p.registrationPath:
|
||||
p.RegistrationHandler(rw, req)
|
||||
default:
|
||||
http.NotFound(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorPage writes a HTML error page to the provided ResponseWriter.
|
||||
func (p *Provider) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
|
||||
utils.WriteErrorPage(rw, code, title, message)
|
||||
}
|
||||
|
||||
// Found writes a HTTP 302 to the provided ResponseWriter with the appropriate
|
||||
// Location header creates from the other parameters.
|
||||
func (p *Provider) Found(rw http.ResponseWriter, uri *url.URL, params interface{}, asFragment bool) {
|
||||
err := utils.WriteRedirect(rw, http.StatusFound, uri, params, asFragment)
|
||||
if err != nil {
|
||||
p.logger.WithError(err).Debugln("failed to write to response")
|
||||
p.ErrorPage(rw, http.StatusInternalServerError, "", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// LoginRequiredPage writes a HTTP 30 to the provided ResponseWrite with the
|
||||
// URL of the provided request (set to the scheme and host of issuer) as
|
||||
// continue parameter.
|
||||
func (p *Provider) LoginRequiredPage(rw http.ResponseWriter, req *http.Request, uri *url.URL) {
|
||||
issURI, _ := url.Parse(p.issuerIdentifier)
|
||||
|
||||
trusted, _ := utils.IsRequestFromTrustedSource(req, p.Config.Config.TrustedProxyIPs, p.Config.Config.TrustedProxyNets)
|
||||
|
||||
continueURI := getRequestURL(req, trusted)
|
||||
continueURI.Scheme = issURI.Scheme
|
||||
continueURI.Host = issURI.Host
|
||||
|
||||
uri, err := url.Parse(fmt.Sprintf("%s?continue=%s&oauth=1", uri.String(), url.QueryEscape(continueURI.String())))
|
||||
if err != nil {
|
||||
p.logger.WithError(err).Debugln("failed to parse sign-in URL")
|
||||
p.ErrorPage(rw, http.StatusInternalServerError, "", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
p.Found(rw, uri, nil, false)
|
||||
}
|
||||
|
||||
// GetAccessTokenClaimsFromRequest reads incoming request, validates the
|
||||
// access token and returns the validated claims.
|
||||
func (p *Provider) GetAccessTokenClaimsFromRequest(req *http.Request) (*konnect.AccessTokenClaims, error) {
|
||||
var err error
|
||||
var claims *konnect.AccessTokenClaims
|
||||
|
||||
auth := strings.SplitN(req.Header.Get("Authorization"), " ", 2)
|
||||
switch auth[0] {
|
||||
case oidc.TokenTypeBearer:
|
||||
if len(auth) != 2 {
|
||||
err = konnectoidc.NewOAuth2Error(oidc.ErrorCodeOAuth2InvalidRequest, "Invalid Bearer authorization header format")
|
||||
break
|
||||
}
|
||||
claims = &konnect.AccessTokenClaims{}
|
||||
_, err = jwt.ParseWithClaims(auth[1], claims, func(token *jwt.Token) (interface{}, error) {
|
||||
// Validator for incoming access tokens, looks up key.
|
||||
return p.validateJWT(token)
|
||||
})
|
||||
if err != nil {
|
||||
// Wrap as OAuth2 error.
|
||||
err = konnectoidc.NewOAuth2Error(oidc.ErrorCodeOAuth2InvalidToken, err.Error())
|
||||
}
|
||||
|
||||
default:
|
||||
err = konnectoidc.NewOAuth2Error(oidc.ErrorCodeOAuth2InvalidRequest, "Bearer authorization required")
|
||||
}
|
||||
|
||||
return claims, err
|
||||
}
|
||||
Reference in New Issue
Block a user