Add claimMapping enforcement

Signed-off-by: Happy2C0de <46957159+Happy2C0de@users.noreply.github.com>
This commit is contained in:
Happy2C0de 2021-08-11 12:20:46 +02:00
parent ba1bd65c10
commit 45143c98b3
2 changed files with 92 additions and 37 deletions

View File

@ -56,7 +56,15 @@ type Config struct {
// PromptType will be used fot the prompt parameter (when offline_access, by default prompt=consent) // PromptType will be used fot the prompt parameter (when offline_access, by default prompt=consent)
PromptType string `json:"promptType"` PromptType string `json:"promptType"`
ClaimMapping struct { ClaimMapping ClaimMapping `json:"claimMapping"`
}
type ClaimMapping struct {
// Enforce the ClaimMapping.
// i.e. an 'email' claim will always be taken if available,
// irrelevant of the settings in EmailKey. This option will enforce the ClaimMapping options independent of the existing claims.
Enforce bool `json:"enforce"` // defaults to false
// Configurable key which contains the preferred username claims // Configurable key which contains the preferred username claims
PreferredUsernameKey string `json:"preferred_username"` // defaults to "preferred_username" PreferredUsernameKey string `json:"preferred_username"` // defaults to "preferred_username"
@ -65,7 +73,6 @@ type Config struct {
// Configurable key which contains the groups claims // Configurable key which contains the groups claims
GroupsKey string `json:"groups"` // defaults to "groups" GroupsKey string `json:"groups"` // defaults to "groups"
} `json:"claimMapping"`
} }
// Domains that don't support basic auth. golang.org/x/oauth2 has an internal // Domains that don't support basic auth. golang.org/x/oauth2 has an internal
@ -153,9 +160,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
promptType: c.PromptType, promptType: c.PromptType,
userIDKey: c.UserIDKey, userIDKey: c.UserIDKey,
userNameKey: c.UserNameKey, userNameKey: c.UserNameKey,
preferredUsernameKey: c.ClaimMapping.PreferredUsernameKey, claimMapping: c.ClaimMapping,
emailKey: c.ClaimMapping.EmailKey,
groupsKey: c.ClaimMapping.GroupsKey,
}, nil }, nil
} }
@ -178,9 +183,7 @@ type oidcConnector struct {
promptType string promptType string
userIDKey string userIDKey string
userNameKey string userNameKey string
preferredUsernameKey string claimMapping ClaimMapping
emailKey string
groupsKey string
} }
func (c *oidcConnector) Close() error { func (c *oidcConnector) Close() error {
@ -288,9 +291,14 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
return identity, fmt.Errorf("missing \"%s\" claim", userNameKey) return identity, fmt.Errorf("missing \"%s\" claim", userNameKey)
} }
preferredUsername, found := claims["preferred_username"].(string) prefUsername := "preferred_username"
preferredUsername, found := claims[prefUsername].(string)
if (!found || c.claimMapping.Enforce) && c.claimMapping.PreferredUsernameKey != "" {
prefUsername = c.claimMapping.PreferredUsernameKey
preferredUsername, found = claims[prefUsername].(string)
if !found { if !found {
preferredUsername, _ = claims[c.preferredUsernameKey].(string) return identity, fmt.Errorf("missing \"%s\" claim", prefUsername)
}
} }
hasEmailScope := false hasEmailScope := false
@ -304,9 +312,12 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
var email string var email string
emailKey := "email" emailKey := "email"
email, found = claims[emailKey].(string) email, found = claims[emailKey].(string)
if !found && c.emailKey != "" { if (!found || c.claimMapping.Enforce) && c.claimMapping.EmailKey != "" {
emailKey = c.emailKey emailKey = c.claimMapping.EmailKey
email, found = claims[emailKey].(string) email, found = claims[emailKey].(string)
if !found {
return identity, fmt.Errorf("missing \"%s\" claim", emailKey)
}
} }
if !found && hasEmailScope { if !found && hasEmailScope {
@ -326,8 +337,8 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
if c.insecureEnableGroups { if c.insecureEnableGroups {
groupsKey := "groups" groupsKey := "groups"
vs, found := claims[groupsKey].([]interface{}) vs, found := claims[groupsKey].([]interface{})
if !found { if (!found || c.claimMapping.Enforce) && c.claimMapping.GroupsKey != "" {
groupsKey = c.groupsKey groupsKey = c.claimMapping.GroupsKey
vs, found = claims[groupsKey].([]interface{}) vs, found = claims[groupsKey].([]interface{})
} }

View File

@ -49,9 +49,7 @@ func TestHandleCallback(t *testing.T) {
name string name string
userIDKey string userIDKey string
userNameKey string userNameKey string
preferredUsernameKey string claimMapping ClaimMapping
emailKey string
groupsKey string
insecureSkipEmailVerified bool insecureSkipEmailVerified bool
scopes []string scopes []string
expectUserID string expectUserID string
@ -81,7 +79,9 @@ func TestHandleCallback(t *testing.T) {
name: "customEmailClaim", name: "customEmailClaim",
userIDKey: "", // not configured userIDKey: "", // not configured
userNameKey: "", // not configured userNameKey: "", // not configured
emailKey: "mail", claimMapping: ClaimMapping{
EmailKey: "mail",
},
expectUserID: "subvalue", expectUserID: "subvalue",
expectUserName: "namevalue", expectUserName: "namevalue",
expectedEmailField: "emailvalue", expectedEmailField: "emailvalue",
@ -92,6 +92,25 @@ func TestHandleCallback(t *testing.T) {
"email_verified": true, "email_verified": true,
}, },
}, },
{
name: "enforceCustomEmailClaim",
userIDKey: "", // not configured
userNameKey: "", // not configured
claimMapping: ClaimMapping{
Enforce: true,
EmailKey: "custommail",
},
expectUserID: "subvalue",
expectUserName: "namevalue",
expectedEmailField: "customemailvalue",
token: map[string]interface{}{
"sub": "subvalue",
"name": "namevalue",
"mail": "emailvalue",
"custommail": "customemailvalue",
"email_verified": true,
},
},
{ {
name: "email_verified not in claims, configured to be skipped", name: "email_verified not in claims, configured to be skipped",
insecureSkipEmailVerified: true, insecureSkipEmailVerified: true,
@ -132,7 +151,9 @@ func TestHandleCallback(t *testing.T) {
}, },
{ {
name: "withPreferredUsernameKey", name: "withPreferredUsernameKey",
preferredUsernameKey: "username_key", claimMapping: ClaimMapping{
PreferredUsernameKey: "username_key",
},
expectUserID: "subvalue", expectUserID: "subvalue",
expectUserName: "namevalue", expectUserName: "namevalue",
expectPreferredUsername: "username_value", expectPreferredUsername: "username_value",
@ -201,7 +222,9 @@ func TestHandleCallback(t *testing.T) {
}, },
{ {
name: "customGroupsKey", name: "customGroupsKey",
groupsKey: "cognito:groups", claimMapping: ClaimMapping{
GroupsKey: "cognito:groups",
},
expectUserID: "subvalue", expectUserID: "subvalue",
expectUserName: "namevalue", expectUserName: "namevalue",
expectedEmailField: "emailvalue", expectedEmailField: "emailvalue",
@ -218,7 +241,9 @@ func TestHandleCallback(t *testing.T) {
}, },
{ {
name: "customGroupsKeyButGroupsProvided", name: "customGroupsKeyButGroupsProvided",
groupsKey: "cognito:groups", claimMapping: ClaimMapping{
GroupsKey: "cognito:groups",
},
expectUserID: "subvalue", expectUserID: "subvalue",
expectUserName: "namevalue", expectUserName: "namevalue",
expectedEmailField: "emailvalue", expectedEmailField: "emailvalue",
@ -234,6 +259,27 @@ func TestHandleCallback(t *testing.T) {
"cognito:groups": []string{"group3", "group4"}, "cognito:groups": []string{"group3", "group4"},
}, },
}, },
{
name: "customGroupsKeyButGroupsProvidedButEnforced",
claimMapping: ClaimMapping{
Enforce: true,
GroupsKey: "cognito:groups",
},
expectUserID: "subvalue",
expectUserName: "namevalue",
expectedEmailField: "emailvalue",
expectGroups: []string{"group3", "group4"},
scopes: []string{"groups"},
insecureSkipEmailVerified: true,
token: map[string]interface{}{
"sub": "subvalue",
"name": "namevalue",
"user_name": "username",
"email": "emailvalue",
"groups": []string{"group1", "group2"},
"cognito:groups": []string{"group3", "group4"},
},
},
} }
for _, tc := range tests { for _, tc := range tests {
@ -264,9 +310,7 @@ func TestHandleCallback(t *testing.T) {
InsecureEnableGroups: true, InsecureEnableGroups: true,
BasicAuthUnsupported: &basicAuth, BasicAuthUnsupported: &basicAuth,
} }
config.ClaimMapping.PreferredUsernameKey = tc.preferredUsernameKey config.ClaimMapping = tc.claimMapping
config.ClaimMapping.EmailKey = tc.emailKey
config.ClaimMapping.GroupsKey = tc.groupsKey
conn, err := newConnector(config) conn, err := newConnector(config)
if err != nil { if err != nil {