-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Map only specific type of OIDC Claims #58524
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
e93302d
Map only specific type of OIDC Claims
jkakavas 4274675
address feedback
jkakavas f1b3589
unused import
jkakavas 1c02a7d
Merge remote-tracking branch 'origin/master' into claims-mapping-changes
jkakavas fd030f0
Don't try to validate null claims
jkakavas cb9e3f2
fix parsing and validation logic
jkakavas ee0b57b
address feedback
jkakavas 2499dc2
checkstyle
jkakavas 41aeec6
Merge remote-tracking branch 'origin/master' into claims-mapping-changes
jkakavas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
import com.nimbusds.openid.connect.sdk.Nonce; | ||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.action.support.PlainActionFuture; | ||
import org.elasticsearch.common.Nullable; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.common.util.concurrent.ThreadContext; | ||
import org.elasticsearch.env.Environment; | ||
|
@@ -85,7 +86,7 @@ public void testAuthentication() throws Exception { | |
|
||
final boolean notPopulateMetadata = randomBoolean(); | ||
final String authenticatingRealm = randomBoolean() ? REALM_NAME : null; | ||
AuthenticationResult result = authenticateWithOidc(principal, roleMapper, notPopulateMetadata, false, authenticatingRealm); | ||
AuthenticationResult result = authenticateWithOidc(principal, roleMapper, notPopulateMetadata, false, authenticatingRealm, null); | ||
assertThat(result, notNullValue()); | ||
assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.SUCCESS)); | ||
assertThat(result.getUser().principal(), equalTo(principal)); | ||
|
@@ -104,6 +105,68 @@ public void testAuthentication() throws Exception { | |
} | ||
} | ||
|
||
public void testClaimPropertyMapping() throws Exception { | ||
final UserRoleMapper roleMapper = mock(UserRoleMapper.class); | ||
final String principal = randomAlphaOfLength(12); | ||
AtomicReference<UserRoleMapper.UserData> userData = new AtomicReference<>(); | ||
doAnswer(invocation -> { | ||
assert invocation.getArguments().length == 2; | ||
userData.set((UserRoleMapper.UserData) invocation.getArguments()[0]); | ||
ActionListener<Set<String>> listener = (ActionListener<Set<String>>) invocation.getArguments()[1]; | ||
listener.onResponse(new HashSet<>(Arrays.asList("kibana_user", "role1"))); | ||
return null; | ||
}).when(roleMapper).resolveRoles(any(UserRoleMapper.UserData.class), any(ActionListener.class)); | ||
Map<String, Object> claimsWithObject = Map.of( | ||
"groups", List.of(Map.of("key1", List.of("value1", "value2")), Map.of("key2", List.of("value1", "value2"))) | ||
); | ||
Map<String, Object> claimsWithNumber = Map.of( | ||
"groups", List.of(2, "value2")); | ||
Exception e = expectThrows(Exception.class, () -> authenticateWithOidc(principal, roleMapper, false, false, | ||
REALM_NAME, claimsWithObject)); | ||
Exception e2 = expectThrows(Exception.class, () -> authenticateWithOidc(principal, roleMapper, false, false, | ||
REALM_NAME, claimsWithNumber)); | ||
assertThat(e.getCause().getMessage(), containsString("expects a claim with String or a String Array value")); | ||
assertThat(e2.getCause().getMessage(), containsString("expects a claim with String or a String Array value")); | ||
} | ||
|
||
public void testClaimMetadataMapping() throws Exception { | ||
final UserRoleMapper roleMapper = mock(UserRoleMapper.class); | ||
final String principal = randomAlphaOfLength(12); | ||
AtomicReference<UserRoleMapper.UserData> userData = new AtomicReference<>(); | ||
doAnswer(invocation -> { | ||
assert invocation.getArguments().length == 2; | ||
userData.set((UserRoleMapper.UserData) invocation.getArguments()[0]); | ||
ActionListener<Set<String>> listener = (ActionListener<Set<String>>) invocation.getArguments()[1]; | ||
listener.onResponse(new HashSet<>(Arrays.asList("kibana_user", "role1"))); | ||
return null; | ||
}).when(roleMapper).resolveRoles(any(UserRoleMapper.UserData.class), any(ActionListener.class)); | ||
Map<String, Object> claims = Map.of( | ||
"string", "String", | ||
"number", 232, | ||
"boolean", true, | ||
"string_array", List.of("one", "two", "three"), | ||
"number_array", List.of(1, 2, 3), | ||
"boolean_array", List.of(true, false, true), | ||
"object", Map.of("key", List.of("value1", "value2")), | ||
"object_array", List.of(Map.of("key1", List.of("value1", "value2")), Map.of("key2", List.of("value1", "value2"))) | ||
); | ||
AuthenticationResult result = authenticateWithOidc(principal, roleMapper, false, false, REALM_NAME, claims); | ||
assertThat(result, notNullValue()); | ||
assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.SUCCESS)); | ||
assertThat(result.getUser().principal(), equalTo(principal)); | ||
assertThat(result.getUser().email(), equalTo("[email protected]")); | ||
assertThat(result.getUser().fullName(), equalTo("Clinton Barton")); | ||
assertThat(result.getUser().roles(), arrayContainingInAnyOrder("kibana_user", "role1")); | ||
assertTrue(result.getUser().metadata().containsKey("oidc(string)")); | ||
assertTrue(result.getUser().metadata().containsKey("oidc(number)")); | ||
assertTrue(result.getUser().metadata().containsKey("oidc(boolean)")); | ||
assertTrue(result.getUser().metadata().containsKey("oidc(string_array)")); | ||
assertTrue(result.getUser().metadata().containsKey("oidc(boolean_array)")); | ||
assertTrue(result.getUser().metadata().containsKey("oidc(number_array)")); | ||
assertFalse(result.getUser().metadata().containsKey("oidc(object_array)")); | ||
assertFalse(result.getUser().metadata().containsKey("oidc(object)")); | ||
} | ||
|
||
public void testWithAuthorizingRealm() throws Exception { | ||
final UserRoleMapper roleMapper = mock(UserRoleMapper.class); | ||
final String principal = randomAlphaOfLength(12); | ||
|
@@ -114,7 +177,7 @@ public void testWithAuthorizingRealm() throws Exception { | |
return null; | ||
}).when(roleMapper).resolveRoles(any(UserRoleMapper.UserData.class), any(ActionListener.class)); | ||
final String authenticatingRealm = randomBoolean() ? REALM_NAME : null; | ||
AuthenticationResult result = authenticateWithOidc(principal, roleMapper, randomBoolean(), true, authenticatingRealm); | ||
AuthenticationResult result = authenticateWithOidc(principal, roleMapper, randomBoolean(), true, authenticatingRealm, null); | ||
assertThat(result, notNullValue()); | ||
assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.SUCCESS)); | ||
assertThat(result.getUser().principal(), equalTo(principal)); | ||
|
@@ -132,7 +195,7 @@ public void testWithAuthorizingRealm() throws Exception { | |
public void testAuthenticationWithWrongRealm() throws Exception{ | ||
final String principal = randomAlphaOfLength(12); | ||
AuthenticationResult result = authenticateWithOidc(principal, mock(UserRoleMapper.class), randomBoolean(), true, | ||
REALM_NAME+randomAlphaOfLength(8)); | ||
REALM_NAME + randomAlphaOfLength(8), null); | ||
assertThat(result, notNullValue()); | ||
assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.CONTINUE)); | ||
} | ||
|
@@ -334,8 +397,8 @@ public void testBuildingAuthenticationRequestWithLoginHint() { | |
} | ||
|
||
private AuthenticationResult authenticateWithOidc(String principal, UserRoleMapper roleMapper, boolean notPopulateMetadata, | ||
boolean useAuthorizingRealm | ||
,String authenticatingRealm) | ||
boolean useAuthorizingRealm, String authenticatingRealm, | ||
@Nullable Map<String, Object> additionalClaims) | ||
throws Exception { | ||
RealmConfig.RealmIdentifier realmIdentifier = new RealmConfig.RealmIdentifier("mock", "mock_lookup"); | ||
final MockLookupRealm lookupRealm = new MockLookupRealm( | ||
|
@@ -360,7 +423,7 @@ private AuthenticationResult authenticateWithOidc(String principal, UserRoleMapp | |
final OpenIdConnectRealm realm = new OpenIdConnectRealm(config, authenticator, roleMapper); | ||
initializeRealms(realm, lookupRealm); | ||
final OpenIdConnectToken token = new OpenIdConnectToken("", new State(), new Nonce(), authenticatingRealm); | ||
final JWTClaimsSet claims = new JWTClaimsSet.Builder() | ||
final JWTClaimsSet.Builder claimsBuilder = new JWTClaimsSet.Builder() | ||
.subject(principal) | ||
.audience("https://rp.elastic.co/cb") | ||
.expirationTime(Date.from(now().plusSeconds(3600))) | ||
|
@@ -370,9 +433,13 @@ private AuthenticationResult authenticateWithOidc(String principal, UserRoleMapp | |
.claim("groups", Arrays.asList("group1", "group2", "groups3")) | ||
.claim("mail", "[email protected]") | ||
.claim("name", "Clinton Barton") | ||
.claim("id_token_hint", "thisis.aserialized.jwt") | ||
.build(); | ||
|
||
.claim("id_token_hint", "thisis.aserialized.jwt"); | ||
if (additionalClaims != null) { | ||
for (Map.Entry<String, Object> entry : additionalClaims.entrySet()) { | ||
claimsBuilder.claim(entry.getKey(), entry.getValue()); | ||
} | ||
} | ||
final JWTClaimsSet claims = claimsBuilder.build(); | ||
doAnswer((i) -> { | ||
ActionListener<JWTClaimsSet> listener = (ActionListener<JWTClaimsSet>) i.getArguments()[1]; | ||
listener.onResponse(claims); | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we probably want to ensure that these are homogenous collections. I don't know how we would make sense of
[ "1", true, 1.0 ]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You used Greek so I get to be pedantic 🎉 s/heterogeneous/homogeneous/ :)
In all seriousness, I took a lenient stance here on purpose for metadata ( as opposed to user properties where I allowed only strings ).
happy to reconsider if you think this would cause an issue somewhere else or that it is too lenient for the sake of being lenient but I don't see any specific problem with it as is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My comment clearly says
homogeneous
😈Fair enough - as long as it's a considered position, I'm not worried.