diff --git a/pom.xml b/pom.xml index 837822b9..54299d1d 100644 --- a/pom.xml +++ b/pom.xml @@ -32,8 +32,8 @@ UTF-8 io.phasetwo.service 5.11.2 - 26.0.2 - 26.0.1 + 26.0.7 + 26.0.3 6.2.7.Final 1.18.34 33.0.0-jre diff --git a/src/main/java/io/phasetwo/service/auth/action/PortalLinkActionTokenHandlerFactory.java b/src/main/java/io/phasetwo/service/auth/action/PortalLinkActionTokenHandlerFactory.java index 5f2cab6e..ce2e8415 100644 --- a/src/main/java/io/phasetwo/service/auth/action/PortalLinkActionTokenHandlerFactory.java +++ b/src/main/java/io/phasetwo/service/auth/action/PortalLinkActionTokenHandlerFactory.java @@ -6,6 +6,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +@SuppressWarnings("rawtypes") @AutoService(ActionTokenHandlerFactory.class) public class PortalLinkActionTokenHandlerFactory implements ActionTokenHandlerFactory { diff --git a/src/main/java/io/phasetwo/service/broker/Mappers.java b/src/main/java/io/phasetwo/service/broker/Mappers.java new file mode 100644 index 00000000..fcd2a137 --- /dev/null +++ b/src/main/java/io/phasetwo/service/broker/Mappers.java @@ -0,0 +1,23 @@ +package io.phasetwo.service.broker; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import lombok.extern.jbosslog.JBossLog; +import org.keycloak.models.IdentityProviderSyncMode; + +@JBossLog +public final class Mappers { + + public static final String ARE_ATTRIBUTE_VALUES_REGEX_PROPERTY_NAME = + "are.attribute.values.regex"; + public static final String ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME = "are.claim.values.regex"; + public static final String ATTRIBUTE_PROPERTY_NAME = "attributes"; + public static final String CLAIM_PROPERTY_NAME = "claims"; + public static final String ORG_ADD_PROPERTY_NAME = "org_add"; + public static final String ORG_PROPERTY_NAME = "org"; + public static final String ORG_ROLE_PROPERTY_NAME = "org_role"; + + public static final Set IDENTITY_PROVIDER_SYNC_MODES = + new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values())); +} diff --git a/src/main/java/io/phasetwo/service/broker/oidc/mappers/AdvancedClaimToOrgRoleMapper.java b/src/main/java/io/phasetwo/service/broker/oidc/mappers/AdvancedClaimToOrgRoleMapper.java new file mode 100644 index 00000000..679695e1 --- /dev/null +++ b/src/main/java/io/phasetwo/service/broker/oidc/mappers/AdvancedClaimToOrgRoleMapper.java @@ -0,0 +1,181 @@ +package io.phasetwo.service.broker.oidc.mappers; + +import static io.phasetwo.service.broker.Mappers.*; +import static org.keycloak.utils.RegexUtils.valueMatchesRegex; + +import com.google.auto.service.AutoService; +import io.phasetwo.service.broker.OrgRoleMapper; +import io.phasetwo.service.model.OrganizationModel; +import io.phasetwo.service.model.OrganizationRoleModel; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.extern.jbosslog.JBossLog; +import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory; +import org.keycloak.broker.oidc.OIDCIdentityProviderFactory; +import org.keycloak.broker.oidc.mappers.AbstractClaimMapper; +import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.broker.provider.IdentityProviderMapper; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.IdentityProviderSyncMode; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.ProviderConfigProperty; + +@JBossLog +@AutoService(IdentityProviderMapper.class) +public class AdvancedClaimToOrgRoleMapper extends AbstractClaimMapper implements OrgRoleMapper { + + public static final String[] COMPATIBLE_PROVIDERS = { + KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID + }; + + private static final List configProperties = new ArrayList<>(); + + static { + ProviderConfigProperty claimsProperty = new ProviderConfigProperty(); + claimsProperty.setName(CLAIM_PROPERTY_NAME); + claimsProperty.setLabel("Claims"); + claimsProperty.setHelpText( + "Name and value of the claims to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'. To use dot (.) literally, escape it with backslash (\\.)"); + claimsProperty.setType(ProviderConfigProperty.MAP_TYPE); + configProperties.add(claimsProperty); + ProviderConfigProperty isClaimValueRegexProperty = new ProviderConfigProperty(); + isClaimValueRegexProperty.setName(ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME); + isClaimValueRegexProperty.setLabel("Regex Claim Values"); + isClaimValueRegexProperty.setHelpText( + "If enabled claim values are interpreted as regular expressions."); + isClaimValueRegexProperty.setType(ProviderConfigProperty.BOOLEAN_TYPE); + configProperties.add(isClaimValueRegexProperty); + + OrgRoleMapper.addOrgConfigProperties(configProperties); + } + + public static final String PROVIDER_ID = "oidc-advanced-org-role-idp-mapper"; + + @Override + public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) { + return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode); + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String[] getCompatibleProviders() { + return COMPATIBLE_PROVIDERS; + } + + @Override + public String getDisplayCategory() { + return "Role Importer"; + } + + @Override + public String getDisplayType() { + return "Advanced Claim to Org Role"; + } + + @Override + public String getHelpText() { + return "If all claims exists, grant the user the specified organization role."; + } + + protected boolean applies( + IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + Map> claims = mapperModel.getConfigMap(CLAIM_PROPERTY_NAME); + boolean areClaimValuesRegex = + Boolean.parseBoolean( + mapperModel.getConfig().getOrDefault(ARE_CLAIM_VALUES_REGEX_PROPERTY_NAME, "false")); + + for (Map.Entry> claim : claims.entrySet()) { + Object claimValue = getClaimValue(context, claim.getKey()); + for (String value : claim.getValue()) { + boolean claimValuesMismatch = + !(areClaimValuesRegex + ? valueMatchesRegex(value, claimValue) + : valueEquals(value, claimValue)); + if (claimValuesMismatch) { + return false; + } + } + } + + return true; + } + + @Override + public void importNewUser( + KeycloakSession session, + RealmModel realm, + UserModel user, + IdentityProviderMapperModel mapperModel, + BrokeredIdentityContext context) { + OrganizationModel org = getOrg(session, realm, mapperModel); + if (org == null) { + return; + } + OrganizationRoleModel role = getOrgRole(session, realm, mapperModel); + if (role == null) { + return; + } + + if (applies(mapperModel, context)) { + addUserToOrg(org, user, mapperModel); + grantOrgRole(org, role, user); + } + } + + @Override + public void updateBrokeredUserLegacy( + KeycloakSession session, + RealmModel realm, + UserModel user, + IdentityProviderMapperModel mapperModel, + BrokeredIdentityContext context) { + OrganizationModel org = getOrg(session, realm, mapperModel); + if (org == null) { + return; + } + OrganizationRoleModel role = getOrgRole(session, realm, mapperModel); + if (role == null) { + return; + } + + if (!applies(mapperModel, context)) { + revokeOrgRole(org, role, user); + } + } + + @Override + public void updateBrokeredUser( + KeycloakSession session, + RealmModel realm, + UserModel user, + IdentityProviderMapperModel mapperModel, + BrokeredIdentityContext context) { + OrganizationModel org = getOrg(session, realm, mapperModel); + if (org == null) { + return; + } + OrganizationRoleModel role = getOrgRole(session, realm, mapperModel); + if (role == null) { + return; + } + + if (applies(mapperModel, context)) { + addUserToOrg(org, user, mapperModel); + grantOrgRole(org, role, user); + } else { + revokeOrgRole(org, role, user); + } + } +} diff --git a/src/main/java/io/phasetwo/service/broker/oidc/mappers/OrgRoleMapper.java b/src/main/java/io/phasetwo/service/broker/oidc/mappers/OrgRoleMapper.java new file mode 100644 index 00000000..8043ac8a --- /dev/null +++ b/src/main/java/io/phasetwo/service/broker/oidc/mappers/OrgRoleMapper.java @@ -0,0 +1,123 @@ +package io.phasetwo.service.broker; + +import static io.phasetwo.service.broker.Mappers.*; + +import com.google.common.base.Strings; +import io.phasetwo.service.model.OrganizationModel; +import io.phasetwo.service.model.OrganizationProvider; +import io.phasetwo.service.model.OrganizationRoleModel; +import java.util.List; +import org.jboss.logging.Logger; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.ProviderConfigProperty; + +public interface OrgRoleMapper { + + static final Logger log = Logger.getLogger(OrgRoleMapper.class); + + static void addOrgConfigProperties(List configProperties) { + ProviderConfigProperty orgAdd = new ProviderConfigProperty(); + orgAdd.setName(ORG_ADD_PROPERTY_NAME); + orgAdd.setLabel("Add To Organization"); + orgAdd.setHelpText("Add user to the organization as a member if not already."); + orgAdd.setType(ProviderConfigProperty.BOOLEAN_TYPE); + configProperties.add(orgAdd); + ProviderConfigProperty org = new ProviderConfigProperty(); + org.setName(ORG_PROPERTY_NAME); + org.setLabel("Organization"); + org.setHelpText("Organization containing the role to grant to user."); + org.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(org); + ProviderConfigProperty orgRole = new ProviderConfigProperty(); + orgRole.setName(ORG_ROLE_PROPERTY_NAME); + orgRole.setLabel("Organization Role"); + orgRole.setHelpText("Organization role to grant to user."); + orgRole.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(orgRole); + } + + default boolean orgAdd(IdentityProviderMapperModel mapperModel) { + return Boolean.parseBoolean( + mapperModel.getConfig().getOrDefault(ORG_ADD_PROPERTY_NAME, "false")); + } + + default void addUserToOrg( + OrganizationModel org, UserModel user, IdentityProviderMapperModel mapperModel) { + if (orgAdd(mapperModel) && !org.hasMembership(user)) { + org.grantMembership(user); + } + } + + default OrganizationModel getOrg( + KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel) { + OrganizationProvider orgs = session.getProvider(OrganizationProvider.class); + String orgName = mapperModel.getConfig().getOrDefault(ORG_PROPERTY_NAME, null); + if (Strings.isNullOrEmpty(orgName)) return null; + return orgs.getOrganizationByName(realm, orgName); + } + + default OrganizationRoleModel getOrgRole( + KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel) { + OrganizationProvider orgs = session.getProvider(OrganizationProvider.class); + String orgName = mapperModel.getConfig().getOrDefault(ORG_PROPERTY_NAME, null); + String orgRoleName = mapperModel.getConfig().getOrDefault(ORG_ROLE_PROPERTY_NAME, null); + if (Strings.isNullOrEmpty(orgName) || Strings.isNullOrEmpty(orgRoleName)) return null; + else return getOrgRole(orgs, orgName, orgRoleName, realm); + } + + default OrganizationRoleModel getOrgRole( + OrganizationProvider orgs, String orgName, String orgRoleName, RealmModel realm) { + OrganizationModel org = orgs.getOrganizationByName(realm, orgName); + if (org == null) { + log.debugf("Cannot map non-existent org %s", orgName); + return null; + } + OrganizationRoleModel role = org.getRoleByName(orgRoleName); + if (role == null) { + log.debugf("Cannot map non-existent org role %s - %s", orgName, orgRoleName); + return null; + } + return role; + } + + default void grantOrgRole(OrganizationModel org, OrganizationRoleModel role, UserModel user) { + if (org.hasMembership(user)) { + role.grantRole(user); + } + } + + default void revokeOrgRole(OrganizationModel org, OrganizationRoleModel role, UserModel user) { + if (org.hasMembership(user)) { + role.revokeRole(user); + } + } + + default void grantOrgRole( + KeycloakSession session, + RealmModel realm, + UserModel user, + IdentityProviderMapperModel mapperModel) { + OrganizationProvider orgs = session.getProvider(OrganizationProvider.class); + String orgName = mapperModel.getConfig().getOrDefault(ORG_PROPERTY_NAME, null); + String orgRoleName = mapperModel.getConfig().getOrDefault(ORG_ROLE_PROPERTY_NAME, null); + if (Strings.isNullOrEmpty(orgName) || Strings.isNullOrEmpty(orgRoleName)) return; + + OrganizationModel org = orgs.getOrganizationByName(realm, orgName); + OrganizationRoleModel role = getOrgRole(orgs, orgName, orgRoleName, realm); + if (org != null && role != null) { + if (orgAdd(mapperModel)) { + if (!org.hasMembership(user)) { + log.infof("Granting org: %s membership to %s", orgName, user.getUsername()); + org.grantMembership(user); + } + } + if (org.hasMembership(user)) { + log.infof("Granting org: %s - role: %s to %s", orgName, orgRoleName, user.getUsername()); + role.grantRole(user); + } + } + } +} diff --git a/src/main/java/io/phasetwo/service/broker/provider/HardcodedOrgRoleMapper.java b/src/main/java/io/phasetwo/service/broker/provider/HardcodedOrgRoleMapper.java new file mode 100644 index 00000000..f0abda8b --- /dev/null +++ b/src/main/java/io/phasetwo/service/broker/provider/HardcodedOrgRoleMapper.java @@ -0,0 +1,96 @@ +package io.phasetwo.service.broker.provider; + +import static io.phasetwo.service.broker.Mappers.*; + +import com.google.auto.service.AutoService; +import io.phasetwo.service.broker.OrgRoleMapper; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.jbosslog.JBossLog; +import org.keycloak.broker.provider.AbstractIdentityProviderMapper; +import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.broker.provider.IdentityProviderMapper; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.IdentityProviderSyncMode; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.ProviderConfigProperty; + +@JBossLog +@AutoService(IdentityProviderMapper.class) +public class HardcodedOrgRoleMapper extends AbstractIdentityProviderMapper + implements OrgRoleMapper { + protected static final List configProperties = new ArrayList<>(); + + static { + OrgRoleMapper.addOrgConfigProperties(configProperties); + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getDisplayCategory() { + return "Role Importer"; + } + + @Override + public String getDisplayType() { + return "Hardcoded Organization Role"; + } + + public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER}; + + public static final String PROVIDER_ID = "oidc-hardcoded-org-role-idp-mapper"; + + @Override + public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) { + return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode); + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String[] getCompatibleProviders() { + return COMPATIBLE_PROVIDERS; + } + + @Override + public void importNewUser( + KeycloakSession session, + RealmModel realm, + UserModel user, + IdentityProviderMapperModel mapperModel, + BrokeredIdentityContext context) { + grantOrgRole(session, realm, user, mapperModel); + } + + @Override + public void updateBrokeredUser( + KeycloakSession session, + RealmModel realm, + UserModel user, + IdentityProviderMapperModel mapperModel, + BrokeredIdentityContext context) { + grantOrgRole(session, realm, user, mapperModel); + } + + @Override + public void updateBrokeredUserLegacy( + KeycloakSession session, + RealmModel realm, + UserModel user, + IdentityProviderMapperModel mapperModel, + BrokeredIdentityContext context) {} + + @Override + public String getHelpText() { + return "When user is imported from provider, hardcode an organization role mapping for it."; + } +} diff --git a/src/main/java/io/phasetwo/service/broker/saml/mappers/AdvancedAttributeToOrgRoleMapper.java b/src/main/java/io/phasetwo/service/broker/saml/mappers/AdvancedAttributeToOrgRoleMapper.java new file mode 100644 index 00000000..36a42073 --- /dev/null +++ b/src/main/java/io/phasetwo/service/broker/saml/mappers/AdvancedAttributeToOrgRoleMapper.java @@ -0,0 +1,188 @@ +package io.phasetwo.service.broker.saml.mappers; + +import static io.phasetwo.service.broker.Mappers.*; +import static org.keycloak.utils.RegexUtils.valueMatchesRegex; + +import com.google.auto.service.AutoService; +import io.phasetwo.service.broker.OrgRoleMapper; +import io.phasetwo.service.model.OrganizationModel; +import io.phasetwo.service.model.OrganizationRoleModel; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.extern.jbosslog.JBossLog; +import org.keycloak.broker.provider.AbstractIdentityProviderMapper; +import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.broker.provider.IdentityProviderMapper; +import org.keycloak.broker.saml.SAMLEndpoint; +import org.keycloak.broker.saml.SAMLIdentityProviderFactory; +import org.keycloak.dom.saml.v2.assertion.AssertionType; +import org.keycloak.dom.saml.v2.assertion.AttributeStatementType; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.IdentityProviderSyncMode; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.ProviderConfigProperty; + +@JBossLog +@AutoService(IdentityProviderMapper.class) +public class AdvancedAttributeToOrgRoleMapper extends AbstractIdentityProviderMapper + implements OrgRoleMapper { + + public static final String PROVIDER_ID = "saml-advanced-org-role-idp-mapper"; + + public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID}; + + private static final List configProperties = new ArrayList<>(); + + static { + ProviderConfigProperty attributeMappingProperty = new ProviderConfigProperty(); + attributeMappingProperty.setName(ATTRIBUTE_PROPERTY_NAME); + attributeMappingProperty.setLabel("Attributes"); + attributeMappingProperty.setHelpText( + "Name and (regex) value of the attributes to search for in token. " + + " The configured name of an attribute is searched in SAML attribute name and attribute friendly name fields." + + " Every given attribute description must be met to set the role." + + " If the attribute is an array, then the value must be contained in the array." + + " If an attribute can be found several times, then one match is sufficient."); + attributeMappingProperty.setType(ProviderConfigProperty.MAP_TYPE); + configProperties.add(attributeMappingProperty); + + ProviderConfigProperty isAttributeRegexProperty = new ProviderConfigProperty(); + isAttributeRegexProperty.setName(ARE_ATTRIBUTE_VALUES_REGEX_PROPERTY_NAME); + isAttributeRegexProperty.setLabel("Regex Attribute Values"); + isAttributeRegexProperty.setHelpText( + "If enabled attribute values are interpreted as regular expressions."); + isAttributeRegexProperty.setType(ProviderConfigProperty.BOOLEAN_TYPE); + configProperties.add(isAttributeRegexProperty); + + OrgRoleMapper.addOrgConfigProperties(configProperties); + } + + @Override + public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) { + return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode); + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String[] getCompatibleProviders() { + return COMPATIBLE_PROVIDERS; + } + + @Override + public String getDisplayCategory() { + return "Role Importer"; + } + + @Override + public String getDisplayType() { + return "Advanced Attribute to Org Role"; + } + + @Override + public String getHelpText() { + return "If the set of attributes exists and can be matched, grant the user the specified organization role."; + } + + protected boolean applies( + final IdentityProviderMapperModel mapperModel, final BrokeredIdentityContext context) { + Map> attributes = mapperModel.getConfigMap(ATTRIBUTE_PROPERTY_NAME); + boolean areAttributeValuesRegexes = + Boolean.parseBoolean( + mapperModel + .getConfig() + .getOrDefault(ARE_ATTRIBUTE_VALUES_REGEX_PROPERTY_NAME, "false")); + + AssertionType assertion = + (AssertionType) context.getContextData().get(SAMLEndpoint.SAML_ASSERTION); + Set attributeAssertions = assertion.getAttributeStatements(); + if (attributeAssertions == null) { + return false; + } + + for (Map.Entry> entry : attributes.entrySet()) { + String attributeKey = entry.getKey(); + for (String value : entry.getValue()) { + List attributeValues = + attributeAssertions.stream() + .flatMap(statements -> statements.getAttributes().stream()) + .filter( + choiceType -> + attributeKey.equals(choiceType.getAttribute().getName()) + || attributeKey.equals(choiceType.getAttribute().getFriendlyName())) + // Several statements with same name are treated like one with several values + .flatMap(choiceType -> choiceType.getAttribute().getAttributeValue().stream()) + .collect(Collectors.toList()); + + boolean attributeValueMatch = + areAttributeValuesRegexes + ? valueMatchesRegex(value, attributeValues) + : attributeValues.contains(value); + if (!attributeValueMatch) { + return false; + } + } + } + + return true; + } + + @Override + public void importNewUser( + KeycloakSession session, + RealmModel realm, + UserModel user, + IdentityProviderMapperModel mapperModel, + BrokeredIdentityContext context) { + OrganizationModel org = getOrg(session, realm, mapperModel); + if (org == null) { + return; + } + OrganizationRoleModel role = getOrgRole(session, realm, mapperModel); + if (role == null) { + return; + } + + if (applies(mapperModel, context)) { + addUserToOrg(org, user, mapperModel); + grantOrgRole(org, role, user); + } + } + + @Override + public void updateBrokeredUser( + KeycloakSession session, + RealmModel realm, + UserModel user, + IdentityProviderMapperModel mapperModel, + BrokeredIdentityContext context) { + OrganizationModel org = getOrg(session, realm, mapperModel); + if (org == null) { + return; + } + OrganizationRoleModel role = getOrgRole(session, realm, mapperModel); + if (role == null) { + return; + } + + if (applies(mapperModel, context)) { + addUserToOrg(org, user, mapperModel); + grantOrgRole(org, role, user); + } else { + revokeOrgRole(org, role, user); + } + } +} diff --git a/src/main/java/io/phasetwo/service/importexport/KeycloakOrgsImportConverter.java b/src/main/java/io/phasetwo/service/importexport/KeycloakOrgsImportConverter.java index 20fbe091..e13af66b 100644 --- a/src/main/java/io/phasetwo/service/importexport/KeycloakOrgsImportConverter.java +++ b/src/main/java/io/phasetwo/service/importexport/KeycloakOrgsImportConverter.java @@ -1,5 +1,9 @@ package io.phasetwo.service.importexport; +import static io.phasetwo.service.Orgs.ORG_CONFIG_SHARED_IDPS_KEY; +import static io.phasetwo.service.Orgs.ORG_OWNER_CONFIG_KEY; +import static io.phasetwo.service.Orgs.ORG_SHARED_IDP_KEY; + import io.phasetwo.service.importexport.representation.OrganizationAttributes; import io.phasetwo.service.importexport.representation.OrganizationRepresentation; import io.phasetwo.service.importexport.representation.OrganizationRoleRepresentation; @@ -11,7 +15,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; - import lombok.extern.jbosslog.JBossLog; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; @@ -22,10 +25,6 @@ import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.IdentityProviderRepresentation; -import static io.phasetwo.service.Orgs.ORG_CONFIG_SHARED_IDPS_KEY; -import static io.phasetwo.service.Orgs.ORG_OWNER_CONFIG_KEY; -import static io.phasetwo.service.Orgs.ORG_SHARED_IDP_KEY; - @JBossLog public final class KeycloakOrgsImportConverter { @@ -115,15 +114,21 @@ public static void addMembers( } public static void createOrganizationIdp( - KeycloakSession session, RealmModel realm, String idpLink, OrganizationModel org, boolean skipMissingIdp) { + KeycloakSession session, + RealmModel realm, + String idpLink, + OrganizationModel org, + boolean skipMissingIdp) { if (Objects.nonNull(idpLink)) { IdentityProviderModel idp = session.identityProviders().getByAlias(idpLink); if (Objects.nonNull(idp)) { - IdentityProviderRepresentation representation = ModelToRepresentation.toRepresentation(realm, idp); - processsSharedIdps(realm, org, representation); + IdentityProviderRepresentation representation = + ModelToRepresentation.toRepresentation(realm, idp); + processsSharedIdps(realm, org, representation); - IdentityProviderModel updated = RepresentationToModel.toModel(realm, representation, session); - session.identityProviders().update(updated); + IdentityProviderModel updated = + RepresentationToModel.toModel(realm, representation, session); + session.identityProviders().update(updated); } else { if (skipMissingIdp) { log.debug( @@ -137,19 +142,20 @@ public static void createOrganizationIdp( } } - private static void processsSharedIdps(RealmModel realm, OrganizationModel org, IdentityProviderRepresentation representation) { - var isSharedIdpsConfigEnabled = realm.getAttribute(ORG_CONFIG_SHARED_IDPS_KEY, false); + private static void processsSharedIdps( + RealmModel realm, OrganizationModel org, IdentityProviderRepresentation representation) { + var isSharedIdpsConfigEnabled = realm.getAttribute(ORG_CONFIG_SHARED_IDPS_KEY, false); - if (isSharedIdpsConfigEnabled) { - IdentityProviders.addMultiOrganization(org, representation); - } else { - representation.getConfig().put(ORG_SHARED_IDP_KEY, "false"); - IdentityProviders.setAttributeMultivalued( - representation.getConfig(), ORG_OWNER_CONFIG_KEY, Set.of(org.getId())); - } + if (isSharedIdpsConfigEnabled) { + IdentityProviders.addMultiOrganization(org, representation); + } else { + representation.getConfig().put(ORG_SHARED_IDP_KEY, "false"); + IdentityProviders.setAttributeMultivalued( + representation.getConfig(), ORG_OWNER_CONFIG_KEY, Set.of(org.getId())); } + } - public static void createOrganizationRoles( + public static void createOrganizationRoles( List roles, OrganizationModel org) { roles.stream() .filter( diff --git a/src/main/java/io/phasetwo/service/model/OrganizationProvider.java b/src/main/java/io/phasetwo/service/model/OrganizationProvider.java index 0d578382..645a02c3 100644 --- a/src/main/java/io/phasetwo/service/model/OrganizationProvider.java +++ b/src/main/java/io/phasetwo/service/model/OrganizationProvider.java @@ -16,6 +16,8 @@ OrganizationModel createOrganization( OrganizationModel getOrganizationById(RealmModel realm, String id); + OrganizationModel getOrganizationByName(RealmModel realm, String name); + Stream getOrganizationsStreamForDomain( RealmModel realm, String domain, boolean verified); diff --git a/src/main/java/io/phasetwo/service/model/jpa/JpaOrganizationProvider.java b/src/main/java/io/phasetwo/service/model/jpa/JpaOrganizationProvider.java index b543fdca..67eb6ab7 100644 --- a/src/main/java/io/phasetwo/service/model/jpa/JpaOrganizationProvider.java +++ b/src/main/java/io/phasetwo/service/model/jpa/JpaOrganizationProvider.java @@ -78,6 +78,20 @@ public OrganizationModel getOrganizationById(RealmModel realm, String id) { } } + @Override + public OrganizationModel getOrganizationByName(RealmModel realm, String name) { + TypedQuery query = + em.createNamedQuery("getOrganizationsByRealmIdAndNameExact", ExtOrganizationEntity.class); + query.setParameter("realmId", realm.getId()); + query.setParameter("name", name); + try { + ExtOrganizationEntity org = query.getSingleResult(); + return new OrganizationAdapter(session, realm, em, org); + } catch (Exception e) { + return null; + } + } + @Override public Stream getOrganizationsStreamForDomain( RealmModel realm, String domain, boolean verified) { diff --git a/src/main/java/io/phasetwo/service/model/jpa/OrganizationAdapter.java b/src/main/java/io/phasetwo/service/model/jpa/OrganizationAdapter.java index 767e546b..7cdf7dc1 100644 --- a/src/main/java/io/phasetwo/service/model/jpa/OrganizationAdapter.java +++ b/src/main/java/io/phasetwo/service/model/jpa/OrganizationAdapter.java @@ -319,7 +319,8 @@ public OrganizationRoleModel addRole(String name) { @Override public Stream getIdentityProvidersStream() { - return session.identityProviders() + return session + .identityProviders() .getAllStream() // Todo: do we need to apply here a role filter? I believe not since its part of the // HomeIdpDiscoverer diff --git a/src/main/java/io/phasetwo/service/model/jpa/entity/ExtOrganizationEntity.java b/src/main/java/io/phasetwo/service/model/jpa/entity/ExtOrganizationEntity.java index 3c480856..174f2cc3 100644 --- a/src/main/java/io/phasetwo/service/model/jpa/entity/ExtOrganizationEntity.java +++ b/src/main/java/io/phasetwo/service/model/jpa/entity/ExtOrganizationEntity.java @@ -15,6 +15,10 @@ @NamedQuery( name = "getOrganizationsByRealmId", query = "SELECT o FROM ExtOrganizationEntity o WHERE o.realmId = :realmId"), + @NamedQuery( + name = "getOrganizationsByRealmIdAndNameExact", + query = + "SELECT o FROM ExtOrganizationEntity o WHERE o.realmId = :realmId AND o.name = :name"), @NamedQuery( name = "getOrganizationsByRealmIdAndName", query = diff --git a/src/main/java/io/phasetwo/service/resource/IdentityProvidersResource.java b/src/main/java/io/phasetwo/service/resource/IdentityProvidersResource.java index 98374ae8..605cbd72 100644 --- a/src/main/java/io/phasetwo/service/resource/IdentityProvidersResource.java +++ b/src/main/java/io/phasetwo/service/resource/IdentityProvidersResource.java @@ -58,7 +58,8 @@ public IdentityProviderResource identityProvider(@PathParam("alias") String alia @GET @Produces(MediaType.APPLICATION_JSON) public Stream getIdentityProviders() { - return session.identityProviders() + return session + .identityProviders() .getAllStream() .filter(provider -> canViewIdp()) .filter(this::idpInOrg) @@ -106,7 +107,8 @@ private void deactivateOtherIdps( boolean disable, String orgId) { if (representation.isEnabled()) { - session.identityProviders() + session + .identityProviders() .getAllStream() .filter(this::idpInOrg) .forEach( diff --git a/src/main/java/io/phasetwo/service/resource/OrganizationResourceProviderFactory.java b/src/main/java/io/phasetwo/service/resource/OrganizationResourceProviderFactory.java index 17940304..5f8f4352 100644 --- a/src/main/java/io/phasetwo/service/resource/OrganizationResourceProviderFactory.java +++ b/src/main/java/io/phasetwo/service/resource/OrganizationResourceProviderFactory.java @@ -216,10 +216,11 @@ private void organizationRemoved(OrganizationModel.OrganizationRemovedEvent even OrganizationModel org = event.getOrganization(); try { org.getIdentityProvidersStream() - .forEach(idp -> { - IdentityProviders.removeOrganization(org.getId(), idp); - event.getKeycloakSession().identityProviders().update(idp); - }); + .forEach( + idp -> { + IdentityProviders.removeOrganization(org.getId(), idp); + event.getKeycloakSession().identityProviders().update(idp); + }); } catch (Exception e) { log.warnf( "Couldn't remove identity providers on organizationRemoved. Likely because this follows a realmRemoved event. %s", diff --git a/src/main/java/io/phasetwo/service/resource/OrganizationsResource.java b/src/main/java/io/phasetwo/service/resource/OrganizationsResource.java index d0c0ac0f..377e9585 100644 --- a/src/main/java/io/phasetwo/service/resource/OrganizationsResource.java +++ b/src/main/java/io/phasetwo/service/resource/OrganizationsResource.java @@ -150,8 +150,7 @@ public Response createOrg(@Valid Organization body) { orgs.createOrganization(realm, body.getName(), auth.getUser(), auth.hasCreateOrg()); org.setDisplayName(body.getDisplayName()); org.setUrl(body.getUrl()); - if (body.getAttributes() != null) - body.getAttributes().forEach(org::setAttribute); + if (body.getAttributes() != null) body.getAttributes().forEach(org::setAttribute); if (body.getDomains() != null) org.setDomains(body.getDomains()); Organization o = convertOrganizationModelToOrganization(org); @@ -188,7 +187,9 @@ public Response addOrganizationsConfig(@Valid OrganizationsConfig body) { private void resetIdentityProviders(boolean newSharedIdpConfig) { var existingSharedIdpConfig = realm.getAttribute(ORG_CONFIG_SHARED_IDPS_KEY, false); if (existingSharedIdpConfig && !newSharedIdpConfig) { - session.identityProviders().getAllStream() + session + .identityProviders() + .getAllStream() .forEach( identityProviderModel -> { identityProviderModel.getConfig().put(ORG_SHARED_IDP_KEY, "false"); @@ -314,7 +315,8 @@ private void createOrganization( KeycloakOrgsImportConverter.createOrganizationRoles( organizationRepresentation.getRoles(), org); - KeycloakOrgsImportConverter.createOrganizationIdp(session, realm, organizationRepresentation.getIdpLink(), org, skipMissingIdp); + KeycloakOrgsImportConverter.createOrganizationIdp( + session, realm, organizationRepresentation.getIdpLink(), org, skipMissingIdp); KeycloakOrgsImportConverter.addMembers( session, realm, organizationRepresentation, org, skipMissingMember); diff --git a/src/test/java/io/phasetwo/service/AbstractCypressOrganizationTest.java b/src/test/java/io/phasetwo/service/AbstractCypressOrganizationTest.java index bf261b34..a4eee84b 100644 --- a/src/test/java/io/phasetwo/service/AbstractCypressOrganizationTest.java +++ b/src/test/java/io/phasetwo/service/AbstractCypressOrganizationTest.java @@ -41,8 +41,8 @@ public class AbstractCypressOrganizationTest { Boolean.parseBoolean(System.getProperty("include.cypress", "false")); public static final String KEYCLOAK_IMAGE = - String.format( - "quay.io/phasetwo/keycloak-crdb:%s", System.getProperty("keycloak-version", "26.0.2")); + String.format( + "quay.io/phasetwo/keycloak-crdb:%s", System.getProperty("keycloak-version", "26.0.2")); public static final String REALM = "master"; public static final String ADMIN_CLI = "admin-cli"; diff --git a/src/test/java/io/phasetwo/service/AbstractOrganizationTest.java b/src/test/java/io/phasetwo/service/AbstractOrganizationTest.java index 373bbbea..cac53378 100644 --- a/src/test/java/io/phasetwo/service/AbstractOrganizationTest.java +++ b/src/test/java/io/phasetwo/service/AbstractOrganizationTest.java @@ -54,7 +54,7 @@ public abstract class AbstractOrganizationTest { public static final String KEYCLOAK_IMAGE = - String.format( + String.format( "quay.io/phasetwo/keycloak-crdb:%s", System.getProperty("keycloak-version", "26.0.2")); public static final String REALM = "master"; public static final String ADMIN_CLI = "admin-cli"; @@ -928,6 +928,6 @@ protected ClientRepresentation createPublicClientInRealm(RealmResource realm, St client.setFullScopeAllowed(false); realm.clients().create(client).close(); - return realm.clients().findByClientId(client.getClientId()).getFirst(); + return realm.clients().findByClientId(client.getClientId()).getFirst(); } } diff --git a/src/test/java/io/phasetwo/service/importexport/OrganizationIdpExportTest.java b/src/test/java/io/phasetwo/service/importexport/OrganizationIdpExportTest.java index 40a8fb1f..f8d03edf 100644 --- a/src/test/java/io/phasetwo/service/importexport/OrganizationIdpExportTest.java +++ b/src/test/java/io/phasetwo/service/importexport/OrganizationIdpExportTest.java @@ -3,17 +3,15 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertTrue; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.collect.ImmutableMap; import io.phasetwo.client.openapi.model.OrganizationRepresentation; import io.phasetwo.service.AbstractOrganizationTest; import io.phasetwo.service.representation.LinkIdp; +import io.phasetwo.service.representation.OrganizationsConfig; import java.io.IOException; import java.util.List; - -import io.phasetwo.service.representation.OrganizationsConfig; import lombok.extern.jbosslog.JBossLog; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -85,43 +83,43 @@ void testOrganizationLinkIdp() throws IOException { void testOrganizationChangeLink() throws IOException { // create organization var organization1 = - createOrganization( - new OrganizationRepresentation() - .name("example-org") - .domains(List.of("example.com", "test.org"))); + createOrganization( + new OrganizationRepresentation() + .name("example-org") + .domains(List.of("example.com", "test.org"))); // create organization var organization2 = - createOrganization( - new OrganizationRepresentation() - .name("example-org2") - .domains(List.of("example2.com", "test2.org"))); + createOrganization( + new OrganizationRepresentation() + .name("example-org2") + .domains(List.of("example2.com", "test2.org"))); // create identity provider String alias1 = "linking-provider-1"; org.keycloak.representations.idm.IdentityProviderRepresentation idp = - new org.keycloak.representations.idm.IdentityProviderRepresentation(); + new org.keycloak.representations.idm.IdentityProviderRepresentation(); idp.setAlias(alias1); idp.setProviderId("oidc"); idp.setEnabled(true); idp.setFirstBrokerLoginFlowAlias("first broker login"); idp.setConfig( - new ImmutableMap.Builder() - .put("useJwksUrl", "true") - .put("syncMode", "FORCE") - .put("authorizationUrl", "https://foo.com") - .put("hideOnLoginPage", "") - .put("loginHint", "") - .put("uiLocales", "") - .put("backchannelSupported", "") - .put("disableUserInfo", "") - .put("acceptsPromptNoneForwardFromClient", "") - .put("validateSignature", "") - .put("pkceEnabled", "") - .put("tokenUrl", "https://foo.com") - .put("clientAuthMethod", "client_secret_post") - .put("clientId", "aabbcc") - .put("clientSecret", "112233") - .build()); + new ImmutableMap.Builder() + .put("useJwksUrl", "true") + .put("syncMode", "FORCE") + .put("authorizationUrl", "https://foo.com") + .put("hideOnLoginPage", "") + .put("loginHint", "") + .put("uiLocales", "") + .put("backchannelSupported", "") + .put("disableUserInfo", "") + .put("acceptsPromptNoneForwardFromClient", "") + .put("validateSignature", "") + .put("pkceEnabled", "") + .put("tokenUrl", "https://foo.com") + .put("clientAuthMethod", "client_secret_post") + .put("clientId", "aabbcc") + .put("clientSecret", "112233") + .build()); keycloak.realm(REALM).identityProviders().create(idp); // link org1 @@ -130,8 +128,8 @@ void testOrganizationChangeLink() throws IOException { link1.setSyncMode("IMPORT"); var responseOrg1Link = postRequest(link1, organization1.getId(), "idps", "link"); assertThat( - responseOrg1Link.getStatusCode(), - is(jakarta.ws.rs.core.Response.Status.CREATED.getStatusCode())); + responseOrg1Link.getStatusCode(), + is(jakarta.ws.rs.core.Response.Status.CREATED.getStatusCode())); // link org2 var link2 = new LinkIdp(); @@ -139,8 +137,8 @@ void testOrganizationChangeLink() throws IOException { link2.setSyncMode("IMPORT"); var responseOrg2Link = postRequest(link2, organization2.getId(), "idps", "link"); assertThat( - responseOrg2Link.getStatusCode(), - is(jakarta.ws.rs.core.Response.Status.CREATED.getStatusCode())); + responseOrg2Link.getStatusCode(), + is(jakarta.ws.rs.core.Response.Status.CREATED.getStatusCode())); // export var export = exportOrgs(keycloak, true); @@ -148,15 +146,15 @@ void testOrganizationChangeLink() throws IOException { // validate org1 export.getOrganizations().stream() - .filter(exportOrg -> exportOrg.getOrganization().getName().equals(organization1.getName())) - .findFirst() - .ifPresent(exportOrg -> Assertions.assertNull(exportOrg.getIdpLink())); + .filter(exportOrg -> exportOrg.getOrganization().getName().equals(organization1.getName())) + .findFirst() + .ifPresent(exportOrg -> Assertions.assertNull(exportOrg.getIdpLink())); // validate org2 export.getOrganizations().stream() - .filter(exportOrg -> exportOrg.getOrganization().getName().equals(organization2.getName())) - .findFirst() - .ifPresent(exportOrg -> assertThat(exportOrg.getIdpLink(), is(alias1))); + .filter(exportOrg -> exportOrg.getOrganization().getName().equals(organization2.getName())) + .findFirst() + .ifPresent(exportOrg -> assertThat(exportOrg.getIdpLink(), is(alias1))); // delete org1 deleteOrganization(organization1.getId()); @@ -176,8 +174,8 @@ public void beforeEach() throws JsonProcessingException { orgConfig.setSharedIdps(false); var responseOrgsConfig = putRequest(orgConfig, url); assertThat( - responseOrgsConfig.getStatusCode(), - is(jakarta.ws.rs.core.Response.Status.OK.getStatusCode())); + responseOrgsConfig.getStatusCode(), + is(jakarta.ws.rs.core.Response.Status.OK.getStatusCode())); } @AfterEach @@ -188,7 +186,7 @@ public void afterEach() throws JsonProcessingException { orgConfig.setSharedIdps(false); var responseOrgsConfig = putRequest(orgConfig, url); assertThat( - responseOrgsConfig.getStatusCode(), - is(jakarta.ws.rs.core.Response.Status.OK.getStatusCode())); + responseOrgsConfig.getStatusCode(), + is(jakarta.ws.rs.core.Response.Status.OK.getStatusCode())); } } diff --git a/src/test/java/io/phasetwo/service/importexport/OrganizationSharedIdpExportTest.java b/src/test/java/io/phasetwo/service/importexport/OrganizationSharedIdpExportTest.java index db97c615..7251e2c1 100644 --- a/src/test/java/io/phasetwo/service/importexport/OrganizationSharedIdpExportTest.java +++ b/src/test/java/io/phasetwo/service/importexport/OrganizationSharedIdpExportTest.java @@ -1,23 +1,22 @@ package io.phasetwo.service.importexport; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; + import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.collect.ImmutableMap; import io.phasetwo.client.openapi.model.OrganizationRepresentation; import io.phasetwo.service.AbstractOrganizationTest; import io.phasetwo.service.representation.LinkIdp; import io.phasetwo.service.representation.OrganizationsConfig; +import java.io.IOException; +import java.util.List; import lombok.extern.jbosslog.JBossLog; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.IOException; -import java.util.List; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; - @JBossLog public class OrganizationSharedIdpExportTest extends AbstractOrganizationTest { @@ -25,43 +24,43 @@ public class OrganizationSharedIdpExportTest extends AbstractOrganizationTest { void testMultiOrganizationLink() throws IOException { // create organization var organization1 = - createOrganization( - new OrganizationRepresentation() - .name("example-org") - .domains(List.of("example.com", "test.org"))); + createOrganization( + new OrganizationRepresentation() + .name("example-org") + .domains(List.of("example.com", "test.org"))); // create organization var organization2 = - createOrganization( - new OrganizationRepresentation() - .name("example-org2") - .domains(List.of("example2.com", "test2.org"))); + createOrganization( + new OrganizationRepresentation() + .name("example-org2") + .domains(List.of("example2.com", "test2.org"))); // create identity provider String alias1 = "linking-provider-1"; org.keycloak.representations.idm.IdentityProviderRepresentation idp = - new org.keycloak.representations.idm.IdentityProviderRepresentation(); + new org.keycloak.representations.idm.IdentityProviderRepresentation(); idp.setAlias(alias1); idp.setProviderId("oidc"); idp.setEnabled(true); idp.setFirstBrokerLoginFlowAlias("first broker login"); idp.setConfig( - new ImmutableMap.Builder() - .put("useJwksUrl", "true") - .put("syncMode", "FORCE") - .put("authorizationUrl", "https://foo.com") - .put("hideOnLoginPage", "") - .put("loginHint", "") - .put("uiLocales", "") - .put("backchannelSupported", "") - .put("disableUserInfo", "") - .put("acceptsPromptNoneForwardFromClient", "") - .put("validateSignature", "") - .put("pkceEnabled", "") - .put("tokenUrl", "https://foo.com") - .put("clientAuthMethod", "client_secret_post") - .put("clientId", "aabbcc") - .put("clientSecret", "112233") - .build()); + new ImmutableMap.Builder() + .put("useJwksUrl", "true") + .put("syncMode", "FORCE") + .put("authorizationUrl", "https://foo.com") + .put("hideOnLoginPage", "") + .put("loginHint", "") + .put("uiLocales", "") + .put("backchannelSupported", "") + .put("disableUserInfo", "") + .put("acceptsPromptNoneForwardFromClient", "") + .put("validateSignature", "") + .put("pkceEnabled", "") + .put("tokenUrl", "https://foo.com") + .put("clientAuthMethod", "client_secret_post") + .put("clientId", "aabbcc") + .put("clientSecret", "112233") + .build()); keycloak.realm(REALM).identityProviders().create(idp); // link org1 @@ -70,8 +69,8 @@ void testMultiOrganizationLink() throws IOException { link1.setSyncMode("IMPORT"); var responseOrg1Link = postRequest(link1, organization1.getId(), "idps", "link"); assertThat( - responseOrg1Link.getStatusCode(), - is(jakarta.ws.rs.core.Response.Status.CREATED.getStatusCode())); + responseOrg1Link.getStatusCode(), + is(jakarta.ws.rs.core.Response.Status.CREATED.getStatusCode())); // link org2 var link2 = new LinkIdp(); @@ -79,8 +78,8 @@ void testMultiOrganizationLink() throws IOException { link2.setSyncMode("IMPORT"); var responseOrg2Link = postRequest(link2, organization2.getId(), "idps", "link"); assertThat( - responseOrg2Link.getStatusCode(), - is(jakarta.ws.rs.core.Response.Status.CREATED.getStatusCode())); + responseOrg2Link.getStatusCode(), + is(jakarta.ws.rs.core.Response.Status.CREATED.getStatusCode())); // export var export = exportOrgs(keycloak, true); @@ -88,15 +87,15 @@ void testMultiOrganizationLink() throws IOException { // validate org1 export.getOrganizations().stream() - .filter(exportOrg -> exportOrg.getOrganization().getName().equals(organization1.getName())) - .findFirst() - .ifPresent(exportOrg -> assertThat(exportOrg.getIdpLink(), is(alias1))); + .filter(exportOrg -> exportOrg.getOrganization().getName().equals(organization1.getName())) + .findFirst() + .ifPresent(exportOrg -> assertThat(exportOrg.getIdpLink(), is(alias1))); // validate org2 export.getOrganizations().stream() - .filter(exportOrg -> exportOrg.getOrganization().getName().equals(organization2.getName())) - .findFirst() - .ifPresent(exportOrg -> assertThat(exportOrg.getIdpLink(), is(alias1))); + .filter(exportOrg -> exportOrg.getOrganization().getName().equals(organization2.getName())) + .findFirst() + .ifPresent(exportOrg -> assertThat(exportOrg.getIdpLink(), is(alias1))); // delete org1 deleteOrganization(organization1.getId()); @@ -116,8 +115,8 @@ public void beforeEach() throws JsonProcessingException { orgConfig.setSharedIdps(true); var responseOrgsConfig = putRequest(orgConfig, url); assertThat( - responseOrgsConfig.getStatusCode(), - is(jakarta.ws.rs.core.Response.Status.OK.getStatusCode())); + responseOrgsConfig.getStatusCode(), + is(jakarta.ws.rs.core.Response.Status.OK.getStatusCode())); } @AfterEach @@ -128,7 +127,7 @@ public void afterEach() throws JsonProcessingException { orgConfig.setSharedIdps(true); var responseOrgsConfig = putRequest(orgConfig, url); assertThat( - responseOrgsConfig.getStatusCode(), - is(jakarta.ws.rs.core.Response.Status.OK.getStatusCode())); + responseOrgsConfig.getStatusCode(), + is(jakarta.ws.rs.core.Response.Status.OK.getStatusCode())); } }