diff --git a/example/internal/mock/storage.go b/example/internal/mock/storage.go index 23a3394..c5797db 100644 --- a/example/internal/mock/storage.go +++ b/example/internal/mock/storage.go @@ -191,7 +191,10 @@ func (s *AuthStorage) AuthorizeClientIDSecret(_ context.Context, id string, _ st 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{ Subject: a.GetSubject(), Address: &oidc.UserinfoAddress{ diff --git a/pkg/oidc/token.go b/pkg/oidc/token.go index cde0885..16edcb3 100644 --- a/pkg/oidc/token.go +++ b/pkg/oidc/token.go @@ -7,6 +7,7 @@ import ( "github.com/caos/oidc/pkg/utils" "golang.org/x/oauth2" + "golang.org/x/text/language" "gopkg.in/square/go-jose.v2" ) @@ -53,6 +54,10 @@ type IDTokenClaims struct { AuthenticationContextClassReference string AuthenticationMethodsReferences []string ClientID string + UserinfoProfile + UserinfoEmail + UserinfoPhone + UserinfoAddress *UserinfoAddress Signature jose.SignatureAlgorithm //TODO: ??? } @@ -65,7 +70,6 @@ type jsonToken struct { NotBefore int64 `json:"nbf,omitempty"` IssuedAt int64 `json:"iat,omitempty"` JWTID string `json:"jti,omitempty"` - UpdatedAt int64 `json:"updated_at,omitempty"` AuthorizedParty string `json:"azp,omitempty"` Nonce string `json:"nonce,omitempty"` AuthTime int64 `json:"auth_time,omitempty"` @@ -79,6 +83,7 @@ type jsonToken struct { ClientID string `json:"client_id,omitempty"` AuthorizedActor interface{} `json:"may_act,omitempty"` //TODO: impl AccessTokenUseNumber int `json:"at_use_nbr,omitempty"` + jsonUserinfo } func (t *AccessTokenClaims) MarshalJSON() ([]byte, error) { @@ -142,7 +147,6 @@ func (t *IDTokenClaims) MarshalJSON() ([]byte, error) { NotBefore: timeToJSON(t.NotBefore), IssuedAt: timeToJSON(t.IssuedAt), JWTID: t.JWTID, - UpdatedAt: timeToJSON(t.UpdatedAt), AuthorizedParty: t.AuthorizedParty, Nonce: t.Nonce, AuthTime: timeToJSON(t.AuthTime), @@ -152,8 +156,71 @@ func (t *IDTokenClaims) MarshalJSON() ([]byte, error) { AuthenticationMethodsReferences: t.AuthenticationMethodsReferences, ClientID: t.ClientID, } + j.setUserinfo(t) 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 { var i jsonToken @@ -176,9 +243,61 @@ func (t *IDTokenClaims) UnmarshalJSON(b []byte) error { t.AuthorizedParty = i.AuthorizedParty t.AccessTokenHash = i.AccessTokenHash t.CodeHash = i.CodeHash + t.UserinfoProfile = i.UnmarshalUserinfoProfile() + t.UserinfoEmail = i.UnmarshalUserinfoEmail() + t.UserinfoPhone = i.UnmarshalUserinfoPhone() + t.UserinfoAddress = i.UnmarshalUserinfoAddress() 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) { hash, err := utils.GetHashAlgorithm(sigAlgorithm) if err != nil { diff --git a/pkg/oidc/userinfo.go b/pkg/oidc/userinfo.go index 5e99d09..1e7243d 100644 --- a/pkg/oidc/userinfo.go +++ b/pkg/oidc/userinfo.go @@ -7,6 +7,35 @@ import ( "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 { Subject string Address *UserinfoAddress @@ -17,10 +46,32 @@ type Userinfo struct { claims map[string]interface{} } +type UserinfoPhoneI interface { + GetPhoneNumber() string + IsPhoneNumberVerified() bool +} type UserinfoPhone struct { PhoneNumber string 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 { Name string GivenName string @@ -38,6 +89,49 @@ type UserinfoProfile struct { 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 UserinfoAddress struct { @@ -49,67 +143,209 @@ type UserinfoAddress struct { 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 { Email string EmailVerified bool } -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 (u UserinfoEmail) GetEmail() string { + return u.Email } -func marshalUserinfoEmail(i UserinfoEmail, claims map[string]interface{}) { - if i.Email != "" { - claims["email"] = i.Email - } - if i.EmailVerified { - claims["email_verified"] = i.EmailVerified - } +func (u UserinfoEmail) IsEmailVerified() bool { + return u.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 { 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) { - 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) + j.jsonUserinfoAddress.Country = i.Country + j.jsonUserinfoAddress.Formatted = i.Formatted + j.jsonUserinfoAddress.Locality = i.Locality + j.jsonUserinfoAddress.PostalCode = i.PostalCode + j.jsonUserinfoAddress.Region = i.Region + j.jsonUserinfoAddress.StreetAddress = i.StreetAddress } func (i *Userinfo) UnmmarshalJSON(data []byte) error { @@ -118,3 +354,7 @@ func (i *Userinfo) UnmmarshalJSON(data []byte) error { } return json.Unmarshal(data, i.claims) } + +type UserInfoRequest struct { + AccessToken string `schema:"access_token"` +} diff --git a/pkg/op/mock/storage.mock.go b/pkg/op/mock/storage.mock.go index ac06842..04316c3 100644 --- a/pkg/op/mock/storage.mock.go +++ b/pkg/op/mock/storage.mock.go @@ -154,18 +154,33 @@ func (mr *MockStorageMockRecorder) GetSigningKey(arg0, arg1, arg2, arg3 interfac } // 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() - ret := m.ctrl.Call(m, "GetUserinfoFromScopes", arg0, arg1) + ret := m.ctrl.Call(m, "GetUserinfoFromScopes", arg0, arg1, arg2) ret0, _ := ret[0].(*oidc.Userinfo) ret1, _ := ret[1].(error) return ret0, ret1 } // 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() - 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 diff --git a/pkg/op/storage.go b/pkg/op/storage.go index b770360..f213618 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -26,7 +26,8 @@ type AuthStorage interface { type OPStorage interface { GetClientByClientID(context.Context, string) (Client, 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 { diff --git a/pkg/op/token.go b/pkg/op/token.go index ff74d69..ce54939 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -24,7 +24,7 @@ func CreateTokenResponse(ctx context.Context, authReq AuthRequest, client Client 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 { return nil, err } @@ -71,9 +71,13 @@ func CreateJWT(issuer string, authReq AuthRequest, exp time.Time, id string, sig 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 exp := time.Now().UTC().Add(validity) + userinfo, err := storage.GetUserinfoFromScopes(ctx, authReq.GetSubject(), authReq.GetScopes()) + if err != nil { + + } claims := &oidc.IDTokenClaims{ Issuer: issuer, Subject: authReq.GetSubject(), @@ -85,6 +89,10 @@ func CreateIDToken(issuer string, authReq AuthRequest, validity time.Duration, a AuthenticationContextClassReference: authReq.GetACR(), AuthenticationMethodsReferences: authReq.GetAMR(), AuthorizedParty: authReq.GetClientID(), + UserinfoProfile: userinfo.UserinfoProfile, + UserinfoEmail: userinfo.UserinfoEmail, + UserinfoPhone: userinfo.UserinfoPhone, + UserinfoAddress: userinfo.Address, } if accessToken != "" { claims.AccessTokenHash, err = oidc.ClaimHash(accessToken, signer.SignatureAlgorithm()) diff --git a/pkg/op/userinfo.go b/pkg/op/userinfo.go index ac47e68..69746c7 100644 --- a/pkg/op/userinfo.go +++ b/pkg/op/userinfo.go @@ -1,21 +1,33 @@ package op import ( + "errors" "net/http" + "strings" + "github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/utils" + "github.com/gorilla/schema" ) type UserinfoProvider interface { + Decoder() *schema.Decoder + Crypto() Crypto Storage() Storage } func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoProvider) { - scopes, err := ScopesFromAccessToken(w, r) + accessToken, err := getAccessToken(r, userinfoProvider.Decoder()) if err != nil { + http.Error(w, "access token missing", http.StatusUnauthorized) 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 { utils.MarshalJSON(w, err) return @@ -23,6 +35,23 @@ func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoP utils.MarshalJSON(w, info) } -func ScopesFromAccessToken(w http.ResponseWriter, r *http.Request) ([]string, error) { - return []string{}, nil +func getAccessToken(r *http.Request, decoder *schema.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 + } + 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 }