claims assertion
This commit is contained in:
parent
d6203fb0d5
commit
b8d892443c
9 changed files with 491 additions and 189 deletions
|
@ -10,7 +10,9 @@ const (
|
|||
ApplicationTypeWeb ApplicationType = iota
|
||||
ApplicationTypeUserAgent
|
||||
ApplicationTypeNative
|
||||
)
|
||||
|
||||
const (
|
||||
AccessTokenTypeBearer AccessTokenType = iota
|
||||
AccessTokenTypeJWT
|
||||
)
|
||||
|
@ -33,6 +35,8 @@ type Client interface {
|
|||
IDTokenLifetime() time.Duration
|
||||
DevMode() bool
|
||||
AllowedScopes() []string
|
||||
AssertAdditionalIdTokenScopes() bool
|
||||
AssertAdditionalAccessTokenScopes() bool
|
||||
}
|
||||
|
||||
func ContainsResponseType(types []oidc.ResponseType, responseType oidc.ResponseType) bool {
|
||||
|
|
|
@ -77,6 +77,34 @@ func (mr *MockClientMockRecorder) ApplicationType() *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplicationType", reflect.TypeOf((*MockClient)(nil).ApplicationType))
|
||||
}
|
||||
|
||||
// AssertAdditionalAccessTokenScopes mocks base method
|
||||
func (m *MockClient) AssertAdditionalAccessTokenScopes() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AssertAdditionalAccessTokenScopes")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AssertAdditionalAccessTokenScopes indicates an expected call of AssertAdditionalAccessTokenScopes
|
||||
func (mr *MockClientMockRecorder) AssertAdditionalAccessTokenScopes() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssertAdditionalAccessTokenScopes", reflect.TypeOf((*MockClient)(nil).AssertAdditionalAccessTokenScopes))
|
||||
}
|
||||
|
||||
// AssertAdditionalIdTokenScopes mocks base method
|
||||
func (m *MockClient) AssertAdditionalIdTokenScopes() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AssertAdditionalIdTokenScopes")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AssertAdditionalIdTokenScopes indicates an expected call of AssertAdditionalIdTokenScopes
|
||||
func (mr *MockClientMockRecorder) AssertAdditionalIdTokenScopes() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssertAdditionalIdTokenScopes", reflect.TypeOf((*MockClient)(nil).AssertAdditionalIdTokenScopes))
|
||||
}
|
||||
|
||||
// AuthMethod mocks base method
|
||||
func (m *MockClient) AuthMethod() op.AuthMethod {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
|
@ -171,6 +171,21 @@ func (mr *MockStorageMockRecorder) GetKeySet(arg0 interface{}) *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeySet", reflect.TypeOf((*MockStorage)(nil).GetKeySet), arg0)
|
||||
}
|
||||
|
||||
// GetPrivateClaimsFromScopes mocks base method
|
||||
func (m *MockStorage) GetPrivateClaimsFromScopes(arg0 context.Context, arg1, arg2 string, arg3 []string) (map[string]interface{}, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPrivateClaimsFromScopes", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].(map[string]interface{})
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetPrivateClaimsFromScopes indicates an expected call of GetPrivateClaimsFromScopes
|
||||
func (mr *MockStorageMockRecorder) GetPrivateClaimsFromScopes(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivateClaimsFromScopes", reflect.TypeOf((*MockStorage)(nil).GetPrivateClaimsFromScopes), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
// GetSigningKey mocks base method
|
||||
func (m *MockStorage) GetSigningKey(arg0 context.Context, arg1 chan<- jose.SigningKey, arg2 chan<- error, arg3 <-chan time.Time) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -184,10 +199,10 @@ func (mr *MockStorageMockRecorder) GetSigningKey(arg0, arg1, arg2, arg3 interfac
|
|||
}
|
||||
|
||||
// GetUserinfoFromScopes mocks base method
|
||||
func (m *MockStorage) GetUserinfoFromScopes(arg0 context.Context, arg1, arg2 string, arg3 []string) (oidc.UserInfoSetter, error) {
|
||||
func (m *MockStorage) GetUserinfoFromScopes(arg0 context.Context, arg1, arg2 string, arg3 []string) (oidc.UserInfo, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUserinfoFromScopes", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].(oidc.UserInfoSetter)
|
||||
ret0, _ := ret[0].(oidc.UserInfo)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
@ -199,10 +214,10 @@ func (mr *MockStorageMockRecorder) GetUserinfoFromScopes(arg0, arg1, arg2, arg3
|
|||
}
|
||||
|
||||
// GetUserinfoFromToken mocks base method
|
||||
func (m *MockStorage) GetUserinfoFromToken(arg0 context.Context, arg1, arg2 string) (oidc.UserInfoSetter, error) {
|
||||
func (m *MockStorage) GetUserinfoFromToken(arg0 context.Context, arg1, arg2 string) (oidc.UserInfo, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUserinfoFromToken", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(oidc.UserInfoSetter)
|
||||
ret0, _ := ret[0].(oidc.UserInfo)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
|
19
pkg/op/op.go
19
pkg/op/op.go
|
@ -51,6 +51,7 @@ type OpenIDProvider interface {
|
|||
Encoder() utils.Encoder
|
||||
IDTokenHintVerifier() IDTokenHintVerifier
|
||||
JWTProfileVerifier() JWTProfileVerifier
|
||||
AccessTokenVerifier() AccessTokenVerifier
|
||||
Crypto() Crypto
|
||||
DefaultLogoutRedirectURI() string
|
||||
Signer() Signer
|
||||
|
@ -152,6 +153,8 @@ type openidProvider struct {
|
|||
signer Signer
|
||||
idTokenHintVerifier IDTokenHintVerifier
|
||||
jwtProfileVerifier JWTProfileVerifier
|
||||
accessTokenVerifier AccessTokenVerifier
|
||||
keySet *openIDKeySet
|
||||
crypto Crypto
|
||||
httpHandler http.Handler
|
||||
decoder *schema.Decoder
|
||||
|
@ -207,7 +210,7 @@ func (o *openidProvider) Encoder() utils.Encoder {
|
|||
|
||||
func (o *openidProvider) IDTokenHintVerifier() IDTokenHintVerifier {
|
||||
if o.idTokenHintVerifier == nil {
|
||||
o.idTokenHintVerifier = NewIDTokenHintVerifier(o.Issuer(), &openIDKeySet{o.Storage()})
|
||||
o.idTokenHintVerifier = NewIDTokenHintVerifier(o.Issuer(), o.openIDKeySet())
|
||||
}
|
||||
return o.idTokenHintVerifier
|
||||
}
|
||||
|
@ -219,6 +222,20 @@ func (o *openidProvider) JWTProfileVerifier() JWTProfileVerifier {
|
|||
return o.jwtProfileVerifier
|
||||
}
|
||||
|
||||
func (o *openidProvider) AccessTokenVerifier() AccessTokenVerifier {
|
||||
if o.accessTokenVerifier == nil {
|
||||
o.accessTokenVerifier = NewAccessTokenVerifier(o.Issuer(), o.openIDKeySet())
|
||||
}
|
||||
return o.accessTokenVerifier
|
||||
}
|
||||
|
||||
func (o *openidProvider) openIDKeySet() oidc.KeySet {
|
||||
if o.keySet == nil {
|
||||
o.keySet = &openIDKeySet{o.Storage()}
|
||||
}
|
||||
return o.keySet
|
||||
}
|
||||
|
||||
func (o *openidProvider) Crypto() Crypto {
|
||||
return o.crypto
|
||||
}
|
||||
|
|
|
@ -28,8 +28,9 @@ type AuthStorage interface {
|
|||
type OPStorage interface {
|
||||
GetClientByClientID(context.Context, string) (Client, error)
|
||||
AuthorizeClientIDSecret(context.Context, string, string) error
|
||||
GetUserinfoFromScopes(context.Context, string, string, []string) (oidc.UserInfoSetter, error)
|
||||
GetUserinfoFromToken(context.Context, string, string) (oidc.UserInfoSetter, error)
|
||||
GetUserinfoFromScopes(context.Context, string, string, []string) (oidc.UserInfo, error)
|
||||
GetUserinfoFromToken(context.Context, string, string) (oidc.UserInfo, error)
|
||||
GetPrivateClaimsFromScopes(context.Context, string, string, []string) (map[string]interface{}, error)
|
||||
GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error)
|
||||
}
|
||||
|
||||
|
|
|
@ -26,12 +26,12 @@ func CreateTokenResponse(ctx context.Context, authReq AuthRequest, client Client
|
|||
var validity time.Duration
|
||||
if createAccessToken {
|
||||
var err error
|
||||
accessToken, validity, err = CreateAccessToken(ctx, authReq, client.AccessTokenType(), creator)
|
||||
accessToken, validity, err = CreateAccessToken(ctx, authReq, client.AccessTokenType(), creator, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
idToken, err := CreateIDToken(ctx, creator.Issuer(), authReq, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer())
|
||||
idToken, err := CreateIDToken(ctx, creator.Issuer(), authReq, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer(), client.AssertAdditionalIdTokenScopes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func CreateTokenResponse(ctx context.Context, authReq AuthRequest, client Client
|
|||
}
|
||||
|
||||
func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator) (*oidc.AccessTokenResponse, error) {
|
||||
accessToken, validity, err := CreateAccessToken(ctx, tokenRequest, AccessTokenTypeBearer, creator)
|
||||
accessToken, validity, err := CreateAccessToken(ctx, tokenRequest, AccessTokenTypeBearer, creator, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -64,14 +64,14 @@ func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, crea
|
|||
}, nil
|
||||
}
|
||||
|
||||
func CreateAccessToken(ctx context.Context, authReq TokenRequest, accessTokenType AccessTokenType, creator TokenCreator) (token string, validity time.Duration, err error) {
|
||||
id, exp, err := creator.Storage().CreateToken(ctx, authReq)
|
||||
func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client Client) (token string, validity time.Duration, err error) {
|
||||
id, exp, err := creator.Storage().CreateToken(ctx, tokenRequest)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
validity = exp.Sub(time.Now().UTC())
|
||||
if accessTokenType == AccessTokenTypeJWT {
|
||||
token, err = CreateJWT(creator.Issuer(), authReq, exp, id, creator.Signer())
|
||||
token, err = CreateJWT(ctx, creator.Issuer(), tokenRequest, exp, id, creator.Signer(), client, creator.Storage())
|
||||
return
|
||||
}
|
||||
token, err = CreateBearerToken(id, creator.Crypto())
|
||||
|
@ -82,14 +82,22 @@ func CreateBearerToken(id string, crypto Crypto) (string, error) {
|
|||
return crypto.Encrypt(id)
|
||||
}
|
||||
|
||||
func CreateJWT(issuer string, tokenRequest TokenRequest, exp time.Time, id string, signer Signer) (string, error) {
|
||||
func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, signer Signer, client Client, storage Storage) (string, error) {
|
||||
claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id)
|
||||
if client != nil && client.AssertAdditionalAccessTokenScopes() {
|
||||
privateClaims, err := storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(tokenRequest.GetScopes()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims.SetPrivateClaims(privateClaims)
|
||||
}
|
||||
return utils.Sign(claims, signer.Signer())
|
||||
}
|
||||
|
||||
func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer) (string, error) {
|
||||
func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer, additonalScopes bool) (string, error) {
|
||||
exp := time.Now().UTC().Add(validity)
|
||||
claims := oidc.NewIDTokenClaims(issuer, authReq.GetSubject(), authReq.GetAudience(), exp, authReq.GetAuthTime(), authReq.GetNonce(), authReq.GetACR(), authReq.GetAMR(), authReq.GetClientID())
|
||||
scopes := authReq.GetScopes()
|
||||
|
||||
if accessToken != "" {
|
||||
atHash, err := oidc.ClaimHash(accessToken, signer.SignatureAlgorithm())
|
||||
|
@ -97,8 +105,13 @@ func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, vali
|
|||
return "", err
|
||||
}
|
||||
claims.SetAccessTokenHash(atHash)
|
||||
} else {
|
||||
userInfo, err := storage.GetUserinfoFromScopes(ctx, authReq.GetSubject(), authReq.GetClientID(), authReq.GetScopes())
|
||||
scopes = removeUserinfoScopes(scopes)
|
||||
}
|
||||
if !additonalScopes {
|
||||
scopes = removeAdditionalScopes(scopes)
|
||||
}
|
||||
if len(scopes) > 0 {
|
||||
userInfo, err := storage.GetUserinfoFromScopes(ctx, authReq.GetSubject(), authReq.GetClientID(), scopes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -114,3 +127,34 @@ func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, vali
|
|||
|
||||
return utils.Sign(claims, signer.Signer())
|
||||
}
|
||||
|
||||
func removeUserinfoScopes(scopes []string) []string {
|
||||
for i := len(scopes) - 1; i >= 0; i-- {
|
||||
if scopes[i] == oidc.ScopeProfile ||
|
||||
scopes[i] == oidc.ScopeEmail ||
|
||||
scopes[i] == oidc.ScopeAddress ||
|
||||
scopes[i] == oidc.ScopePhone {
|
||||
|
||||
scopes[i] = scopes[len(scopes)-1]
|
||||
scopes[len(scopes)-1] = ""
|
||||
scopes = scopes[:len(scopes)-1]
|
||||
}
|
||||
}
|
||||
return scopes
|
||||
}
|
||||
|
||||
func removeAdditionalScopes(scopes []string) []string {
|
||||
for i := len(scopes) - 1; i >= 0; i-- {
|
||||
if !(scopes[i] == oidc.ScopeOpenID ||
|
||||
scopes[i] == oidc.ScopeProfile ||
|
||||
scopes[i] == oidc.ScopeEmail ||
|
||||
scopes[i] == oidc.ScopeAddress ||
|
||||
scopes[i] == oidc.ScopePhone) {
|
||||
|
||||
scopes[i] = scopes[len(scopes)-1]
|
||||
scopes[len(scopes)-1] = ""
|
||||
scopes = scopes[:len(scopes)-1]
|
||||
}
|
||||
}
|
||||
return scopes
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ type UserinfoProvider interface {
|
|||
Decoder() utils.Decoder
|
||||
Crypto() Crypto
|
||||
Storage() Storage
|
||||
AccessTokenVerifier() AccessTokenVerifier
|
||||
}
|
||||
|
||||
func userinfoHandler(userinfoProvider UserinfoProvider) func(http.ResponseWriter, *http.Request) {
|
||||
|
@ -27,10 +28,20 @@ func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoP
|
|||
http.Error(w, "access token missing", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
tokenID, err := userinfoProvider.Crypto().Decrypt(accessToken)
|
||||
if err != nil {
|
||||
http.Error(w, "access token missing", http.StatusUnauthorized)
|
||||
return
|
||||
var tokenID string
|
||||
if strings.HasPrefix(accessToken, "eyJhbGci") { //TODO: improve
|
||||
accessTokenClaims, err := VerifyAccessToken(r.Context(), accessToken, userinfoProvider.AccessTokenVerifier())
|
||||
if err != nil {
|
||||
http.Error(w, "access token invalid", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
tokenID = accessTokenClaims.GetTokenID()
|
||||
} else {
|
||||
tokenID, err = userinfoProvider.Crypto().Decrypt(accessToken)
|
||||
if err != nil {
|
||||
http.Error(w, "access token invalid", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
info, err := userinfoProvider.Storage().GetUserinfoFromToken(r.Context(), tokenID, r.Header.Get("origin"))
|
||||
if err != nil {
|
||||
|
|
85
pkg/op/verifier_access_token.go
Normal file
85
pkg/op/verifier_access_token.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package op
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
type AccessTokenVerifier interface {
|
||||
oidc.Verifier
|
||||
SupportedSignAlgs() []string
|
||||
KeySet() oidc.KeySet
|
||||
}
|
||||
|
||||
type accessTokenVerifier struct {
|
||||
issuer string
|
||||
maxAgeIAT time.Duration
|
||||
offset time.Duration
|
||||
supportedSignAlgs []string
|
||||
maxAge time.Duration
|
||||
acr oidc.ACRVerifier
|
||||
keySet oidc.KeySet
|
||||
}
|
||||
|
||||
//Issuer implements oidc.Verifier interface
|
||||
func (i *accessTokenVerifier) Issuer() string {
|
||||
return i.issuer
|
||||
}
|
||||
|
||||
//MaxAgeIAT implements oidc.Verifier interface
|
||||
func (i *accessTokenVerifier) MaxAgeIAT() time.Duration {
|
||||
return i.maxAgeIAT
|
||||
}
|
||||
|
||||
//Offset implements oidc.Verifier interface
|
||||
func (i *accessTokenVerifier) Offset() time.Duration {
|
||||
return i.offset
|
||||
}
|
||||
|
||||
//SupportedSignAlgs implements AccessTokenVerifier interface
|
||||
func (i *accessTokenVerifier) SupportedSignAlgs() []string {
|
||||
return i.supportedSignAlgs
|
||||
}
|
||||
|
||||
//KeySet implements AccessTokenVerifier interface
|
||||
func (i *accessTokenVerifier) KeySet() oidc.KeySet {
|
||||
return i.keySet
|
||||
}
|
||||
|
||||
func NewAccessTokenVerifier(issuer string, keySet oidc.KeySet) AccessTokenVerifier {
|
||||
verifier := &idTokenHintVerifier{
|
||||
issuer: issuer,
|
||||
keySet: keySet,
|
||||
}
|
||||
return verifier
|
||||
}
|
||||
|
||||
//VerifyAccessToken validates the access token (issuer, signature and expiration)
|
||||
func VerifyAccessToken(ctx context.Context, token string, v AccessTokenVerifier) (oidc.AccessTokenClaims, error) {
|
||||
claims := oidc.EmptyAccessTokenClaims()
|
||||
|
||||
decrypted, err := oidc.DecryptToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload, err := oidc.ParseToken(decrypted, claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := oidc.CheckIssuer(claims, v.Issuer()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs(), v.KeySet()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckExpiration(claims, v.Offset()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue