mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-05-01 01:16:40 +00:00
feat(auth): add ForceUserInfo option to OpenID provider (#797)
Problem: When using Casdoor as an OpenID provider, there's an inconsistency between the user information in the JWT token and the UserInfo endpoint. The token contains the user's unique ID in the `name` field, while the UserInfo endpoint correctly returns the user's display name. Solution: This PR adds a new `ForceUserInfo` option to the OpenID provider configuration. When enabled, it forces the use of the UserInfo endpoint to retrieve user information instead of relying on claims from the ID token. Impact: - Default behavior remains unchanged (backward compatible) - New option allows administrators to force using UserInfo endpoint data - Particularly useful for providers like Casdoor that don't fully comply with OIDC standards Related: I've opened an issue in the Casdoor repository (https://github.com/casdoor/casdoor/issues/3806) to discuss the root cause. However, changing Casdoor's token structure might cause significant compatibility issues for existing integrations, so it's unclear if this can be fixed at the provider level. This PR provides a workaround in Vikunja that doesn't affect existing functionality.
This commit is contained in:
@@ -292,3 +292,127 @@ func TestGetOrCreateUser(t *testing.T) {
|
||||
assert.Equal(t, 11, int(u.ID), "user id 11 expected")
|
||||
})
|
||||
}
|
||||
|
||||
// TestMergeClaims tests the mergeClaims function with different configurations including forceUserInfo
|
||||
func TestMergeClaims(t *testing.T) {
|
||||
t.Run("ForceUserInfo enabled - should use userinfo values", func(t *testing.T) {
|
||||
// Setup token claims
|
||||
tokenClaims := &claims{
|
||||
Email: "token-email@example.com",
|
||||
Name: "Token Name",
|
||||
PreferredUsername: "token_username",
|
||||
}
|
||||
|
||||
// Setup userinfo claims
|
||||
userinfoClaims := &claims{
|
||||
Email: "userinfo-email@example.com",
|
||||
Name: "UserInfo Name",
|
||||
PreferredUsername: "userinfo_username",
|
||||
}
|
||||
|
||||
// Test with ForceUserInfo enabled
|
||||
err := mergeClaims(tokenClaims, userinfoClaims, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify userinfo data was used
|
||||
assert.Equal(t, "userinfo-email@example.com", tokenClaims.Email)
|
||||
assert.Equal(t, "UserInfo Name", tokenClaims.Name)
|
||||
assert.Equal(t, "userinfo_username", tokenClaims.PreferredUsername)
|
||||
})
|
||||
|
||||
t.Run("ForceUserInfo disabled - should use token values if present", func(t *testing.T) {
|
||||
// Setup token claims with all values
|
||||
tokenClaims := &claims{
|
||||
Email: "token-email@example.com",
|
||||
Name: "Token Name",
|
||||
PreferredUsername: "token_username",
|
||||
}
|
||||
|
||||
// Setup userinfo claims
|
||||
userinfoClaims := &claims{
|
||||
Email: "userinfo-email@example.com",
|
||||
Name: "UserInfo Name",
|
||||
PreferredUsername: "userinfo_username",
|
||||
}
|
||||
|
||||
// Test with ForceUserInfo disabled
|
||||
err := mergeClaims(tokenClaims, userinfoClaims, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify token data was preserved
|
||||
assert.Equal(t, "token-email@example.com", tokenClaims.Email)
|
||||
assert.Equal(t, "Token Name", tokenClaims.Name)
|
||||
assert.Equal(t, "token_username", tokenClaims.PreferredUsername)
|
||||
})
|
||||
|
||||
t.Run("Missing values - should use userinfo when token is missing values", func(t *testing.T) {
|
||||
// Setup token claims with missing values
|
||||
tokenClaims := &claims{
|
||||
Email: "token-email@example.com",
|
||||
// Missing Name and PreferredUsername
|
||||
}
|
||||
|
||||
// Setup userinfo claims
|
||||
userinfoClaims := &claims{
|
||||
Email: "userinfo-email@example.com",
|
||||
Name: "UserInfo Name",
|
||||
PreferredUsername: "userinfo_username",
|
||||
}
|
||||
|
||||
// Test with ForceUserInfo disabled, but missing values in token
|
||||
err := mergeClaims(tokenClaims, userinfoClaims, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify token email was kept, but missing fields were filled from userinfo
|
||||
assert.Equal(t, "token-email@example.com", tokenClaims.Email)
|
||||
assert.Equal(t, "UserInfo Name", tokenClaims.Name)
|
||||
assert.Equal(t, "userinfo_username", tokenClaims.PreferredUsername)
|
||||
})
|
||||
|
||||
t.Run("Use nickname when preferred_username is missing", func(t *testing.T) {
|
||||
// Setup token claims with missing preferred_username
|
||||
tokenClaims := &claims{
|
||||
Email: "token-email@example.com",
|
||||
Name: "Token Name",
|
||||
// Missing PreferredUsername
|
||||
}
|
||||
|
||||
// Setup userinfo claims with nickname but no preferred_username
|
||||
userinfoClaims := &claims{
|
||||
Email: "userinfo-email@example.com",
|
||||
Name: "UserInfo Name",
|
||||
Nickname: "userinfo_nickname",
|
||||
// Missing PreferredUsername to test fallback to nickname
|
||||
}
|
||||
|
||||
// Test with ForceUserInfo disabled
|
||||
err := mergeClaims(tokenClaims, userinfoClaims, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify nickname was used for preferred_username
|
||||
assert.Equal(t, "userinfo_nickname", tokenClaims.PreferredUsername)
|
||||
})
|
||||
|
||||
t.Run("Error when email is missing", func(t *testing.T) {
|
||||
// Setup token claims with missing email
|
||||
tokenClaims := &claims{
|
||||
// Missing Email
|
||||
Name: "Token Name",
|
||||
PreferredUsername: "token_username",
|
||||
}
|
||||
|
||||
// Setup userinfo claims also with missing email
|
||||
userinfoClaims := &claims{
|
||||
// Missing Email
|
||||
Name: "UserInfo Name",
|
||||
PreferredUsername: "userinfo_username",
|
||||
}
|
||||
|
||||
// Test with ForceUserInfo disabled
|
||||
err := mergeClaims(tokenClaims, userinfoClaims, false)
|
||||
|
||||
// Verify error is returned for missing email
|
||||
require.Error(t, err)
|
||||
assert.IsType(t, &user.ErrNoOpenIDEmailProvided{}, err)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user