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
|
||||
AuthorizationEndpoint() Endpoint
|
||||
TokenEndpoint() Endpoint
|
||||
IntrospectionEndpoint() Endpoint
|
||||
UserinfoEndpoint() Endpoint
|
||||
EndSessionEndpoint() Endpoint
|
||||
KeysEndpoint() Endpoint
|
||||
|
|
|
@ -22,9 +22,9 @@ func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfigurati
|
|||
Issuer: c.Issuer(),
|
||||
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
|
||||
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
|
||||
// IntrospectionEndpoint: c.Intro().Absolute(c.Issuer()),
|
||||
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
|
||||
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
|
||||
IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()),
|
||||
UserinfoEndpoint: c.UserinfoEndpoint().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),
|
||||
|
|
17
pkg/op/op.go
17
pkg/op/op.go
|
@ -21,7 +21,7 @@ const (
|
|||
readinessEndpoint = "/ready"
|
||||
defaultAuthorizationEndpoint = "authorize"
|
||||
defaulTokenEndpoint = "oauth/token"
|
||||
defaultIntrospectEndpoint = "introspect"
|
||||
defaultIntrospectEndpoint = "oauth/introspect"
|
||||
defaultUserinfoEndpoint = "userinfo"
|
||||
defaultEndSessionEndpoint = "end_session"
|
||||
defaultKeysEndpoint = "keys"
|
||||
|
@ -78,6 +78,7 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router
|
|||
router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o)))
|
||||
router.NewRoute().Path(o.AuthorizationEndpoint().Relative()+"/callback").Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o)))
|
||||
router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o)))
|
||||
router.HandleFunc(o.IntrospectionEndpoint().Relative(), introspectionHandler(o))
|
||||
router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o))
|
||||
router.Handle(o.EndSessionEndpoint().Relative(), intercept(endSessionHandler(o)))
|
||||
router.HandleFunc(o.KeysEndpoint().Relative(), keysHandler(o))
|
||||
|
@ -166,6 +167,10 @@ func (o *openidProvider) TokenEndpoint() Endpoint {
|
|||
return o.endpoints.Token
|
||||
}
|
||||
|
||||
func (o *openidProvider) IntrospectionEndpoint() Endpoint {
|
||||
return o.endpoints.Introspection
|
||||
}
|
||||
|
||||
func (o *openidProvider) UserinfoEndpoint() Endpoint {
|
||||
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 {
|
||||
return func(o *openidProvider) error {
|
||||
if err := endpoint.Validate(); err != nil {
|
||||
|
|
|
@ -28,10 +28,15 @@ type AuthStorage interface {
|
|||
type OPStorage interface {
|
||||
GetClientByClientID(ctx context.Context, clientID string) (Client, error)
|
||||
AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error
|
||||
GetUserinfoFromScopes(ctx context.Context, userID, clientID string, scopes []string) (oidc.UserInfo, error)
|
||||
GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (oidc.UserInfo, 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
|
||||
GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (map[string]interface{}, 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 {
|
||||
|
|
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