You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
94 lines
2.5 KiB
94 lines
2.5 KiB
package toolbox
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/cloudtrust/keycloak-client/v3"
|
|
oidc "github.com/coreos/go-oidc"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// OidcVerifierProvider is an interface for a provider of OidcVerifier instances
|
|
type OidcVerifierProvider interface {
|
|
GetOidcVerifier(realm string) (OidcVerifier, error)
|
|
}
|
|
|
|
// OidcVerifier is an interface for OIDC token verifiers
|
|
type OidcVerifier interface {
|
|
Verify(accessToken string) error
|
|
}
|
|
|
|
type verifierCache struct {
|
|
duration time.Duration
|
|
errorTolerance time.Duration
|
|
tokenURL *url.URL
|
|
verifiers map[string]cachedVerifier
|
|
verifiersMutex sync.RWMutex
|
|
}
|
|
|
|
type cachedVerifier struct {
|
|
verifier *oidc.IDTokenVerifier
|
|
createdAt time.Time
|
|
expireAt time.Time
|
|
invalidateOnErrorAt time.Time
|
|
}
|
|
|
|
// NewVerifierCache create an instance of OIDC verifier cache
|
|
func NewVerifierCache(tokenURL *url.URL, timeToLive time.Duration, errorTolerance time.Duration) OidcVerifierProvider {
|
|
return &verifierCache{
|
|
duration: timeToLive,
|
|
errorTolerance: errorTolerance,
|
|
tokenURL: tokenURL,
|
|
verifiers: make(map[string]cachedVerifier),
|
|
verifiersMutex: sync.RWMutex{},
|
|
}
|
|
}
|
|
|
|
func (vc *verifierCache) GetOidcVerifier(realm string) (OidcVerifier, error) {
|
|
vc.verifiersMutex.RLock()
|
|
v, ok := vc.verifiers[realm]
|
|
vc.verifiersMutex.RUnlock()
|
|
if ok && v.isValid() {
|
|
return &v, nil
|
|
}
|
|
var oidcProvider *oidc.Provider
|
|
{
|
|
var err error
|
|
var issuer = fmt.Sprintf("%s/auth/realms/%s", vc.tokenURL.String(), realm)
|
|
oidcProvider, err = oidc.NewProvider(context.Background(), issuer)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, keycloak.MsgErrCannotCreate+"."+keycloak.OIDCProvider)
|
|
}
|
|
}
|
|
|
|
ov := oidcProvider.Verifier(&oidc.Config{SkipClientIDCheck: true})
|
|
res := cachedVerifier{
|
|
createdAt: time.Now(),
|
|
expireAt: time.Now().Add(vc.duration),
|
|
invalidateOnErrorAt: time.Now().Add(vc.errorTolerance),
|
|
verifier: ov,
|
|
}
|
|
vc.verifiersMutex.Lock()
|
|
vc.verifiers[realm] = res
|
|
vc.verifiersMutex.Unlock()
|
|
|
|
return &res, nil
|
|
}
|
|
|
|
func (cv *cachedVerifier) isValid() bool {
|
|
return time.Now().Before(cv.expireAt)
|
|
}
|
|
|
|
func (cv *cachedVerifier) Verify(accessToken string) error {
|
|
_, err := cv.verifier.Verify(context.Background(), accessToken)
|
|
if err != nil && time.Now().After(cv.invalidateOnErrorAt) {
|
|
// An error occured and current time is after invalidateOnErrorAt
|
|
// Let's make this verifier expire
|
|
cv.expireAt = cv.createdAt
|
|
}
|
|
return err
|
|
}
|
|
|