working userinfo
This commit is contained in:
parent
04cda7c843
commit
f14302fc29
7 changed files with 478 additions and 63 deletions
|
@ -191,7 +191,10 @@ func (s *AuthStorage) AuthorizeClientIDSecret(_ context.Context, id string, _ st
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthStorage) GetUserinfoFromScopes(context.Context, []string) (*oidc.Userinfo, error) {
|
func (s *AuthStorage) GetUserinfoFromToken(ctx context.Context, _ string) (*oidc.Userinfo, error) {
|
||||||
|
return s.GetUserinfoFromScopes(ctx, "", []string{})
|
||||||
|
}
|
||||||
|
func (s *AuthStorage) GetUserinfoFromScopes(_ context.Context, _ string, _ []string) (*oidc.Userinfo, error) {
|
||||||
return &oidc.Userinfo{
|
return &oidc.Userinfo{
|
||||||
Subject: a.GetSubject(),
|
Subject: a.GetSubject(),
|
||||||
Address: &oidc.UserinfoAddress{
|
Address: &oidc.UserinfoAddress{
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/utils"
|
"github.com/caos/oidc/pkg/utils"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,6 +54,10 @@ type IDTokenClaims struct {
|
||||||
AuthenticationContextClassReference string
|
AuthenticationContextClassReference string
|
||||||
AuthenticationMethodsReferences []string
|
AuthenticationMethodsReferences []string
|
||||||
ClientID string
|
ClientID string
|
||||||
|
UserinfoProfile
|
||||||
|
UserinfoEmail
|
||||||
|
UserinfoPhone
|
||||||
|
UserinfoAddress *UserinfoAddress
|
||||||
|
|
||||||
Signature jose.SignatureAlgorithm //TODO: ???
|
Signature jose.SignatureAlgorithm //TODO: ???
|
||||||
}
|
}
|
||||||
|
@ -65,7 +70,6 @@ type jsonToken struct {
|
||||||
NotBefore int64 `json:"nbf,omitempty"`
|
NotBefore int64 `json:"nbf,omitempty"`
|
||||||
IssuedAt int64 `json:"iat,omitempty"`
|
IssuedAt int64 `json:"iat,omitempty"`
|
||||||
JWTID string `json:"jti,omitempty"`
|
JWTID string `json:"jti,omitempty"`
|
||||||
UpdatedAt int64 `json:"updated_at,omitempty"`
|
|
||||||
AuthorizedParty string `json:"azp,omitempty"`
|
AuthorizedParty string `json:"azp,omitempty"`
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
AuthTime int64 `json:"auth_time,omitempty"`
|
AuthTime int64 `json:"auth_time,omitempty"`
|
||||||
|
@ -79,6 +83,7 @@ type jsonToken struct {
|
||||||
ClientID string `json:"client_id,omitempty"`
|
ClientID string `json:"client_id,omitempty"`
|
||||||
AuthorizedActor interface{} `json:"may_act,omitempty"` //TODO: impl
|
AuthorizedActor interface{} `json:"may_act,omitempty"` //TODO: impl
|
||||||
AccessTokenUseNumber int `json:"at_use_nbr,omitempty"`
|
AccessTokenUseNumber int `json:"at_use_nbr,omitempty"`
|
||||||
|
jsonUserinfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *AccessTokenClaims) MarshalJSON() ([]byte, error) {
|
func (t *AccessTokenClaims) MarshalJSON() ([]byte, error) {
|
||||||
|
@ -142,7 +147,6 @@ func (t *IDTokenClaims) MarshalJSON() ([]byte, error) {
|
||||||
NotBefore: timeToJSON(t.NotBefore),
|
NotBefore: timeToJSON(t.NotBefore),
|
||||||
IssuedAt: timeToJSON(t.IssuedAt),
|
IssuedAt: timeToJSON(t.IssuedAt),
|
||||||
JWTID: t.JWTID,
|
JWTID: t.JWTID,
|
||||||
UpdatedAt: timeToJSON(t.UpdatedAt),
|
|
||||||
AuthorizedParty: t.AuthorizedParty,
|
AuthorizedParty: t.AuthorizedParty,
|
||||||
Nonce: t.Nonce,
|
Nonce: t.Nonce,
|
||||||
AuthTime: timeToJSON(t.AuthTime),
|
AuthTime: timeToJSON(t.AuthTime),
|
||||||
|
@ -152,8 +156,71 @@ func (t *IDTokenClaims) MarshalJSON() ([]byte, error) {
|
||||||
AuthenticationMethodsReferences: t.AuthenticationMethodsReferences,
|
AuthenticationMethodsReferences: t.AuthenticationMethodsReferences,
|
||||||
ClientID: t.ClientID,
|
ClientID: t.ClientID,
|
||||||
}
|
}
|
||||||
|
j.setUserinfo(t)
|
||||||
return json.Marshal(j)
|
return json.Marshal(j)
|
||||||
}
|
}
|
||||||
|
func (t *IDTokenClaims) GetUserinfoProfile() UserinfoProfile {
|
||||||
|
return t.UserinfoProfile
|
||||||
|
}
|
||||||
|
func (t *IDTokenClaims) GetUserinfoEmail() UserinfoEmail {
|
||||||
|
return t.UserinfoEmail
|
||||||
|
}
|
||||||
|
func (t *IDTokenClaims) GetUserinfoPhone() UserinfoPhone {
|
||||||
|
return t.UserinfoPhone
|
||||||
|
}
|
||||||
|
func (t *IDTokenClaims) GetAddress() *UserinfoAddress {
|
||||||
|
return t.UserinfoAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (t *IDTokenClaims) GetUserinfoEmail() UserinfoEmailI {
|
||||||
|
// return t.UserinfoEmail
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (t *IDTokenClaims) setUserinfo(j *jsonToken) {
|
||||||
|
// t.setUserinfoProfile(j)
|
||||||
|
// t.setUserinfoEmail(j)
|
||||||
|
// t.setUserinfoPhone(j)
|
||||||
|
// t.setUserinfoAddress(j)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (t *IDTokenClaims) setUserinfoProfile(j *jsonToken) {
|
||||||
|
// j.Name = t.Name
|
||||||
|
// j.GivenName = t.GivenName
|
||||||
|
// j.FamilyName = t.FamilyName
|
||||||
|
// j.MiddleName = t.MiddleName
|
||||||
|
// j.Nickname = t.Nickname
|
||||||
|
// j.Profile = t.Profile
|
||||||
|
// j.Picture = t.Picture
|
||||||
|
// j.Website = t.Website
|
||||||
|
// j.Gender = string(t.Gender)
|
||||||
|
// j.Birthdate = t.Birthdate
|
||||||
|
// j.Zoneinfo = t.Zoneinfo
|
||||||
|
// j.Locale = t.Locale.String()
|
||||||
|
// j.UpdatedAt = timeToJSON(t.UpdatedAt)
|
||||||
|
// j.PreferredUsername = t.PreferredUsername
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (t *IDTokenClaims) setUserinfoEmail(j *jsonToken) {
|
||||||
|
// j.Email = t.Email
|
||||||
|
// j.EmailVerified = t.EmailVerified
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (t *IDTokenClaims) setUserinfoPhone(j *jsonToken) {
|
||||||
|
// j.Phone = t.PhoneNumber
|
||||||
|
// j.PhoneVerified = t.PhoneNumberVerified
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (t *IDTokenClaims) setUserinfoAddress(j *jsonToken) {
|
||||||
|
// if t.UserinfoAddress == nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// j.jsonUserinfoAddress.Country = t.UserinfoAddress.Country
|
||||||
|
// j.jsonUserinfoAddress.Formatted = t.UserinfoAddress.Formatted
|
||||||
|
// j.jsonUserinfoAddress.Locality = t.UserinfoAddress.Locality
|
||||||
|
// j.jsonUserinfoAddress.PostalCode = t.UserinfoAddress.PostalCode
|
||||||
|
// j.jsonUserinfoAddress.Region = t.UserinfoAddress.Region
|
||||||
|
// j.jsonUserinfoAddress.StreetAddress = t.UserinfoAddress.StreetAddress
|
||||||
|
// }
|
||||||
|
|
||||||
func (t *IDTokenClaims) UnmarshalJSON(b []byte) error {
|
func (t *IDTokenClaims) UnmarshalJSON(b []byte) error {
|
||||||
var i jsonToken
|
var i jsonToken
|
||||||
|
@ -176,9 +243,61 @@ func (t *IDTokenClaims) UnmarshalJSON(b []byte) error {
|
||||||
t.AuthorizedParty = i.AuthorizedParty
|
t.AuthorizedParty = i.AuthorizedParty
|
||||||
t.AccessTokenHash = i.AccessTokenHash
|
t.AccessTokenHash = i.AccessTokenHash
|
||||||
t.CodeHash = i.CodeHash
|
t.CodeHash = i.CodeHash
|
||||||
|
t.UserinfoProfile = i.UnmarshalUserinfoProfile()
|
||||||
|
t.UserinfoEmail = i.UnmarshalUserinfoEmail()
|
||||||
|
t.UserinfoPhone = i.UnmarshalUserinfoPhone()
|
||||||
|
t.UserinfoAddress = i.UnmarshalUserinfoAddress()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j *jsonToken) UnmarshalUserinfoProfile() UserinfoProfile {
|
||||||
|
locale, _ := language.Parse(j.Locale)
|
||||||
|
return UserinfoProfile{
|
||||||
|
Name: j.Name,
|
||||||
|
GivenName: j.GivenName,
|
||||||
|
FamilyName: j.FamilyName,
|
||||||
|
MiddleName: j.MiddleName,
|
||||||
|
Nickname: j.Nickname,
|
||||||
|
Profile: j.Profile,
|
||||||
|
Picture: j.Picture,
|
||||||
|
Website: j.Website,
|
||||||
|
Gender: Gender(j.Gender),
|
||||||
|
Birthdate: j.Birthdate,
|
||||||
|
Zoneinfo: j.Zoneinfo,
|
||||||
|
Locale: locale,
|
||||||
|
UpdatedAt: time.Unix(j.UpdatedAt, 0).UTC(),
|
||||||
|
PreferredUsername: j.PreferredUsername,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jsonToken) UnmarshalUserinfoEmail() UserinfoEmail {
|
||||||
|
return UserinfoEmail{
|
||||||
|
Email: j.Email,
|
||||||
|
EmailVerified: j.EmailVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jsonToken) UnmarshalUserinfoPhone() UserinfoPhone {
|
||||||
|
return UserinfoPhone{
|
||||||
|
PhoneNumber: j.Phone,
|
||||||
|
PhoneNumberVerified: j.PhoneVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jsonToken) UnmarshalUserinfoAddress() *UserinfoAddress {
|
||||||
|
if j.jsonUserinfoAddress == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &UserinfoAddress{
|
||||||
|
Country: j.jsonUserinfoAddress.Country,
|
||||||
|
Formatted: j.jsonUserinfoAddress.Formatted,
|
||||||
|
Locality: j.jsonUserinfoAddress.Locality,
|
||||||
|
PostalCode: j.jsonUserinfoAddress.PostalCode,
|
||||||
|
Region: j.jsonUserinfoAddress.Region,
|
||||||
|
StreetAddress: j.jsonUserinfoAddress.StreetAddress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ClaimHash(claim string, sigAlgorithm jose.SignatureAlgorithm) (string, error) {
|
func ClaimHash(claim string, sigAlgorithm jose.SignatureAlgorithm) (string, error) {
|
||||||
hash, err := utils.GetHashAlgorithm(sigAlgorithm)
|
hash, err := utils.GetHashAlgorithm(sigAlgorithm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,6 +7,35 @@ import (
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type userinfo interface {
|
||||||
|
GetUserinfoProfile() UserinfoProfile
|
||||||
|
GetUserinfoEmail() UserinfoEmail
|
||||||
|
GetUserinfoPhone() UserinfoPhone
|
||||||
|
GetAddress() *UserinfoAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserinfoProfileI interface {
|
||||||
|
GetName() string
|
||||||
|
GetGivenName() string
|
||||||
|
GetFamilyName() string
|
||||||
|
GetMiddleName() string
|
||||||
|
GetNickname() string
|
||||||
|
GetProfile() string
|
||||||
|
GetPicture() string
|
||||||
|
GetWebsite() string
|
||||||
|
GetGender() Gender
|
||||||
|
GetBirthdate() string
|
||||||
|
GetZoneinfo() string
|
||||||
|
GetLocale() language.Tag
|
||||||
|
GetUpdatedAt() time.Time
|
||||||
|
GetPreferredUsername() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserinfoEmailI interface {
|
||||||
|
GetEmail() string
|
||||||
|
IsEmailVerified() bool
|
||||||
|
}
|
||||||
|
|
||||||
type Userinfo struct {
|
type Userinfo struct {
|
||||||
Subject string
|
Subject string
|
||||||
Address *UserinfoAddress
|
Address *UserinfoAddress
|
||||||
|
@ -17,10 +46,32 @@ type Userinfo struct {
|
||||||
claims map[string]interface{}
|
claims map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserinfoPhoneI interface {
|
||||||
|
GetPhoneNumber() string
|
||||||
|
IsPhoneNumberVerified() bool
|
||||||
|
}
|
||||||
type UserinfoPhone struct {
|
type UserinfoPhone struct {
|
||||||
PhoneNumber string
|
PhoneNumber string
|
||||||
PhoneNumberVerified bool
|
PhoneNumberVerified bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u UserinfoPhone) GetPhoneNumber() string {
|
||||||
|
return u.PhoneNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UserinfoPhone) IsPhoneNumberVerified() bool {
|
||||||
|
return u.PhoneNumberVerified
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserinfoAddressI interface {
|
||||||
|
GetCountry() string
|
||||||
|
GetFormatted() string
|
||||||
|
GetLocality() string
|
||||||
|
GetPostalCode() string
|
||||||
|
GetRegion() string
|
||||||
|
GetStreetAddress() string
|
||||||
|
}
|
||||||
|
|
||||||
type UserinfoProfile struct {
|
type UserinfoProfile struct {
|
||||||
Name string
|
Name string
|
||||||
GivenName string
|
GivenName string
|
||||||
|
@ -38,6 +89,49 @@ type UserinfoProfile struct {
|
||||||
PreferredUsername string
|
PreferredUsername string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u UserinfoProfile) GetName() string {
|
||||||
|
return u.Name
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetGivenName() string {
|
||||||
|
return u.GivenName
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetFamilyName() string {
|
||||||
|
return u.FamilyName
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetMiddleName() string {
|
||||||
|
return u.MiddleName
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetNickname() string {
|
||||||
|
return u.Nickname
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetProfile() string {
|
||||||
|
return u.Profile
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetPicture() string {
|
||||||
|
return u.Picture
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetWebsite() string {
|
||||||
|
return u.Website
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetGender() Gender {
|
||||||
|
return u.Gender
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetBirthdate() string {
|
||||||
|
return u.Birthdate
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetZoneinfo() string {
|
||||||
|
return u.Zoneinfo
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetLocale() language.Tag {
|
||||||
|
return u.Locale
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetUpdatedAt() time.Time {
|
||||||
|
return u.UpdatedAt
|
||||||
|
}
|
||||||
|
func (u UserinfoProfile) GetPreferredUsername() string {
|
||||||
|
return u.PreferredUsername
|
||||||
|
}
|
||||||
|
|
||||||
type Gender string
|
type Gender string
|
||||||
|
|
||||||
type UserinfoAddress struct {
|
type UserinfoAddress struct {
|
||||||
|
@ -49,67 +143,209 @@ type UserinfoAddress struct {
|
||||||
Country string
|
Country string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u UserinfoAddress) GetCountry() string {
|
||||||
|
return u.Country
|
||||||
|
}
|
||||||
|
func (u UserinfoAddress) GetFormatted() string {
|
||||||
|
return u.Formatted
|
||||||
|
}
|
||||||
|
func (u UserinfoAddress) GetLocality() string {
|
||||||
|
return u.Locality
|
||||||
|
}
|
||||||
|
func (u UserinfoAddress) GetPostalCode() string {
|
||||||
|
return u.PostalCode
|
||||||
|
}
|
||||||
|
func (u UserinfoAddress) GetRegion() string {
|
||||||
|
return u.Region
|
||||||
|
}
|
||||||
|
func (u UserinfoAddress) GetStreetAddress() string {
|
||||||
|
return u.StreetAddress
|
||||||
|
}
|
||||||
|
|
||||||
type UserinfoEmail struct {
|
type UserinfoEmail struct {
|
||||||
Email string
|
Email string
|
||||||
EmailVerified bool
|
EmailVerified bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalUserinfoProfile(i UserinfoProfile, claims map[string]interface{}) {
|
func (u UserinfoEmail) GetEmail() string {
|
||||||
claims["name"] = i.Name
|
return u.Email
|
||||||
claims["given_name"] = i.GivenName
|
|
||||||
claims["family_name"] = i.FamilyName
|
|
||||||
claims["middle_name"] = i.MiddleName
|
|
||||||
claims["nickname"] = i.Nickname
|
|
||||||
claims["profile"] = i.Profile
|
|
||||||
claims["picture"] = i.Picture
|
|
||||||
claims["website"] = i.Website
|
|
||||||
claims["gender"] = i.Gender
|
|
||||||
claims["birthdate"] = i.Birthdate
|
|
||||||
claims["Zoneinfo"] = i.Zoneinfo
|
|
||||||
claims["locale"] = i.Locale.String()
|
|
||||||
claims["updated_at"] = i.UpdatedAt.UTC().Unix()
|
|
||||||
claims["preferred_username"] = i.PreferredUsername
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalUserinfoEmail(i UserinfoEmail, claims map[string]interface{}) {
|
func (u UserinfoEmail) IsEmailVerified() bool {
|
||||||
if i.Email != "" {
|
return u.EmailVerified
|
||||||
claims["email"] = i.Email
|
|
||||||
}
|
|
||||||
if i.EmailVerified {
|
|
||||||
claims["email_verified"] = i.EmailVerified
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalUserinfoAddress(i *UserinfoAddress, claims map[string]interface{}) {
|
type jsonUserinfo struct {
|
||||||
|
jsonUserinfoProfile
|
||||||
|
jsonUserinfoEmail
|
||||||
|
jsonUserinfoPhone
|
||||||
|
jsonUserinfoAddress *jsonUserinfoAddress `json:"address,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonUserinfoProfile struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
GivenName string `json:"given_name,omitempty"`
|
||||||
|
FamilyName string `json:"family_name,omitempty"`
|
||||||
|
MiddleName string `json:"middle_name,omitempty"`
|
||||||
|
Nickname string `json:"nickname,omitempty"`
|
||||||
|
Profile string `json:"profile,omitempty"`
|
||||||
|
Picture string `json:"picture,omitempty"`
|
||||||
|
Website string `json:"website,omitempty"`
|
||||||
|
Gender string `json:"gender,omitempty"`
|
||||||
|
Birthdate string `json:"birthdate,omitempty"`
|
||||||
|
Zoneinfo string `json:"zoneinfo,omitempty"`
|
||||||
|
Locale string `json:"locale,omitempty"`
|
||||||
|
UpdatedAt int64 `json:"updated_at,omitempty"`
|
||||||
|
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonUserinfoEmail struct {
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
EmailVerified bool `json:"email_verified,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonUserinfoPhone struct {
|
||||||
|
Phone string `json:"phone_number,omitempty"`
|
||||||
|
PhoneVerified bool `json:"phone_number_verified,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonUserinfoAddress struct {
|
||||||
|
Formatted string `json:"formatted,omitempty"`
|
||||||
|
StreetAddress string `json:"street_address,omitempty"`
|
||||||
|
Locality string `json:"locality,omitempty"`
|
||||||
|
Region string `json:"region,omitempty"`
|
||||||
|
PostalCode string `json:"postal_code,omitempty"`
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (t *Userinfo) setUserinfoProfile(j *jsonToken) {
|
||||||
|
// j.Name = t.Name
|
||||||
|
// j.GivenName = t.GivenName
|
||||||
|
// j.FamilyName = t.FamilyName
|
||||||
|
// j.MiddleName = t.MiddleName
|
||||||
|
// j.Nickname = t.Nickname
|
||||||
|
// j.Profile = t.Profile
|
||||||
|
// j.Picture = t.Picture
|
||||||
|
// j.Website = t.Website
|
||||||
|
// j.Gender = string(t.Gender)
|
||||||
|
// j.Birthdate = t.Birthdate
|
||||||
|
// j.Zoneinfo = t.Zoneinfo
|
||||||
|
// j.Locale = t.Locale.String()
|
||||||
|
// j.UpdatedAt = timeToJSON(t.UpdatedAt)
|
||||||
|
// j.PreferredUsername = t.PreferredUsername
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func marshalUserinfoProfile(i UserinfoProfile, claims map[string]interface{}) {
|
||||||
|
// claims["name"] = i.Name
|
||||||
|
// claims["given_name"] = i.GivenName
|
||||||
|
// claims["family_name"] = i.FamilyName
|
||||||
|
// claims["middle_name"] = i.MiddleName
|
||||||
|
// claims["nickname"] = i.Nickname
|
||||||
|
// claims["profile"] = i.Profile
|
||||||
|
// claims["picture"] = i.Picture
|
||||||
|
// claims["website"] = i.Website
|
||||||
|
// claims["gender"] = i.Gender
|
||||||
|
// claims["birthdate"] = i.Birthdate
|
||||||
|
// claims["Zoneinfo"] = i.Zoneinfo
|
||||||
|
// claims["locale"] = i.Locale.String()
|
||||||
|
// claims["updated_at"] = i.UpdatedAt.UTC().Unix()
|
||||||
|
// claims["preferred_username"] = i.PreferredUsername
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func marshalUserinfoEmail(i UserinfoEmail, claims map[string]interface{}) {
|
||||||
|
// if i.Email != "" {
|
||||||
|
// claims["email"] = i.Email
|
||||||
|
// }
|
||||||
|
// if i.EmailVerified {
|
||||||
|
// claims["email_verified"] = i.EmailVerified
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func marshalUserinfoAddress(i *UserinfoAddress, claims map[string]interface{}) {
|
||||||
|
// if i == nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// address := make(map[string]interface{})
|
||||||
|
// if i.Formatted != "" {
|
||||||
|
// address["formatted"] = i.Formatted
|
||||||
|
// }
|
||||||
|
// if i.StreetAddress != "" {
|
||||||
|
// address["street_address"] = i.StreetAddress
|
||||||
|
// }
|
||||||
|
// claims["address"] = address
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func marshalUserinfoPhone(i UserinfoPhone, claims map[string]interface{}) {
|
||||||
|
// claims["phone_number"] = i.PhoneNumber
|
||||||
|
// claims["phone_number_verified"] = i.PhoneNumberVerified
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (i *Userinfo) MarshalJSON() ([]byte, error) {
|
||||||
|
j := new(jsonUserinfo)
|
||||||
|
j.setUserinfo(i)
|
||||||
|
return json.Marshal(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Userinfo) GetAddress() *UserinfoAddress {
|
||||||
|
return i.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Userinfo) GetUserinfoProfile() UserinfoProfile {
|
||||||
|
return i.UserinfoProfile
|
||||||
|
}
|
||||||
|
func (i *Userinfo) GetUserinfoEmail() UserinfoEmail {
|
||||||
|
return i.UserinfoEmail
|
||||||
|
}
|
||||||
|
func (i *Userinfo) GetUserinfoPhone() UserinfoPhone {
|
||||||
|
return i.UserinfoPhone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jsonUserinfo) setUserinfo(i userinfo) {
|
||||||
|
j.setUserinfoProfile(i.GetUserinfoProfile())
|
||||||
|
j.setUserinfoEmail(i.GetUserinfoEmail())
|
||||||
|
j.setUserinfoPhone(i.GetUserinfoPhone())
|
||||||
|
j.setUserinfoAddress(i.GetAddress())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jsonUserinfo) setUserinfoProfile(i UserinfoProfile) {
|
||||||
|
j.Name = i.Name
|
||||||
|
j.GivenName = i.GivenName
|
||||||
|
j.FamilyName = i.FamilyName
|
||||||
|
j.MiddleName = i.MiddleName
|
||||||
|
j.Nickname = i.Nickname
|
||||||
|
j.Profile = i.Profile
|
||||||
|
j.Picture = i.Picture
|
||||||
|
j.Website = i.Website
|
||||||
|
j.Gender = string(i.Gender)
|
||||||
|
j.Birthdate = i.Birthdate
|
||||||
|
j.Zoneinfo = i.Zoneinfo
|
||||||
|
if i.Locale != language.Und {
|
||||||
|
j.Locale = i.Locale.String()
|
||||||
|
}
|
||||||
|
j.UpdatedAt = timeToJSON(i.UpdatedAt)
|
||||||
|
j.PreferredUsername = i.PreferredUsername
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jsonUserinfo) setUserinfoEmail(i UserinfoEmail) {
|
||||||
|
j.Email = i.Email
|
||||||
|
j.EmailVerified = i.EmailVerified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jsonUserinfo) setUserinfoPhone(i UserinfoPhone) {
|
||||||
|
j.Phone = i.PhoneNumber
|
||||||
|
j.PhoneVerified = i.PhoneNumberVerified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *jsonUserinfo) setUserinfoAddress(i *UserinfoAddress) {
|
||||||
if i == nil {
|
if i == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
address := make(map[string]interface{})
|
j.jsonUserinfoAddress.Country = i.Country
|
||||||
if i.Formatted != "" {
|
j.jsonUserinfoAddress.Formatted = i.Formatted
|
||||||
address["formatted"] = i.Formatted
|
j.jsonUserinfoAddress.Locality = i.Locality
|
||||||
}
|
j.jsonUserinfoAddress.PostalCode = i.PostalCode
|
||||||
if i.StreetAddress != "" {
|
j.jsonUserinfoAddress.Region = i.Region
|
||||||
address["street_address"] = i.StreetAddress
|
j.jsonUserinfoAddress.StreetAddress = i.StreetAddress
|
||||||
}
|
|
||||||
claims["address"] = address
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalUserinfoPhone(i UserinfoPhone, claims map[string]interface{}) {
|
|
||||||
claims["phone_number"] = i.PhoneNumber
|
|
||||||
claims["phone_number_verified"] = i.PhoneNumberVerified
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Userinfo) MarshalJSON() ([]byte, error) {
|
|
||||||
claims := i.claims
|
|
||||||
if claims == nil {
|
|
||||||
claims = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
claims["sub"] = i.Subject
|
|
||||||
marshalUserinfoAddress(i.Address, claims)
|
|
||||||
marshalUserinfoEmail(i.UserinfoEmail, claims)
|
|
||||||
marshalUserinfoPhone(i.UserinfoPhone, claims)
|
|
||||||
marshalUserinfoProfile(i.UserinfoProfile, claims)
|
|
||||||
return json.Marshal(claims)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Userinfo) UnmmarshalJSON(data []byte) error {
|
func (i *Userinfo) UnmmarshalJSON(data []byte) error {
|
||||||
|
@ -118,3 +354,7 @@ func (i *Userinfo) UnmmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
return json.Unmarshal(data, i.claims)
|
return json.Unmarshal(data, i.claims)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserInfoRequest struct {
|
||||||
|
AccessToken string `schema:"access_token"`
|
||||||
|
}
|
||||||
|
|
|
@ -154,18 +154,33 @@ func (mr *MockStorageMockRecorder) GetSigningKey(arg0, arg1, arg2, arg3 interfac
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserinfoFromScopes mocks base method
|
// GetUserinfoFromScopes mocks base method
|
||||||
func (m *MockStorage) GetUserinfoFromScopes(arg0 context.Context, arg1 []string) (*oidc.Userinfo, error) {
|
func (m *MockStorage) GetUserinfoFromScopes(arg0 context.Context, arg1 string, arg2 []string) (*oidc.Userinfo, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetUserinfoFromScopes", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetUserinfoFromScopes", arg0, arg1, arg2)
|
||||||
ret0, _ := ret[0].(*oidc.Userinfo)
|
ret0, _ := ret[0].(*oidc.Userinfo)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserinfoFromScopes indicates an expected call of GetUserinfoFromScopes
|
// GetUserinfoFromScopes indicates an expected call of GetUserinfoFromScopes
|
||||||
func (mr *MockStorageMockRecorder) GetUserinfoFromScopes(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockStorageMockRecorder) GetUserinfoFromScopes(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserinfoFromScopes", reflect.TypeOf((*MockStorage)(nil).GetUserinfoFromScopes), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserinfoFromScopes", reflect.TypeOf((*MockStorage)(nil).GetUserinfoFromScopes), arg0, arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserinfoFromToken mocks base method
|
||||||
|
func (m *MockStorage) GetUserinfoFromToken(arg0 context.Context, arg1 string) (*oidc.Userinfo, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetUserinfoFromToken", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(*oidc.Userinfo)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserinfoFromToken indicates an expected call of GetUserinfoFromToken
|
||||||
|
func (mr *MockStorageMockRecorder) GetUserinfoFromToken(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserinfoFromToken", reflect.TypeOf((*MockStorage)(nil).GetUserinfoFromToken), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health mocks base method
|
// Health mocks base method
|
||||||
|
|
|
@ -26,7 +26,8 @@ type AuthStorage interface {
|
||||||
type OPStorage interface {
|
type OPStorage interface {
|
||||||
GetClientByClientID(context.Context, string) (Client, error)
|
GetClientByClientID(context.Context, string) (Client, error)
|
||||||
AuthorizeClientIDSecret(context.Context, string, string) error
|
AuthorizeClientIDSecret(context.Context, string, string) error
|
||||||
GetUserinfoFromScopes(context.Context, []string) (*oidc.Userinfo, error)
|
GetUserinfoFromScopes(context.Context, string, []string) (*oidc.Userinfo, error)
|
||||||
|
GetUserinfoFromToken(context.Context, string) (*oidc.Userinfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
|
|
|
@ -24,7 +24,7 @@ func CreateTokenResponse(ctx context.Context, authReq AuthRequest, client Client
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
idToken, err := CreateIDToken(creator.Issuer(), authReq, client.IDTokenLifetime(), accessToken, code, creator.Signer())
|
idToken, err := CreateIDToken(ctx, creator.Issuer(), authReq, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -71,9 +71,13 @@ func CreateJWT(issuer string, authReq AuthRequest, exp time.Time, id string, sig
|
||||||
return signer.SignAccessToken(claims)
|
return signer.SignAccessToken(claims)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateIDToken(issuer string, authReq AuthRequest, validity time.Duration, accessToken, code string, signer Signer) (string, error) {
|
func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
exp := time.Now().UTC().Add(validity)
|
exp := time.Now().UTC().Add(validity)
|
||||||
|
userinfo, err := storage.GetUserinfoFromScopes(ctx, authReq.GetSubject(), authReq.GetScopes())
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
claims := &oidc.IDTokenClaims{
|
claims := &oidc.IDTokenClaims{
|
||||||
Issuer: issuer,
|
Issuer: issuer,
|
||||||
Subject: authReq.GetSubject(),
|
Subject: authReq.GetSubject(),
|
||||||
|
@ -85,6 +89,10 @@ func CreateIDToken(issuer string, authReq AuthRequest, validity time.Duration, a
|
||||||
AuthenticationContextClassReference: authReq.GetACR(),
|
AuthenticationContextClassReference: authReq.GetACR(),
|
||||||
AuthenticationMethodsReferences: authReq.GetAMR(),
|
AuthenticationMethodsReferences: authReq.GetAMR(),
|
||||||
AuthorizedParty: authReq.GetClientID(),
|
AuthorizedParty: authReq.GetClientID(),
|
||||||
|
UserinfoProfile: userinfo.UserinfoProfile,
|
||||||
|
UserinfoEmail: userinfo.UserinfoEmail,
|
||||||
|
UserinfoPhone: userinfo.UserinfoPhone,
|
||||||
|
UserinfoAddress: userinfo.Address,
|
||||||
}
|
}
|
||||||
if accessToken != "" {
|
if accessToken != "" {
|
||||||
claims.AccessTokenHash, err = oidc.ClaimHash(accessToken, signer.SignatureAlgorithm())
|
claims.AccessTokenHash, err = oidc.ClaimHash(accessToken, signer.SignatureAlgorithm())
|
||||||
|
|
|
@ -1,21 +1,33 @@
|
||||||
package op
|
package op
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
"github.com/caos/oidc/pkg/utils"
|
"github.com/caos/oidc/pkg/utils"
|
||||||
|
"github.com/gorilla/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserinfoProvider interface {
|
type UserinfoProvider interface {
|
||||||
|
Decoder() *schema.Decoder
|
||||||
|
Crypto() Crypto
|
||||||
Storage() Storage
|
Storage() Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoProvider) {
|
func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoProvider) {
|
||||||
scopes, err := ScopesFromAccessToken(w, r)
|
accessToken, err := getAccessToken(r, userinfoProvider.Decoder())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
http.Error(w, "access token missing", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err := userinfoProvider.Storage().GetUserinfoFromScopes(r.Context(), scopes)
|
tokenID, err := userinfoProvider.Crypto().Decrypt(accessToken)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "access token missing", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info, err := userinfoProvider.Storage().GetUserinfoFromToken(r.Context(), tokenID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.MarshalJSON(w, err)
|
utils.MarshalJSON(w, err)
|
||||||
return
|
return
|
||||||
|
@ -23,6 +35,23 @@ func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoP
|
||||||
utils.MarshalJSON(w, info)
|
utils.MarshalJSON(w, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ScopesFromAccessToken(w http.ResponseWriter, r *http.Request) ([]string, error) {
|
func getAccessToken(r *http.Request, decoder *schema.Decoder) (string, error) {
|
||||||
return []string{}, nil
|
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
|
||||||
|
}
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("unable to parse request")
|
||||||
|
}
|
||||||
|
req := new(oidc.UserInfoRequest)
|
||||||
|
err = decoder.Decode(req, r.Form)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("unable to parse request")
|
||||||
|
}
|
||||||
|
return req.AccessToken, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue