8000 Fix crash and inconsistent matching in account console linked-account… by sguilhen · Pull Request #54652 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public Response linkedAccounts(
Map<String, String> searchOptions = Map.of(
IdentityProviderModel.ENABLED, "true",
IdentityProviderModel.ORGANIZATION_ID, "",
IdentityProviderModel.SEARCH, search == null ? "" : search,
IdentityProviderModel.SEARCH, resolveSearch(search),
IdentityProviderModel.ALIAS_NOT_IN, fedAliasesToExclude,
IdentityProviderModel.SHOW_IN_ACCOUNT_CONSOLE, IdentityProviderShowInAccountConsole.ALWAYS.name());

Expand Down Expand Up @@ -186,20 +186,33 @@ private LinkedAccountRepresentation toLinkedAccount(IdentityProviderModel provid
return rep;
}

private String resolveSearch(String search) {
if (search == null) {
return "";
}
// a lone '"' would otherwise be treated by JpaIdentityProviderStorageProvider#getAliasSearchPredicate
// as a quoted/exact search with an empty name and throw a StringIndexOutOfBoundsException; appending
// '*' keeps the same "starts with a literal quote" semantics while avoiding that crash
return "\"".equals(search) ? search + "*" : search;
}

private boolean matchesLinkedProvider(final LinkedAccountRepresentation linkedAccount, final String search) {
if (search == null) {
return true;
}else if (search.startsWith("\"") && search.endsWith("\"")) {
} else if (search.length() > 1 && search.startsWith("\"") && search.endsWith("\"")) {
final String name = search.substring(1, search.length() - 1);
return linkedAccount.getProviderAlias().equals(name) || linkedAccount.getDisplayName().equals(name);
} else if (search.startsWith("*") && search.endsWith("*")) {
final String name = search.substring(1, search.length() - 1);
return linkedAccount.getProviderAlias().contains(name) || linkedAccount.getDisplayName().contains(name);
} else if (search.length() > 1 && search.startsWith("*") && search.endsWith("*")) {
// non-exact searches are case-insensitive, matching the LIKE lower(...) semantics used by
// JpaIdentityProviderStorageProvider#getAliasSearchPredicate for the "linked=false" branch
final String name = search.substring(1, search.length() - 1).toLowerCase(Locale.ROOT);
return linkedAccount.getProviderAlias().toLowerCase(Locale.ROOT).contains(name) || linkedAccount.getDisplayName().toLowerCase(Locale.ROOT).contains(name);
} else if (search.endsWith("*")) {
final String name = search.substring(0, search.length() - 1);
return linkedAccount.getProviderAlias().startsWith(name) || linkedAccount.getDisplayName().startsWith(name);
final String name = search.substring(0, search.length() - 1).toLowerCase(Locale.ROOT);
return linkedAccount.getProviderAlias().toLowerCase(Locale.ROOT).startsWith(name) || linkedAccount.getDisplayName().toLowerCase(Locale.ROOT).startsWith(name);
} else {
return linkedAccount.getProviderAlias().startsWith(search) || linkedAccount.getDisplayName().startsWith(search);
final String name = search.toLowerCase(Locale.ROOT);
return linkedAccount.getProviderAlias().toLowerCase(Locale.ROOT).startsWith(name) || linkedAccount.getDisplayName().toLowerCase(Locale.ROOT).startsWith(name);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,34 @@ public void testGetLinkedAccountsWithPagination() throws IOException {
assertEquals(1, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("myoidc"));

// non-exact searches are case-insensitive on both branches
accounts = linkedAccountsRep("linked=true&search=GIT*");
assertEquals(2, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("github", "gitlab"));

accounts = linkedAccountsRep("linked=false&search=*O*");
assertEquals(3, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("facebook", "microsoft", "myoidc"));
}

@Test
public void testGetLinkedAccountsWithEdgeCaseSearchValues() throws IOException {
// a lone '*' is the "contains everything" wildcard, equivalent to no search at all
List<LinkedAccountRepresentation> accounts = linkedAccountsRep("linked=true&search=*");
assertEquals(3, accounts.size());

accounts = linkedAccountsRep("linked=false&search=*");
assertEquals(5, accounts.size());

// a lone '"' must not crash the endpoint; it matches nothing on either branch
accounts = linkedAccountsRep("linked=true&search=%22");
assertEquals(0, accounts.size());

accounts = linkedAccountsRep("linked=false&search=%22");
assertEquals(0, accounts.size());
}

@Test
Expand Down
Loading
0