Search Identity Providers by alias or display name

Closes #32588

Signed-off-by: cgeorgilakis-grnet <cgeorgilakis@admin.grnet.gr>
This commit is contained in:
cgeorgilakis-grnet 2024-09-06 17:26:29 +03:00 committed by Alexander Schwartz
parent 1ed516d980
commit f8b1b3ee03
5 changed files with 37 additions and 16 deletions

View file

@ -52,6 +52,7 @@ import org.keycloak.utils.StringUtil;
import static org.keycloak.models.IdentityProviderModel.ALIAS; import static org.keycloak.models.IdentityProviderModel.ALIAS;
import static org.keycloak.models.IdentityProviderModel.ALIAS_NOT_IN; import static org.keycloak.models.IdentityProviderModel.ALIAS_NOT_IN;
import static org.keycloak.models.IdentityProviderModel.AUTHENTICATE_BY_DEFAULT; import static org.keycloak.models.IdentityProviderModel.AUTHENTICATE_BY_DEFAULT;
import static org.keycloak.models.IdentityProviderModel.DISPLAY_NAME;
import static org.keycloak.models.IdentityProviderModel.ENABLED; import static org.keycloak.models.IdentityProviderModel.ENABLED;
import static org.keycloak.models.IdentityProviderModel.FIRST_BROKER_LOGIN_FLOW_ID; import static org.keycloak.models.IdentityProviderModel.FIRST_BROKER_LOGIN_FLOW_ID;
import static org.keycloak.models.IdentityProviderModel.HIDE_ON_LOGIN; import static org.keycloak.models.IdentityProviderModel.HIDE_ON_LOGIN;
@ -506,13 +507,13 @@ public class JpaIdentityProviderStorageProvider implements IdentityProviderStora
if (search.startsWith("\"") && search.endsWith("\"")) { if (search.startsWith("\"") && search.endsWith("\"")) {
// exact search - alias must be an exact match // exact search - alias must be an exact match
search = search.substring(1, search.length() - 1); search = search.substring(1, search.length() - 1);
return builder.equal(idp.get(ALIAS), search); return builder.or(builder.equal(idp.get(ALIAS), search),builder.equal(idp.get(DISPLAY_NAME), search));
} else { } else {
search = search.replace("%", "\\%").replace("_", "\\_").replace("*", "%"); search = search.replace("%", "\\%").replace("_", "\\_").replace("*", "%");
if (!search.endsWith("%")) { if (!search.endsWith("%")) {
search += "%"; // default to prefix search search += "%"; // default to prefix search
} }
return builder.like(builder.lower(idp.get(ALIAS)), search.toLowerCase(), '\\'); return builder.or(builder.like(builder.lower(idp.get(ALIAS)), search.toLowerCase(), '\\'),builder.like(builder.lower(idp.get(DISPLAY_NAME)), search.toLowerCase(), '\\'));
} }
} }

View file

@ -38,6 +38,7 @@ public class IdentityProviderModel implements Serializable {
public static final String CASE_SENSITIVE_ORIGINAL_USERNAME = "caseSensitiveOriginalUsername"; public static final String CASE_SENSITIVE_ORIGINAL_USERNAME = "caseSensitiveOriginalUsername";
public static final String CLAIM_FILTER_NAME = "claimFilterName"; public static final String CLAIM_FILTER_NAME = "claimFilterName";
public static final String CLAIM_FILTER_VALUE = "claimFilterValue"; public static final String CLAIM_FILTER_VALUE = "claimFilterValue";
public static final String DISPLAY_NAME = "displayName";
public static final String DO_NOT_STORE_USERS = "doNotStoreUsers"; public static final String DO_NOT_STORE_USERS = "doNotStoreUsers";
public static final String ENABLED = "enabled"; public static final String ENABLED = "enabled";
public static final String FILTERED_BY_CLAIMS = "filteredByClaim"; public static final String FILTERED_BY_CLAIMS = "filteredByClaim";

View file

@ -179,15 +179,15 @@ public class LinkedAccountsResource {
return true; return true;
}else if (search.startsWith("\"") && search.endsWith("\"")) { }else if (search.startsWith("\"") && search.endsWith("\"")) {
final String name = search.substring(1, search.length() - 1); final String name = search.substring(1, search.length() - 1);
return linkedAccount.getProviderAlias().equals(name); return linkedAccount.getProviderAlias().equals(name) || linkedAccount.getDisplayName().equals(name);
} else if (search.startsWith("*") && search.endsWith("*")) { } else if (search.startsWith("*") && search.endsWith("*")) {
final String name = search.substring(1, search.length() - 1); final String name = search.substring(1, search.length() - 1);
return linkedAccount.getProviderAlias().contains(name); return linkedAccount.getProviderAlias().contains(name) || linkedAccount.getDisplayName().contains(name);
} else if (search.endsWith("*")) { } else if (search.endsWith("*")) {
final String name = search.substring(0, search.length() - 1); final String name = search.substring(0, search.length() - 1);
return linkedAccount.getProviderAlias().startsWith(name); return linkedAccount.getProviderAlias().startsWith(name) || linkedAccount.getDisplayName().startsWith(name);
} else { } else {
return linkedAccount.getProviderAlias().startsWith(search); return linkedAccount.getProviderAlias().startsWith(search) || linkedAccount.getDisplayName().startsWith(search);
} }
} }

View file

@ -89,17 +89,18 @@ public class LinkedAccountsRestServiceTest extends AbstractTestRealmKeycloakTest
testRealm.getUsers().add(UserBuilder.create().username("no-account-access").password("password").build()); testRealm.getUsers().add(UserBuilder.create().username("no-account-access").password("password").build());
testRealm.getUsers().add(UserBuilder.create().username("view-account-access").role("account", "view-profile").password("password").build()); testRealm.getUsers().add(UserBuilder.create().username("view-account-access").role("account", "view-profile").password("password").build());
String[] providers = new String[]{"saml:mysaml", "oidc:myoidc", "github", "gitlab", "twitter", "facebook", "bitbucket", "microsoft"}; String[] providers = new String[]{"saml:mysaml:saml-idp", "oidc:myoidc:oidc-idp", "github", "gitlab", "twitter", "facebook", "bitbucket", "microsoft"};
for (int i = 0; i < providers.length; i++) { for (int i = 0; i < providers.length; i++) {
String[] idpInfo = providers[i].split(":"); String[] idpInfo = providers[i].split(":");
testRealm.addIdentityProvider(IdentityProviderBuilder.create() testRealm.addIdentityProvider(IdentityProviderBuilder.create()
.providerId(idpInfo[0]) .providerId(idpInfo[0])
.alias(idpInfo.length == 1 ? idpInfo[0] : idpInfo[1]) .alias(idpInfo.length == 1 ? idpInfo[0] : idpInfo[1])
.displayName(idpInfo.length == 1 ? null : idpInfo[2])
.setAttribute("guiOrder", String.valueOf(i)) .setAttribute("guiOrder", String.valueOf(i))
.build()); .build());
} }
addFederatedIdentities(testRealm, "github", "gitlab", "twitter"); addFederatedIdentities(testRealm, "github", "gitlab", "mysaml");
} }
private void addFederatedIdentities(RealmRepresentation testRealm, String... idpAliases) { private void addFederatedIdentities(RealmRepresentation testRealm, String... idpAliases) {
@ -183,7 +184,7 @@ public class LinkedAccountsRestServiceTest extends AbstractTestRealmKeycloakTest
List<String> linkedAccountAliases = details.stream().map(LinkedAccountRepresentation::getProviderAlias).toList(); List<String> linkedAccountAliases = details.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("mysaml", "myoidc", "github", "gitlab", "twitter", "facebook", "bitbucket", "microsoft")); assertThat(linkedAccountAliases, contains("mysaml", "myoidc", "github", "gitlab", "twitter", "facebook", "bitbucket", "microsoft"));
List<String> expectedConnectedAccounts = List.of("github", "gitlab", "twitter"); List<String> expectedConnectedAccounts = List.of("github", "gitlab", "mysaml");
for (LinkedAccountRepresentation account : details) { for (LinkedAccountRepresentation account : details) {
if (expectedConnectedAccounts.contains(account.getProviderAlias())) { if (expectedConnectedAccounts.contains(account.getProviderAlias())) {
assertTrue(account.isConnected()); assertTrue(account.isConnected());
@ -201,7 +202,7 @@ public class LinkedAccountsRestServiceTest extends AbstractTestRealmKeycloakTest
assertEquals(3, accounts.size()); assertEquals(3, accounts.size());
List<String> linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList(); List<String> linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("github", "gitlab", "twitter")); assertThat(linkedAccountAliases, contains("mysaml", "github", "gitlab"));
for (LinkedAccountRepresentation account : accounts) { for (LinkedAccountRepresentation account : accounts) {
assertTrue(account.isConnected()); assertTrue(account.isConnected());
} }
@ -210,12 +211,12 @@ public class LinkedAccountsRestServiceTest extends AbstractTestRealmKeycloakTest
accounts = linkedAccountsRep("linked=true&first=0&max=2"); accounts = linkedAccountsRep("linked=true&first=0&max=2");
assertEquals(2, accounts.size()); assertEquals(2, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList(); linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("github", "gitlab")); assertThat(linkedAccountAliases, contains("mysaml","github"));
accounts = linkedAccountsRep("linked=true&first=2&max=4"); accounts = linkedAccountsRep("linked=true&first=2&max=4");
assertEquals(1, accounts.size()); assertEquals(1, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList(); linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("twitter")); assertThat(linkedAccountAliases, contains("gitlab"));
// now use a search string to further filter the results. // now use a search string to further filter the results.
accounts = linkedAccountsRep("linked=true&search=git*"); accounts = linkedAccountsRep("linked=true&search=git*");
@ -223,13 +224,18 @@ public class LinkedAccountsRestServiceTest extends AbstractTestRealmKeycloakTest
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList(); linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("github", "gitlab")); assertThat(linkedAccountAliases, contains("github", "gitlab"));
accounts = linkedAccountsRep("linked=true&search=*l-id*");
assertEquals(1, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("mysaml"));
// search unlinked identity providers. // search unlinked identity providers.
accounts = linkedAccountsRep("linked=false&first=0&max=10"); accounts = linkedAccountsRep("linked=false&first=0&max=10");
assertEquals(5, accounts.size()); assertEquals(5, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList(); linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
// the unlinked accounts are ordered by alias, not gui order (this test needs to be adjusted if the model is fixed to order by gui order) // the unlinked accounts are ordered by alias, not gui order (this test needs to be adjusted if the model is fixed to order by gui order)
assertThat(linkedAccountAliases, contains("bitbucket", "facebook", "microsoft", "myoidc", "mysaml")); assertThat(linkedAccountAliases, contains("bitbucket", "facebook", "microsoft", "myoidc", "twitter"));
for (LinkedAccountRepresentation account : accounts) { for (LinkedAccountRepresentation account : accounts) {
assertFalse(account.isConnected()); assertFalse(account.isConnected());
} }
@ -243,7 +249,7 @@ public class LinkedAccountsRestServiceTest extends AbstractTestRealmKeycloakTest
accounts = linkedAccountsRep("linked=false&first=3&max=3"); accounts = linkedAccountsRep("linked=false&first=3&max=3");
assertEquals(2, accounts.size()); assertEquals(2, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList(); linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("myoidc", "mysaml")); assertThat(linkedAccountAliases, contains("myoidc", "twitter"));
// now use a search string to filter the results. // now use a search string to filter the results.
accounts = linkedAccountsRep("linked=false&search=*o*"); accounts = linkedAccountsRep("linked=false&search=*o*");
@ -256,6 +262,12 @@ public class LinkedAccountsRestServiceTest extends AbstractTestRealmKeycloakTest
assertEquals(1, accounts.size()); assertEquals(1, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList(); linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("microsoft")); assertThat(linkedAccountAliases, contains("microsoft"));
//search based on display name
accounts = linkedAccountsRep("linked=false&search=*c-id*");
assertEquals(1, accounts.size());
linkedAccountAliases = accounts.stream().map(LinkedAccountRepresentation::getProviderAlias).toList();
assertThat(linkedAccountAliases, contains("myoidc"));
} }
@Test @Test

View file

@ -140,7 +140,7 @@ public class IdentityProviderTest extends AbstractAdminTest {
@Test @Test
public void testFind() { public void testFind() {
create(createRep("twitter", "twitter", true, Collections.singletonMap("key1", "value1"))); create(createRep("twitter", "twitter idp","twitter", true, Collections.singletonMap("key1", "value1")));
create(createRep("linkedin-openid-connect", "linkedin-openid-connect")); create(createRep("linkedin-openid-connect", "linkedin-openid-connect"));
create(createRep("google", "google")); create(createRep("google", "google"));
create(createRep("github", "github")); create(createRep("github", "github"));
@ -160,6 +160,9 @@ public class IdentityProviderTest extends AbstractAdminTest {
Assert.assertNames(realm.identityProviders().find("*oo*", true, 0, 5), "google", "facebook"); Assert.assertNames(realm.identityProviders().find("*oo*", true, 0, 5), "google", "facebook");
//based on display name search
Assert.assertNames(realm.identityProviders().find("*ter i*", true, 0, 5), "twitter");
List<IdentityProviderRepresentation> results = realm.identityProviders().find("\"twitter\"", true, 0, 5); List<IdentityProviderRepresentation> results = realm.identityProviders().find("\"twitter\"", true, 0, 5);
Assert.assertNames(results, "twitter"); Assert.assertNames(results, "twitter");
Assert.assertTrue("Result is not in brief representation", results.iterator().next().getConfig().isEmpty()); Assert.assertTrue("Result is not in brief representation", results.iterator().next().getConfig().isEmpty());
@ -603,10 +606,14 @@ public class IdentityProviderTest extends AbstractAdminTest {
} }
private IdentityProviderRepresentation createRep(String id, String providerId,boolean enabled, Map<String, String> config) { private IdentityProviderRepresentation createRep(String id, String providerId,boolean enabled, Map<String, String> config) {
return createRep(id, id, providerId, enabled, config);
}
private IdentityProviderRepresentation createRep(String id, String displayName, String providerId, boolean enabled, Map<String, String> config) {
IdentityProviderRepresentation idp = new IdentityProviderRepresentation(); IdentityProviderRepresentation idp = new IdentityProviderRepresentation();
idp.setAlias(id); idp.setAlias(id);
idp.setDisplayName(id); idp.setDisplayName(displayName);
idp.setProviderId(providerId); idp.setProviderId(providerId);
idp.setEnabled(enabled); idp.setEnabled(enabled);
if (config != null) { if (config != null) {