introspect

This commit is contained in:
Livio Amstutz 2021-02-10 16:42:01 +01:00
parent 134999bc33
commit 138da8a208
13 changed files with 305 additions and 98 deletions

View file

@ -3,6 +3,8 @@ package op
import (
"net/http"
"golang.org/x/text/language"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/utils"
)
@ -24,17 +26,22 @@ func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfigurati
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()),
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
//RevocationEndpoint: c.RevocationEndpoint().Absolute(c.Issuer()),
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
// CheckSessionIframe: c.TokenEndpoint().Absolute(c.Issuer())(c.CheckSessionIframe),
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
ScopesSupported: Scopes(c),
ResponseTypesSupported: ResponseTypes(c),
GrantTypesSupported: GrantTypes(c),
ClaimsSupported: SupportedClaims(c),
IDTokenSigningAlgValuesSupported: SigAlgorithms(s),
SubjectTypesSupported: SubjectTypes(c),
TokenEndpointAuthMethodsSupported: AuthMethods(c),
CodeChallengeMethodsSupported: CodeChallengeMethods(c),
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
ScopesSupported: Scopes(c),
ResponseTypesSupported: ResponseTypes(c),
//ResponseModesSupported:
GrantTypesSupported: GrantTypes(c),
//ACRValuesSupported: ACRValues(c),
SubjectTypesSupported: SubjectTypes(c),
IDTokenSigningAlgValuesSupported: SigAlgorithms(s),
TokenEndpointAuthMethodsSupported: AuthMethodsTokenEndpoint(c),
IntrospectionEndpointAuthMethodsSupported: AuthMethodsIntrospectionEndpoint(c),
ClaimsSupported: SupportedClaims(c),
CodeChallengeMethodsSupported: CodeChallengeMethods(c),
UILocalesSupported: UILocales(c),
}
}
@ -58,15 +65,16 @@ func ResponseTypes(c Configuration) []string {
} //TODO: ok for now, check later if dynamic needed
}
func GrantTypes(c Configuration) []string {
grantTypes := []string{
string(oidc.GrantTypeCode),
func GrantTypes(c Configuration) []oidc.GrantType {
grantTypes := []oidc.GrantType{
oidc.GrantTypeCode,
oidc.GrantTypeImplicit,
}
if c.GrantTypeTokenExchangeSupported() {
grantTypes = append(grantTypes, string(oidc.GrantTypeTokenExchange))
grantTypes = append(grantTypes, oidc.GrantTypeTokenExchange)
}
if c.GrantTypeJWTAuthorizationSupported() {
grantTypes = append(grantTypes, string(oidc.GrantTypeBearer))
grantTypes = append(grantTypes, oidc.GrantTypeBearer)
}
return grantTypes
}
@ -108,7 +116,7 @@ func SubjectTypes(c Configuration) []string {
return []string{"public"} //TODO: config
}
func AuthMethods(c Configuration) []oidc.AuthMethod {
func AuthMethodsTokenEndpoint(c Configuration) []oidc.AuthMethod {
authMethods := []oidc.AuthMethod{
oidc.AuthMethodNone,
oidc.AuthMethodBasic,
@ -122,6 +130,16 @@ func AuthMethods(c Configuration) []oidc.AuthMethod {
return authMethods
}
func AuthMethodsIntrospectionEndpoint(c Configuration) []oidc.AuthMethod {
authMethods := []oidc.AuthMethod{
oidc.AuthMethodBasic,
}
if c.AuthMethodPrivateKeyJWTSupported() {
authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT)
}
return authMethods
}
func CodeChallengeMethods(c Configuration) []oidc.CodeChallengeMethod {
codeMethods := make([]oidc.CodeChallengeMethod, 0, 1)
if c.CodeMethodS256Supported() {
@ -129,3 +147,10 @@ func CodeChallengeMethods(c Configuration) []oidc.CodeChallengeMethod {
}
return codeMethods
}
func UILocales(c Configuration) []language.Tag {
return []language.Tag{
language.English,
language.German,
}
}

View file

@ -30,7 +30,7 @@ type OPStorage interface {
AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error
SetUserinfoFromScopes(ctx context.Context, userinfo oidc.UserInfoSetter, userID, clientID string, scopes []string) error
SetUserinfoFromToken(ctx context.Context, userinfo oidc.UserInfoSetter, tokenID, subject, origin string) error
SetIntrospectionFromToken(ctx context.Context, userinfo oidc.IntrospectionResponse, tokenID, subject, callerTokenID, callerSubject string) error
SetIntrospectionFromToken(ctx context.Context, userinfo oidc.IntrospectionResponse, tokenID, subject, clientID string) error
GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (map[string]interface{}, error)
GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error)
ValidateJWTProfileScopes(ctx context.Context, userID string, scope oidc.Scopes) (oidc.Scopes, error)

View file

@ -3,7 +3,6 @@ package op
import (
"errors"
"net/http"
"strings"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/utils"
@ -16,6 +15,11 @@ type Introspector interface {
AccessTokenVerifier() AccessTokenVerifier
}
type IntrospectorJWTProfile interface {
Introspector
JWTProfileVerifier() JWTProfileVerifier
}
func introspectionHandler(introspector Introspector) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
Introspect(w, r, introspector)
@ -23,24 +27,18 @@ func introspectionHandler(introspector Introspector) func(http.ResponseWriter, *
}
func Introspect(w http.ResponseWriter, r *http.Request, introspector Introspector) {
callerToken := r.Header.Get("authorization")
response := oidc.NewIntrospectionResponse()
callerToken, callerSubject, ok := getTokenIDAndSubject(r.Context(), introspector, strings.TrimPrefix(callerToken, oidc.PrefixBearer))
if !ok {
utils.MarshalJSON(w, response)
return
}
introspectionToken, err := ParseTokenInrospectionRequest(r, introspector.Decoder())
token, clientID, err := ParseTokenIntrospectionRequest(r, introspector)
if err != nil {
utils.MarshalJSON(w, response)
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
tokenID, subject, ok := getTokenIDAndSubject(r.Context(), introspector, introspectionToken)
tokenID, subject, ok := getTokenIDAndSubject(r.Context(), introspector, token)
if !ok {
utils.MarshalJSON(w, response)
return
}
err = introspector.Storage().SetIntrospectionFromToken(r.Context(), response, tokenID, subject, callerToken, callerSubject)
err = introspector.Storage().SetIntrospectionFromToken(r.Context(), response, tokenID, subject, clientID)
if err != nil {
utils.MarshalJSON(w, response)
return
@ -49,15 +47,31 @@ func Introspect(w http.ResponseWriter, r *http.Request, introspector Introspecto
utils.MarshalJSON(w, response)
}
func ParseTokenInrospectionRequest(r *http.Request, decoder utils.Decoder) (string, error) {
err := r.ParseForm()
func ParseTokenIntrospectionRequest(r *http.Request, introspector Introspector) (token, clientID string, err error) {
err = r.ParseForm()
if err != nil {
return "", errors.New("unable to parse request")
return "", "", errors.New("unable to parse request")
}
req := new(oidc.IntrospectionRequest)
err = decoder.Decode(req, r.Form)
req := new(struct {
oidc.IntrospectionRequest
oidc.ClientAssertionParams
})
err = introspector.Decoder().Decode(req, r.Form)
if err != nil {
return "", errors.New("unable to parse request")
return "", "", errors.New("unable to parse request")
}
return req.Token, nil
if introspectorJWTProfile, ok := introspector.(IntrospectorJWTProfile); ok && req.ClientAssertion != "" {
profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, introspectorJWTProfile.JWTProfileVerifier())
if err == nil {
return req.Token, profile.Issuer, nil
}
}
clientID, clientSecret, ok := r.BasicAuth()
if ok {
if err := introspector.Storage().AuthorizeClientIDSecret(r.Context(), clientID, clientSecret); err != nil {
return "", "", err
}
return req.Token, clientID, nil
}
return "", "", errors.New("invalid authorization")
}

View file

@ -24,7 +24,7 @@ func userinfoHandler(userinfoProvider UserinfoProvider) func(http.ResponseWriter
}
func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoProvider) {
accessToken, err := getAccessToken(r, userinfoProvider.Decoder())
accessToken, err := ParseUserinfoRequest(r, userinfoProvider.Decoder())
if err != nil {
http.Error(w, "access token missing", http.StatusUnauthorized)
return
@ -43,16 +43,12 @@ func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoP
utils.MarshalJSON(w, info)
}
func getAccessToken(r *http.Request, decoder utils.Decoder) (string, error) {
authHeader := r.Header.Get("authorization")
if authHeader != "" {
parts := strings.Split(authHeader, "Bearer ")
if len(parts) != 2 {
return "", errors.New("invalid auth header")
}
return parts[1], nil
func ParseUserinfoRequest(r *http.Request, decoder utils.Decoder) (string, error) {
accessToken, err := getAccessToken(r)
if err == nil {
return accessToken, nil
}
err := r.ParseForm()
err = r.ParseForm()
if err != nil {
return "", errors.New("unable to parse request")
}
@ -64,6 +60,18 @@ func getAccessToken(r *http.Request, decoder utils.Decoder) (string, error) {
return req.AccessToken, nil
}
func getAccessToken(r *http.Request) (string, error) {
authHeader := r.Header.Get("authorization")
if authHeader == "" {
return "", errors.New("no auth header")
}
parts := strings.Split(authHeader, "Bearer ")
if len(parts) != 2 {
return "", errors.New("invalid auth header")
}
return parts[1], nil
}
func getTokenIDAndSubject(ctx context.Context, userinfoProvider UserinfoProvider, accessToken string) (string, string, bool) {
tokenIDSubject, err := userinfoProvider.Crypto().Decrypt(accessToken)
if err == nil {