introspect
This commit is contained in:
parent
d693f6113d
commit
a1a21f0d59
6 changed files with 341 additions and 6 deletions
256
pkg/oidc/introspection.go
Normal file
256
pkg/oidc/introspection.go
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
|
"github.com/caos/oidc/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntrospectionRequest struct {
|
||||||
|
Token string `schema:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntrospectionResponse interface {
|
||||||
|
UserInfoSetter
|
||||||
|
SetActive(bool)
|
||||||
|
IsActive() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIntrospectionResponse() IntrospectionResponse {
|
||||||
|
return &introspectionResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type introspectionResponse struct {
|
||||||
|
Active bool `json:"active"`
|
||||||
|
Subject string `json:"sub,omitempty"`
|
||||||
|
userInfoProfile
|
||||||
|
userInfoEmail
|
||||||
|
userInfoPhone
|
||||||
|
Address UserInfoAddress `json:"address,omitempty"`
|
||||||
|
|
||||||
|
claims map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) IsActive() bool {
|
||||||
|
return u.Active
|
||||||
|
}
|
||||||
|
func (u *introspectionResponse) GetSubject() string {
|
||||||
|
return u.Subject
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetName() string {
|
||||||
|
return u.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetGivenName() string {
|
||||||
|
return u.GivenName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetFamilyName() string {
|
||||||
|
return u.FamilyName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetMiddleName() string {
|
||||||
|
return u.MiddleName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetNickname() string {
|
||||||
|
return u.Nickname
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetProfile() string {
|
||||||
|
return u.Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetPicture() string {
|
||||||
|
return u.Picture
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetWebsite() string {
|
||||||
|
return u.Website
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetGender() Gender {
|
||||||
|
return u.Gender
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetBirthdate() string {
|
||||||
|
return u.Birthdate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetZoneinfo() string {
|
||||||
|
return u.Zoneinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetLocale() language.Tag {
|
||||||
|
return u.Locale
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetPreferredUsername() string {
|
||||||
|
return u.PreferredUsername
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetEmail() string {
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) IsEmailVerified() bool {
|
||||||
|
return u.EmailVerified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetPhoneNumber() string {
|
||||||
|
return u.PhoneNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) IsPhoneNumberVerified() bool {
|
||||||
|
return u.PhoneNumberVerified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetAddress() UserInfoAddress {
|
||||||
|
return u.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) GetClaim(key string) interface{} {
|
||||||
|
return u.claims[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetActive(active bool) {
|
||||||
|
u.Active = active
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetSubject(sub string) {
|
||||||
|
u.Subject = sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetName(name string) {
|
||||||
|
u.Name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetGivenName(name string) {
|
||||||
|
u.GivenName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetFamilyName(name string) {
|
||||||
|
u.FamilyName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetMiddleName(name string) {
|
||||||
|
u.MiddleName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetNickname(name string) {
|
||||||
|
u.Nickname = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetUpdatedAt(date time.Time) {
|
||||||
|
u.UpdatedAt = Time(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetProfile(profile string) {
|
||||||
|
u.Profile = profile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetPicture(picture string) {
|
||||||
|
u.Picture = picture
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetWebsite(website string) {
|
||||||
|
u.Website = website
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetGender(gender Gender) {
|
||||||
|
u.Gender = gender
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetBirthdate(birthdate string) {
|
||||||
|
u.Birthdate = birthdate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetZoneinfo(zoneInfo string) {
|
||||||
|
u.Zoneinfo = zoneInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetLocale(locale language.Tag) {
|
||||||
|
u.Locale = locale
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetPreferredUsername(name string) {
|
||||||
|
u.PreferredUsername = name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetEmail(email string, verified bool) {
|
||||||
|
u.Email = email
|
||||||
|
u.EmailVerified = verified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetPhone(phone string, verified bool) {
|
||||||
|
u.PhoneNumber = phone
|
||||||
|
u.PhoneNumberVerified = verified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) SetAddress(address UserInfoAddress) {
|
||||||
|
u.Address = address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *introspectionResponse) AppendClaims(key string, value interface{}) {
|
||||||
|
if u.claims == nil {
|
||||||
|
u.claims = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
u.claims[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *introspectionResponse) MarshalJSON() ([]byte, error) {
|
||||||
|
type Alias introspectionResponse
|
||||||
|
a := &struct {
|
||||||
|
*Alias
|
||||||
|
Locale interface{} `json:"locale,omitempty"`
|
||||||
|
UpdatedAt int64 `json:"updated_at,omitempty"`
|
||||||
|
PreferredUsername string `json:"username,omitempty"`
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(i),
|
||||||
|
}
|
||||||
|
if !i.Locale.IsRoot() {
|
||||||
|
a.Locale = i.Locale
|
||||||
|
}
|
||||||
|
if !time.Time(i.UpdatedAt).IsZero() {
|
||||||
|
a.UpdatedAt = time.Time(i.UpdatedAt).Unix()
|
||||||
|
}
|
||||||
|
a.PreferredUsername = i.PreferredUsername
|
||||||
|
i.PreferredUsername = ""
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(i.claims) == 0 {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := json.Marshal(i.claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("jws: invalid map of custom claims %v", i.claims)
|
||||||
|
}
|
||||||
|
return utils.ConcatenateJSON(b, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *introspectionResponse) UnmarshalJSON(data []byte) error {
|
||||||
|
type Alias introspectionResponse
|
||||||
|
a := &struct {
|
||||||
|
*Alias
|
||||||
|
UpdatedAt int64 `json:"update_at,omitempty"`
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(i),
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
i.UpdatedAt = Time(time.Unix(a.UpdatedAt, 0).UTC())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ type Configuration interface {
|
||||||
Issuer() string
|
Issuer() string
|
||||||
AuthorizationEndpoint() Endpoint
|
AuthorizationEndpoint() Endpoint
|
||||||
TokenEndpoint() Endpoint
|
TokenEndpoint() Endpoint
|
||||||
|
IntrospectionEndpoint() Endpoint
|
||||||
UserinfoEndpoint() Endpoint
|
UserinfoEndpoint() Endpoint
|
||||||
EndSessionEndpoint() Endpoint
|
EndSessionEndpoint() Endpoint
|
||||||
KeysEndpoint() Endpoint
|
KeysEndpoint() Endpoint
|
||||||
|
|
|
@ -22,9 +22,9 @@ func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfigurati
|
||||||
Issuer: c.Issuer(),
|
Issuer: c.Issuer(),
|
||||||
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
|
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
|
||||||
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
|
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
|
||||||
// IntrospectionEndpoint: c.Intro().Absolute(c.Issuer()),
|
IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()),
|
||||||
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
|
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
|
||||||
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
|
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
|
||||||
// CheckSessionIframe: c.TokenEndpoint().Absolute(c.Issuer())(c.CheckSessionIframe),
|
// CheckSessionIframe: c.TokenEndpoint().Absolute(c.Issuer())(c.CheckSessionIframe),
|
||||||
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
|
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
|
||||||
ScopesSupported: Scopes(c),
|
ScopesSupported: Scopes(c),
|
||||||
|
|
17
pkg/op/op.go
17
pkg/op/op.go
|
@ -21,7 +21,7 @@ const (
|
||||||
readinessEndpoint = "/ready"
|
readinessEndpoint = "/ready"
|
||||||
defaultAuthorizationEndpoint = "authorize"
|
defaultAuthorizationEndpoint = "authorize"
|
||||||
defaulTokenEndpoint = "oauth/token"
|
defaulTokenEndpoint = "oauth/token"
|
||||||
defaultIntrospectEndpoint = "introspect"
|
defaultIntrospectEndpoint = "oauth/introspect"
|
||||||
defaultUserinfoEndpoint = "userinfo"
|
defaultUserinfoEndpoint = "userinfo"
|
||||||
defaultEndSessionEndpoint = "end_session"
|
defaultEndSessionEndpoint = "end_session"
|
||||||
defaultKeysEndpoint = "keys"
|
defaultKeysEndpoint = "keys"
|
||||||
|
@ -78,6 +78,7 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router
|
||||||
router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o)))
|
router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o)))
|
||||||
router.NewRoute().Path(o.AuthorizationEndpoint().Relative()+"/callback").Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o)))
|
router.NewRoute().Path(o.AuthorizationEndpoint().Relative()+"/callback").Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o)))
|
||||||
router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o)))
|
router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o)))
|
||||||
|
router.HandleFunc(o.IntrospectionEndpoint().Relative(), introspectionHandler(o))
|
||||||
router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o))
|
router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o))
|
||||||
router.Handle(o.EndSessionEndpoint().Relative(), intercept(endSessionHandler(o)))
|
router.Handle(o.EndSessionEndpoint().Relative(), intercept(endSessionHandler(o)))
|
||||||
router.HandleFunc(o.KeysEndpoint().Relative(), keysHandler(o))
|
router.HandleFunc(o.KeysEndpoint().Relative(), keysHandler(o))
|
||||||
|
@ -166,6 +167,10 @@ func (o *openidProvider) TokenEndpoint() Endpoint {
|
||||||
return o.endpoints.Token
|
return o.endpoints.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *openidProvider) IntrospectionEndpoint() Endpoint {
|
||||||
|
return o.endpoints.Introspection
|
||||||
|
}
|
||||||
|
|
||||||
func (o *openidProvider) UserinfoEndpoint() Endpoint {
|
func (o *openidProvider) UserinfoEndpoint() Endpoint {
|
||||||
return o.endpoints.Userinfo
|
return o.endpoints.Userinfo
|
||||||
}
|
}
|
||||||
|
@ -332,6 +337,16 @@ func WithCustomTokenEndpoint(endpoint Endpoint) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithCustomIntrospectionEndpoint(endpoint Endpoint) Option {
|
||||||
|
return func(o *openidProvider) error {
|
||||||
|
if err := endpoint.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.endpoints.Introspection = endpoint
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithCustomUserinfoEndpoint(endpoint Endpoint) Option {
|
func WithCustomUserinfoEndpoint(endpoint Endpoint) Option {
|
||||||
return func(o *openidProvider) error {
|
return func(o *openidProvider) error {
|
||||||
if err := endpoint.Validate(); err != nil {
|
if err := endpoint.Validate(); err != nil {
|
||||||
|
|
|
@ -28,10 +28,15 @@ type AuthStorage interface {
|
||||||
type OPStorage interface {
|
type OPStorage interface {
|
||||||
GetClientByClientID(ctx context.Context, clientID string) (Client, error)
|
GetClientByClientID(ctx context.Context, clientID string) (Client, error)
|
||||||
AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error
|
AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error
|
||||||
GetUserinfoFromScopes(ctx context.Context, userID, clientID string, scopes []string) (oidc.UserInfo, error)
|
SetUserinfoFromScopes(ctx context.Context, userinfo oidc.UserInfoSetter, userID, clientID string, scopes []string) error
|
||||||
GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (oidc.UserInfo, error)
|
SetUserinfoFromToken(ctx context.Context, userinfo oidc.UserInfoSetter, tokenID, subject, origin string) error
|
||||||
GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (map[string]interface{}, error)
|
GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (map[string]interface{}, error)
|
||||||
GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error)
|
GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error)
|
||||||
|
|
||||||
|
//deprecated: use GetUserinfoFromScopes instead
|
||||||
|
GetUserinfoFromScopes(ctx context.Context, userID, clientID string, scopes []string) (oidc.UserInfo, error)
|
||||||
|
//deprecated: use SetUserinfoFromToken instead
|
||||||
|
GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (oidc.UserInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
|
|
58
pkg/op/token_intospection.go
Normal file
58
pkg/op/token_intospection.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package op
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
|
"github.com/caos/oidc/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Introspector interface {
|
||||||
|
Decoder() utils.Decoder
|
||||||
|
Crypto() Crypto
|
||||||
|
Storage() Storage
|
||||||
|
AccessTokenVerifier() AccessTokenVerifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func introspectionHandler(introspector Introspector) func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
Introspect(w, r, introspector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Introspect(w http.ResponseWriter, r *http.Request, introspector Introspector) {
|
||||||
|
//validate authorization
|
||||||
|
|
||||||
|
response := oidc.NewIntrospectionResponse()
|
||||||
|
token, err := ParseTokenInrospectionRequest(r, introspector.Decoder())
|
||||||
|
if err != nil {
|
||||||
|
utils.MarshalJSON(w, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tokenID, subject, ok := getTokenIDAndSubject(r.Context(), introspector, token)
|
||||||
|
if !ok {
|
||||||
|
utils.MarshalJSON(w, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = introspector.Storage().SetUserinfoFromToken(r.Context(), response, tokenID, subject, r.Header.Get("origin"))
|
||||||
|
if err != nil {
|
||||||
|
utils.MarshalJSON(w, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetActive(true)
|
||||||
|
utils.MarshalJSON(w, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseTokenInrospectionRequest(r *http.Request, decoder utils.Decoder) (string, error) {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("unable to parse request")
|
||||||
|
}
|
||||||
|
req := new(oidc.IntrospectionRequest)
|
||||||
|
err = decoder.Decode(req, r.Form)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("unable to parse request")
|
||||||
|
}
|
||||||
|
return req.Token, nil
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue