fix: use of generic oauth provider (#5345)

Adds a id_attribute to the GenericOAuthProvider, which is used to map the external User. Further mapping can be done in actions by using the `rawInfo` of the new `ctx.v1.providerInfo` field.
This commit is contained in:
Livio Spring
2023-03-03 11:38:49 +01:00
committed by GitHub
parent cfe00ef0d0
commit 2efa305e10
28 changed files with 456 additions and 98 deletions

View File

@@ -2,6 +2,8 @@ package oauth
import (
"encoding/json"
"fmt"
"strconv"
"golang.org/x/text/language"
@@ -11,92 +13,97 @@ import (
var _ idp.User = (*UserMapper)(nil)
// UserMapper is an implementation of [idp.User].
// It can be used in ZITADEL actions to map the raw `info`
// It can be used in ZITADEL actions to map the `RawInfo`
type UserMapper struct {
ID string
FirstName string
LastName string
DisplayName string
NickName string
PreferredUsername string
Email string
EmailVerified bool
Phone string
PhoneVerified bool
PreferredLanguage string
AvatarURL string
Profile string
info map[string]interface{}
idAttribute string
RawInfo map[string]interface{}
}
func NewUserMapper(idAttribute string) *UserMapper {
return &UserMapper{
idAttribute: idAttribute,
RawInfo: make(map[string]interface{}),
}
}
func (u *UserMapper) UnmarshalJSON(data []byte) error {
if u.info == nil {
u.info = make(map[string]interface{})
}
return json.Unmarshal(data, &u.info)
return json.Unmarshal(data, &u.RawInfo)
}
// GetID is an implementation of the [idp.User] interface.
func (u *UserMapper) GetID() string {
return u.ID
id, ok := u.RawInfo[u.idAttribute]
if !ok {
return ""
}
switch i := id.(type) {
case string:
return i
case int:
return strconv.Itoa(i)
case float64:
return strconv.FormatFloat(i, 'f', -1, 64)
default:
return fmt.Sprint(i)
}
}
// GetFirstName is an implementation of the [idp.User] interface.
func (u *UserMapper) GetFirstName() string {
return u.FirstName
return ""
}
// GetLastName is an implementation of the [idp.User] interface.
func (u *UserMapper) GetLastName() string {
return u.LastName
return ""
}
// GetDisplayName is an implementation of the [idp.User] interface.
func (u *UserMapper) GetDisplayName() string {
return u.DisplayName
return ""
}
// GetNickname is an implementation of the [idp.User] interface.
func (u *UserMapper) GetNickname() string {
return u.NickName
return ""
}
// GetPreferredUsername is an implementation of the [idp.User] interface.
func (u *UserMapper) GetPreferredUsername() string {
return u.PreferredUsername
return ""
}
// GetEmail is an implementation of the [idp.User] interface.
func (u *UserMapper) GetEmail() string {
return u.Email
return ""
}
// IsEmailVerified is an implementation of the [idp.User] interface.
func (u *UserMapper) IsEmailVerified() bool {
return u.EmailVerified
return false
}
// GetPhone is an implementation of the [idp.User] interface.
func (u *UserMapper) GetPhone() string {
return u.Phone
return ""
}
// IsPhoneVerified is an implementation of the [idp.User] interface.
func (u *UserMapper) IsPhoneVerified() bool {
return u.PhoneVerified
return false
}
// GetPreferredLanguage is an implementation of the [idp.User] interface.
func (u *UserMapper) GetPreferredLanguage() language.Tag {
return language.Make(u.PreferredLanguage)
return language.Und
}
// GetAvatarURL is an implementation of the [idp.User] interface.
func (u *UserMapper) GetAvatarURL() string {
return u.AvatarURL
return ""
}
// GetProfile is an implementation of the [idp.User] interface.
func (u *UserMapper) GetProfile() string {
return u.Profile
return ""
}

View File

@@ -93,9 +93,7 @@ func TestProvider_FetchUser(t *testing.T) {
Reply(http.StatusInternalServerError)
},
userMapper: func() idp.User {
return &UserMapper{
ID: "userID",
}
return NewUserMapper("userID")
},
authURL: "https://oauth2.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=user&state=testState",
tokens: &oidc.Tokens{
@@ -135,7 +133,7 @@ func TestProvider_FetchUser(t *testing.T) {
})
},
userMapper: func() idp.User {
return &UserMapper{}
return NewUserMapper("userID")
},
authURL: "https://issuer.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=user&state=testState",
tokens: &oidc.Tokens{
@@ -147,12 +145,13 @@ func TestProvider_FetchUser(t *testing.T) {
},
want: want{
user: &UserMapper{
info: map[string]interface{}{
idAttribute: "userID",
RawInfo: map[string]interface{}{
"userID": "id",
"custom": "claim",
},
},
id: "",
id: "id",
firstName: "",
lastName: "",
displayName: "",
@@ -202,7 +201,7 @@ func TestProvider_FetchUser(t *testing.T) {
})
},
userMapper: func() idp.User {
return &UserMapper{}
return NewUserMapper("userID")
},
authURL: "https://issuer.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=user&state=testState",
tokens: nil,
@@ -210,12 +209,13 @@ func TestProvider_FetchUser(t *testing.T) {
},
want: want{
user: &UserMapper{
info: map[string]interface{}{
idAttribute: "userID",
RawInfo: map[string]interface{}{
"userID": "id",
"custom": "claim",
},
},
id: "",
id: "id",
firstName: "",
lastName: "",
displayName: "",