From 02860da8b6e1f88e7d32abc2c077ca609692849a Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Mon, 19 Oct 2020 22:18:25 -0400 Subject: [PATCH] use claim mappings when retrieving user identity Signed-off-by: Rui Yang --- connector/oauth/oauth.go | 88 ++++++++++++++++++++++------------- connector/oauth/oauth_test.go | 19 ++++---- docs/connectors/oauth.md | 33 +++++++++---- 3 files changed, 90 insertions(+), 50 deletions(-) diff --git a/connector/oauth/oauth.go b/connector/oauth/oauth.go index d14319a5..ad83dad2 100644 --- a/connector/oauth/oauth.go +++ b/connector/oauth/oauth.go @@ -28,10 +28,12 @@ type oauthConnector struct { authorizationURL string userInfoURL string scopes []string - groupsKey string userIDKey string userNameKey string preferredUsernameKey string + emailKey string + emailVerifiedKey string + groupsKey string httpClient *http.Client logger log.Logger } @@ -41,36 +43,43 @@ type connectorData struct { } type Config struct { - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - RedirectURI string `json:"redirectURI"` - TokenURL string `json:"tokenURL"` - AuthorizationURL string `json:"authorizationURL"` - UserInfoURL string `json:"userInfoURL"` - Scopes []string `json:"scopes"` - GroupsKey string `json:"groupsKey"` - UserIDKey string `json:"userIDKey"` - UserNameKey string `json:"userNameKey"` - PreferredUsernameKey string `json:"preferredUsernameKey"` - RootCAs []string `json:"rootCAs"` - InsecureSkipVerify bool `json:"insecureSkipVerify"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + RedirectURI string `json:"redirectURI"` + TokenURL string `json:"tokenURL"` + AuthorizationURL string `json:"authorizationURL"` + UserInfoURL string `json:"userInfoURL"` + Scopes []string `json:"scopes"` + RootCAs []string `json:"rootCAs"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` + UserIDKey string `json:"userIDKey"` // defaults to "id" + ClaimMapping struct { + UserNameKey string `json:"userNameKey"` // defaults to "user_name" + PreferredUsernameKey string `json:"preferredUsernameKey"` // defaults to "preferred_username" + GroupsKey string `json:"groupsKey"` // defaults to "groups" + EmailKey string `json:"emailKey"` // defaults to "email" + EmailVerifiedKey string `json:"emailVerifiedKey"` // defaults to "email_verified" + } `json:"claimMapping"` } func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { var err error oauthConn := &oauthConnector{ - clientID: c.ClientID, - clientSecret: c.ClientSecret, - tokenURL: c.TokenURL, - authorizationURL: c.AuthorizationURL, - userInfoURL: c.UserInfoURL, - scopes: c.Scopes, - groupsKey: c.GroupsKey, - userIDKey: c.UserIDKey, - userNameKey: c.UserNameKey, - redirectURI: c.RedirectURI, - logger: logger, + clientID: c.ClientID, + clientSecret: c.ClientSecret, + tokenURL: c.TokenURL, + authorizationURL: c.AuthorizationURL, + userInfoURL: c.UserInfoURL, + scopes: c.Scopes, + redirectURI: c.RedirectURI, + logger: logger, + userIDKey: c.UserIDKey, + userNameKey: c.ClaimMapping.UserNameKey, + preferredUsernameKey: c.ClaimMapping.PreferredUsernameKey, + groupsKey: c.ClaimMapping.GroupsKey, + emailKey: c.ClaimMapping.EmailKey, + emailVerifiedKey: c.ClaimMapping.EmailVerifiedKey, } oauthConn.httpClient, err = newHTTPClient(c.RootCAs, c.InsecureSkipVerify) @@ -173,26 +182,39 @@ func (c *oauthConnector) HandleCallback(s connector.Scopes, r *http.Request) (id } if c.userIDKey == "" { - c.userIDKey = "user_id" + c.userIDKey = "id" } + userID, found := userInfoResult[c.userIDKey].(string) + if !found { + return identity, fmt.Errorf("OAuth Connector: not found %v claim", c.userIDKey) + } + + identity.UserID = userID if c.userNameKey == "" { c.userNameKey = "user_name" } - if c.groupsKey == "" { - c.groupsKey = "groups" - } - if c.preferredUsernameKey == "" { c.preferredUsernameKey = "preferred_username" } - identity.UserID, _ = userInfoResult[c.userIDKey].(string) + if c.groupsKey == "" { + c.groupsKey = "groups" + } + + if c.emailKey == "" { + c.emailKey = "email" + } + + if c.emailVerifiedKey == "" { + c.emailVerifiedKey = "email_verified" + } + identity.Username, _ = userInfoResult[c.userNameKey].(string) identity.PreferredUsername, _ = userInfoResult[c.preferredUsernameKey].(string) - identity.Email, _ = userInfoResult["email"].(string) - identity.EmailVerified, _ = userInfoResult["email_verified"].(bool) + identity.Email, _ = userInfoResult[c.emailKey].(string) + identity.EmailVerified, _ = userInfoResult[c.emailVerifiedKey].(bool) if s.Groups { groups := map[string]bool{} diff --git a/connector/oauth/oauth_test.go b/connector/oauth/oauth_test.go index 7ba12be1..b8074aa4 100644 --- a/connector/oauth/oauth_test.go +++ b/connector/oauth/oauth_test.go @@ -75,8 +75,8 @@ func TestHandleCallBackForGroupsInUserInfo(t *testing.T) { "user_id_key": "test-user-id", "user_name_key": "test-username", "preferred_username": "test-preferred-username", - "email": "test-email", - "email_verified": true, + "mail": "mod_mail", + "has_verified_email": false, "groups_key": []string{"admin-group", "user-group"}, } @@ -96,8 +96,8 @@ func TestHandleCallBackForGroupsInUserInfo(t *testing.T) { assert.Equal(t, identity.UserID, "test-user-id") assert.Equal(t, identity.Username, "test-username") assert.Equal(t, identity.PreferredUsername, "test-preferred-username") - assert.Equal(t, identity.Email, "test-email") - assert.Equal(t, identity.EmailVerified, true) + assert.Equal(t, identity.Email, "mod_mail") + assert.Equal(t, identity.EmailVerified, false) } func TestHandleCallBackForGroupsInToken(t *testing.T) { @@ -128,8 +128,8 @@ func TestHandleCallBackForGroupsInToken(t *testing.T) { assert.Equal(t, identity.PreferredUsername, "test-preferred-username") assert.Equal(t, identity.UserID, "test-user-id") assert.Equal(t, identity.Username, "test-username") - assert.Equal(t, identity.Email, "test-email") - assert.Equal(t, identity.EmailVerified, true) + assert.Equal(t, identity.Email, "") + assert.Equal(t, identity.EmailVerified, false) } func testSetup(t *testing.T, tokenClaims map[string]interface{}, userInfoClaims map[string]interface{}) *httptest.Server { @@ -198,11 +198,14 @@ func newConnector(t *testing.T, serverURL string) *oauthConnector { AuthorizationURL: serverURL + "/authorize", UserInfoURL: serverURL + "/userinfo", Scopes: []string{"openid", "groups"}, - GroupsKey: "groups_key", UserIDKey: "user_id_key", - UserNameKey: "user_name_key", } + testConfig.ClaimMapping.UserNameKey = "user_name_key" + testConfig.ClaimMapping.GroupsKey = "groups_key" + testConfig.ClaimMapping.EmailKey = "mail" + testConfig.ClaimMapping.EmailVerifiedKey = "has_verified_email" + log := logrus.New() conn, err := testConfig.Open("id", log) diff --git a/docs/connectors/oauth.md b/docs/connectors/oauth.md index 2092b495..d129bc5f 100644 --- a/docs/connectors/oauth.md +++ b/docs/connectors/oauth.md @@ -35,15 +35,30 @@ connectors: # scopes: # - identity - # Optional: Configurable keys for groups claim look up - # Default: groups - # groupsKey: - - # Optional: Configurable keys for user ID claim look up - # Default: user_id + # Optional: Configurable keys for user ID look up + # Default: id # userIDKey: - # Optional: Configurable keys for preferred username claim look up - # Default: preferred_username - # preferredUsernameKey: + # Auth roviders return non-standard user identity profile + # Use claimMapping to map those user infomations to standard claims: + claimMapping: + # Optional: Configurable keys for user name look up + # Default: user_name + # userNameKey: + + # Optional: Configurable keys for preferred username look up + # Default: preferred_username + # preferredUsernameKey: + + # Optional: Configurable keys for user groups look up + # Default: groups + # groupsKey: + + # Optional: Configurable keys for email look up + # Default: email + # emailKey: + + # Optional: Configurable keys for email verified look up + # Default: email_verified + # emailVerifiedKey: ```