From 73191202f2e39bf040749b69a31d01bdfbcec8eb Mon Sep 17 00:00:00 2001 From: shekhar16 Date: Wed, 11 Oct 2023 11:26:59 +0530 Subject: [PATCH] feat: keycloak refactoring referred to Issue #5330 review (#6157) * feat(jans-keycloak-link): setup changes for keycloak migration review #5330 * chore: use only annotated beans * chore: use only annotated beans * chore: add fake beans producers * chore: resolve dependency issues * chore: reuse shared configuration in link and keycloack * refactor(jans-keycloak-link): refactor the code to increase reusability * refactor(jans-keycloak-link): refactor the code to increase reusability * refactor(jans-keycloak-link): refactor the code to increase reusability * Delete jans-config-api/docs/jans-config-api-swagger.yaml * chore: update swagger docs files * Revert "Delete jans-config-api/docs/jans-config-api-swagger.yaml" This reverts commit e424397b73ea1879a1d34c04a66f2bb83c1d97c8. * refactor(jans-keycloak-link): refactor the pom version --------- Co-authored-by: Yuriy Movchan --- .../docs/jans-config-api-swagger.yaml | 8 +- jans-keycloak-link/model/pom.xml | 19 + .../keycloak/link/model/CacheCompoundKey.java | 91 ++ .../jans/keycloak/link/model/JansInumMap.java | 126 ++ .../link/model/config/AppConfiguration.java | 173 +++ .../model/config/BaseDnConfiguration.java | 76 + .../config/CacheRefreshAttributeMapping.java | 22 + .../config/CacheRefreshConfiguration.java | 29 + .../jans/keycloak/link/model/config/Conf.java | 76 + .../link/model/config/Configuration.java | 16 + .../model/config/KeycloakConfiguration.java | 87 ++ .../model/config/StaticConfiguration.java | 36 + .../resources/META-INF/maven/archetype.xml | 9 + .../resources/archetype-resources/pom.xml | 15 + .../src/main/java/App.java | 13 + .../src/test/java/AppTest.java | 38 + jans-keycloak-link/pom.xml | 110 ++ jans-keycloak-link/server/pom.xml | 100 ++ .../link/server/service/AppInitializer.java | 246 +++ .../link/server/service/AttributeService.java | 31 + .../CacheRefreshConfigurationService.java | 92 ++ .../server/service/CustomScriptService.java | 29 + .../link/server/service/KeycloakService.java | 73 + .../link/server/service/LoggerService.java | 49 + .../server/service/OrganizationService.java | 27 + .../service/SystemResteasyInitializer.java | 33 + .../service/config/ApplicationFactory.java | 60 + .../service/config/ConfigurationFactory.java | 362 +++++ .../link/timer/JansKeycloakLinkTimer.java | 937 +++++++++++ .../link/ws/rs/HealthCheckController.java | 42 + .../src/main/resources/META-INF/beans.xml | 6 + .../server/src/main/resources/log4j2.xml | 114 ++ .../src/main/resources/quartz.properties | 4 + .../src/main/webapp/WEB-INF/jetty-env.xml | 15 + .../src/main/webapp/WEB-INF/jetty-web.xml | 29 + .../server/src/main/webapp/WEB-INF/web.xml | 43 + .../server/src/main/webapp/index.jsp | 5 + jans-keycloak-link/service/pom.xml | 38 + .../link/service/CacheRefreshService.java | 191 +++ .../service/CacheRefreshUpdateMethod.java | 64 + .../keycloak/link/service/IPersonService.java | 27 + .../keycloak/link/service/PersonService.java | 130 ++ .../cdi/event/StatusCheckerTimerEvent.java | 14 + .../src/main/resources/META-INF/beans.xml | 6 + .../src/test/java/io/jans/AppTest.java | 38 + .../io/jans/link/constants/JansConstants.java | 1 + .../jans/link/event}/CacheRefreshEvent.java | 2 +- .../config/CacheRefreshAttributeMapping.java | 36 +- .../config/CacheRefreshConfiguration.java | 150 +- .../jans/link/model/config/Configuration.java | 2 +- .../model/config/shared/BaseJansInumMap.java | 58 + .../shared/CacheRefreshAttributeMapping.java | 54 + .../shared/CacheRefreshConfiguration.java | 167 ++ .../model/config/shared/Configuration.java | 16 + .../{ => server}/service/AppInitializer.java | 9 +- .../link/server/service/AttributeService.java | 31 + .../CacheRefrshConfigurationService.java | 2 +- .../server/service/CustomScriptService.java | 29 + .../link/server}/service/LoggerService.java | 2 +- .../server/service/OrganizationService.java | 27 + .../service/SystemResteasyInitializer.java | 2 +- .../service/config/ApplicationFactory.java | 0 .../service/config/ConfigurationFactory.java | 0 .../io/jans/link/timer/CacheRefreshTimer.java | 1376 ----------------- .../io/jans/link/timer/JansLinkTimer.java | 834 ++++++++++ .../src/main/resources/META-INF/beans.xml | 2 +- .../java/io/jans/link/test/LdapSample.java | 4 +- .../jans/link/service/AttributeService.java | 30 +- .../jans/link/service/BaseJansLinkTimer.java | 715 +++++++++ .../CacheRefreshSnapshotFileService.java | 2 +- .../link/service/OrganizationService.java | 22 +- .../jans/link/service/PairwiseIdService.java | 3 - .../io/jans/link/service/PersonService.java | 1 - .../service/custom/CustomScriptService.java | 18 +- .../cdi/event/StatusCheckerTimerEvent.java | 1 - .../src/main/resources/META-INF/beans.xml | 2 +- 76 files changed, 5718 insertions(+), 1629 deletions(-) create mode 100644 jans-keycloak-link/model/pom.xml create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/CacheCompoundKey.java create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/JansInumMap.java create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/AppConfiguration.java create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/BaseDnConfiguration.java create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/CacheRefreshAttributeMapping.java create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/CacheRefreshConfiguration.java create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/Conf.java create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/Configuration.java create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/KeycloakConfiguration.java create mode 100644 jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/StaticConfiguration.java create mode 100644 jans-keycloak-link/model/src/main/resources/META-INF/maven/archetype.xml create mode 100644 jans-keycloak-link/model/src/main/resources/archetype-resources/pom.xml create mode 100644 jans-keycloak-link/model/src/main/resources/archetype-resources/src/main/java/App.java create mode 100644 jans-keycloak-link/model/src/main/resources/archetype-resources/src/test/java/AppTest.java create mode 100644 jans-keycloak-link/pom.xml create mode 100644 jans-keycloak-link/server/pom.xml create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/AppInitializer.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/AttributeService.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/CacheRefreshConfigurationService.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/CustomScriptService.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/KeycloakService.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/LoggerService.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/OrganizationService.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/SystemResteasyInitializer.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/service/config/ApplicationFactory.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/service/config/ConfigurationFactory.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/timer/JansKeycloakLinkTimer.java create mode 100644 jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/ws/rs/HealthCheckController.java create mode 100644 jans-keycloak-link/server/src/main/resources/META-INF/beans.xml create mode 100644 jans-keycloak-link/server/src/main/resources/log4j2.xml create mode 100644 jans-keycloak-link/server/src/main/resources/quartz.properties create mode 100644 jans-keycloak-link/server/src/main/webapp/WEB-INF/jetty-env.xml create mode 100644 jans-keycloak-link/server/src/main/webapp/WEB-INF/jetty-web.xml create mode 100644 jans-keycloak-link/server/src/main/webapp/WEB-INF/web.xml create mode 100644 jans-keycloak-link/server/src/main/webapp/index.jsp create mode 100644 jans-keycloak-link/service/pom.xml create mode 100644 jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/CacheRefreshService.java create mode 100644 jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/CacheRefreshUpdateMethod.java create mode 100644 jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/IPersonService.java create mode 100644 jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/PersonService.java create mode 100644 jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/status/cdi/event/StatusCheckerTimerEvent.java create mode 100644 jans-keycloak-link/service/src/main/resources/META-INF/beans.xml create mode 100644 jans-keycloak-link/service/src/test/java/io/jans/AppTest.java rename jans-link/{server/src/main/java/io/jans/link => model/src/main/java/io/jans/link/event}/CacheRefreshEvent.java (56%) create mode 100644 jans-link/model/src/main/java/io/jans/link/model/config/shared/BaseJansInumMap.java create mode 100644 jans-link/model/src/main/java/io/jans/link/model/config/shared/CacheRefreshAttributeMapping.java create mode 100644 jans-link/model/src/main/java/io/jans/link/model/config/shared/CacheRefreshConfiguration.java create mode 100644 jans-link/model/src/main/java/io/jans/link/model/config/shared/Configuration.java rename jans-link/server/src/main/java/io/jans/link/{ => server}/service/AppInitializer.java (97%) create mode 100644 jans-link/server/src/main/java/io/jans/link/server/service/AttributeService.java rename jans-link/{service/src/main/java/io/jans/link => server/src/main/java/io/jans/link/server}/service/CacheRefrshConfigurationService.java (98%) create mode 100644 jans-link/server/src/main/java/io/jans/link/server/service/CustomScriptService.java rename jans-link/{service/src/main/java/io/jans/link => server/src/main/java/io/jans/link/server}/service/LoggerService.java (96%) create mode 100644 jans-link/server/src/main/java/io/jans/link/server/service/OrganizationService.java rename jans-link/server/src/main/java/io/jans/link/{ => server}/service/SystemResteasyInitializer.java (95%) rename jans-link/{service => server}/src/main/java/io/jans/link/service/config/ApplicationFactory.java (100%) rename jans-link/{service => server}/src/main/java/io/jans/link/service/config/ConfigurationFactory.java (100%) delete mode 100644 jans-link/server/src/main/java/io/jans/link/timer/CacheRefreshTimer.java create mode 100644 jans-link/server/src/main/java/io/jans/link/timer/JansLinkTimer.java create mode 100644 jans-link/service/src/main/java/io/jans/link/service/BaseJansLinkTimer.java diff --git a/jans-config-api/docs/jans-config-api-swagger.yaml b/jans-config-api/docs/jans-config-api-swagger.yaml index 9ae1647f5db..626b1890fde 100644 --- a/jans-config-api/docs/jans-config-api-swagger.yaml +++ b/jans-config-api/docs/jans-config-api-swagger.yaml @@ -7460,15 +7460,13 @@ components: type: string adminCanView: type: boolean - adminCanEdit: + userCanView: type: boolean userCanAccess: type: boolean - userCanView: - type: boolean - whitePagesCanView: + adminCanEdit: type: boolean - userCanEdit: + adminCanView: type: boolean adminCanAccess: type: boolean diff --git a/jans-keycloak-link/model/pom.xml b/jans-keycloak-link/model/pom.xml new file mode 100644 index 00000000000..f31368596d9 --- /dev/null +++ b/jans-keycloak-link/model/pom.xml @@ -0,0 +1,19 @@ + + + jans-keycloak-link-parent + io.jans + 1.0.19-SNAPSHOT + + 4.0.0 + jans-keycloak-link-model + jans-keycloak-link-model + http://maven.apache.org + + + io.jans + jans-link-model + ${project.version} + + + diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/CacheCompoundKey.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/CacheCompoundKey.java new file mode 100644 index 00000000000..e74bee095d4 --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/CacheCompoundKey.java @@ -0,0 +1,91 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.keycloak.link.model; + +import java.io.Serializable; + +/** + * Compound key with String[] array + * + * @author Yuriy Movchan Date: 07.21.2011 + */ +public class CacheCompoundKey implements Serializable { + + private static final long serialVersionUID = -3366537601347036591L; + + private String primaryKeyValues; + private String secondaryKeyValues; + private String tertiaryKeyValues; + + public CacheCompoundKey(String primaryKeyValues, String secondaryKeyValues, String tertiaryKeyValues) { + this.primaryKeyValues = primaryKeyValues; + this.secondaryKeyValues = secondaryKeyValues; + this.tertiaryKeyValues = tertiaryKeyValues; + } + + public CacheCompoundKey(String[] keyValues) { + if (keyValues.length > 0) { + primaryKeyValues = keyValues[0]; + } + if (keyValues.length > 1) { + secondaryKeyValues = keyValues[1]; + } + if (keyValues.length > 2) { + tertiaryKeyValues = keyValues[2]; + } + } + + public String getPrimaryKeyValues() { + return primaryKeyValues; + } + + public String getSecondaryKeyValues() { + return secondaryKeyValues; + } + + public String getTertiaryKeyValues() { + return tertiaryKeyValues; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (null == primaryKeyValues ? 0 : primaryKeyValues.hashCode()); + result = prime * result + (null == secondaryKeyValues ? 0 : secondaryKeyValues.hashCode()); + result = prime * result + (null == tertiaryKeyValues? 0 : tertiaryKeyValues.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CacheCompoundKey other = (CacheCompoundKey) obj; + if (null != primaryKeyValues && !primaryKeyValues.equalsIgnoreCase(other.primaryKeyValues)) + return false; + if (null != secondaryKeyValues && !secondaryKeyValues.equalsIgnoreCase(other.secondaryKeyValues)) + return false; + if (null != tertiaryKeyValues && !tertiaryKeyValues.equalsIgnoreCase(other.tertiaryKeyValues)) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("CacheCompoundKey [primaryKeyValues=").append(primaryKeyValues).append(", secondaryKeyValues=") + .append(secondaryKeyValues).append(", tertiaryKeyValues=").append(tertiaryKeyValues) + .append("]"); + return builder.toString(); + } + +} diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/JansInumMap.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/JansInumMap.java new file mode 100644 index 00000000000..21634d97a40 --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/JansInumMap.java @@ -0,0 +1,126 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.keycloak.link.model; + +import io.jans.model.GluuStatus; +import io.jans.orm.annotation.AttributeName; +import io.jans.orm.annotation.DataEntry; +import io.jans.orm.annotation.ObjectClass; +import io.jans.orm.model.base.Entry; + +import java.io.Serializable; + +/** + * GluuInumMap + * + * @author Yuriy Movchan Date: 07.13.2011 + */ +@DataEntry(sortBy = { "inum" }) +@ObjectClass(value = "jansInumMap") +public class JansInumMap extends Entry implements Serializable { + + private static final long serialVersionUID = -2190480357430436503L; + + @AttributeName(ignoreDuringUpdate = true) + private String inum; + + @AttributeName(name = "jansPrimaryKeyAttrName") + private String primaryKeyAttrName; + + @AttributeName(name = "jansPrimaryKeyValue") + private String primaryKeyValues; + + @AttributeName(name = "jansSecondaryKeyAttrName") + private String secondaryKeyAttrName; + + @AttributeName(name = "jansSecondaryKeyValue") + private String secondaryKeyValues; + + @AttributeName(name = "jansTertiaryKeyAttrName") + private String tertiaryKeyAttrName; + + @AttributeName(name = "tertiaryKeyValue") + private String tertiaryKeyValues; + + @AttributeName(name = "jansStatus") + private GluuStatus status; + + public String getInum() { + return inum; + } + + public void setInum(String inum) { + this.inum = inum; + } + + public String getPrimaryKeyAttrName() { + return primaryKeyAttrName; + } + + public void setPrimaryKeyAttrName(String primaryKeyAttrName) { + this.primaryKeyAttrName = primaryKeyAttrName; + } + + public String getPrimaryKeyValues() { + return primaryKeyValues; + } + + public void setPrimaryKeyValues(String primaryKeyValues) { + this.primaryKeyValues = primaryKeyValues; + } + + public String getSecondaryKeyAttrName() { + return secondaryKeyAttrName; + } + + public void setSecondaryKeyAttrName(String secondaryKeyAttrName) { + this.secondaryKeyAttrName = secondaryKeyAttrName; + } + + public String getSecondaryKeyValues() { + return secondaryKeyValues; + } + + public void setSecondaryKeyValues(String secondaryKeyValues) { + this.secondaryKeyValues = secondaryKeyValues; + } + + public String getTertiaryKeyAttrName() { + return tertiaryKeyAttrName; + } + + public void setTertiaryKeyAttrName(String tertiaryKeyAttrName) { + this.tertiaryKeyAttrName = tertiaryKeyAttrName; + } + + public String getTertiaryKeyValues() { + return tertiaryKeyValues; + } + + public void setTertiaryKeyValues(String tertiaryKeyValues) { + this.tertiaryKeyValues = tertiaryKeyValues; + } + + public GluuStatus getStatus() { + return status; + } + + public void setStatus(GluuStatus status) { + this.status = status; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GluuInumMap [inum=").append(inum).append(", primaryKeyAttrName=").append(primaryKeyAttrName) + .append(", primaryKeyValues=").append(primaryKeyValues).append(", secondaryKeyAttrName=") + .append(secondaryKeyAttrName).append(", secondaryKeyValues=").append(secondaryKeyValues) + .append(", tertiaryKeyAttrName=").append(tertiaryKeyAttrName).append(", tertiaryKeyValues=") + .append(tertiaryKeyValues).append(", status=").append(status).append("]"); + return builder.toString(); + } +} diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/AppConfiguration.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/AppConfiguration.java new file mode 100644 index 00000000000..b1e5943f791 --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/AppConfiguration.java @@ -0,0 +1,173 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.model.config; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.enterprise.inject.Vetoed; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * Janssen Project configuration + * + * @author Yuriy Movchan + * @version 0.1, 05/15/2013 + */ +@Vetoed +@JsonIgnoreProperties(ignoreUnknown = true) +public class AppConfiguration extends CacheRefreshConfiguration { + + private String baseDN; + + private String[] personObjectClassTypes; + private String personCustomObjectClass; + + private String[] personObjectClassDisplayNames; + + private String[] contactObjectClassTypes; + + private boolean allowPersonModification; + + // In seconds; will be converted to millis + // In seconds; will be converted to millis + + private List supportedUserStatus= Arrays.asList("active","inactive"); + + private String loggingLevel; + private String loggingLayout; + private String externalLoggerConfiguration; + + private int metricReporterInterval; + private int metricReporterKeepDataDays; + private Boolean metricReporterEnabled; + private Boolean disableJdkLogger = true; + + // in seconds + private int cleanServiceInterval; + + private boolean keycloakLinkEnabled; + private String keycloakLinkServerIpAddress; + + private String keycloakLinkPollingInterval; + + private Date keycloakLinkLastUpdate; + private String keycloakLinkLastUpdateCount; + private String keycloakLinkProblemCount; + + private Boolean useLocalCache = false; + + public String getBaseDN() { + return baseDN; + } + + public String[] getPersonObjectClassTypes() { + return personObjectClassTypes; + } + + public String getPersonCustomObjectClass() { + return personCustomObjectClass; + } + + public String[] getContactObjectClassTypes() { + return contactObjectClassTypes; + } + + + public boolean isAllowPersonModification() { + return allowPersonModification; + } + + public String getLoggingLevel() { + return loggingLevel; + } + + public String getLoggingLayout() { + return loggingLayout; + } + + public String getExternalLoggerConfiguration() { + return externalLoggerConfiguration; + } + + public int getMetricReporterInterval() { + return metricReporterInterval; + } + + public List getSupportedUserStatus() { + return supportedUserStatus; + } + + public int getMetricReporterKeepDataDays() { + return metricReporterKeepDataDays; + } + + public Boolean getMetricReporterEnabled() { + return metricReporterEnabled; + } + + public Boolean getDisableJdkLogger() { + return disableJdkLogger; + } + + public int getCleanServiceInterval() { + return cleanServiceInterval; + } + + public Boolean getUseLocalCache() { + return useLocalCache; + } + + public boolean isKeycloakLinkEnabled() { + return keycloakLinkEnabled; + } + + public void setKeycloakLinkEnabled(boolean keycloakLinkEnabled) { + this.keycloakLinkEnabled = keycloakLinkEnabled; + } + + public String getKeycloakLinkServerIpAddress() { + return keycloakLinkServerIpAddress; + } + + public void setKeycloakLinkServerIpAddress(String keycloakLinkServerIpAddress) { + this.keycloakLinkServerIpAddress = keycloakLinkServerIpAddress; + } + + public String getKeycloakLinkPollingInterval() { + return keycloakLinkPollingInterval; + } + + public void setKeycloakLinkPollingInterval(String keycloakLinkPollingInterval) { + this.keycloakLinkPollingInterval = keycloakLinkPollingInterval; + } + + public Date getKeycloakLinkLastUpdate() { + return keycloakLinkLastUpdate; + } + + public void setKeycloakLinkLastUpdate(Date keycloakLinkLastUpdate) { + this.keycloakLinkLastUpdate = keycloakLinkLastUpdate; + } + + public String getKeycloakLinkLastUpdateCount() { + return keycloakLinkLastUpdateCount; + } + + public void setKeycloakLinkLastUpdateCount(String keycloakLinkLastUpdateCount) { + this.keycloakLinkLastUpdateCount = keycloakLinkLastUpdateCount; + } + + public String getKeycloakLinkProblemCount() { + return keycloakLinkProblemCount; + } + + public void setKeycloakLinkProblemCount(String keycloakLinkProblemCount) { + this.keycloakLinkProblemCount = keycloakLinkProblemCount; + } +} diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/BaseDnConfiguration.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/BaseDnConfiguration.java new file mode 100644 index 00000000000..cf0b7d47f28 --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/BaseDnConfiguration.java @@ -0,0 +1,76 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.model.config; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.enterprise.inject.Vetoed; + +/** + * @author Yuriy Movchan + * @version 0.1, 04/05/2023 + */ + +@Vetoed +@JsonIgnoreProperties(ignoreUnknown = true) +public class BaseDnConfiguration { + private String configuration; + + private String people; + private String groups; + private String attributes; + private String scripts; + private String metric; + + public String getConfiguration() { + return configuration; + } + + public void setConfiguration(String configuration) { + this.configuration = configuration; + } + + public String getAttributes() { + return attributes; + } + + public void setAttributes(String attributes) { + this.attributes = attributes; + } + + public String getScripts() { + return scripts; + } + + public void setScripts(String scripts) { + this.scripts = scripts; + } + + public String getPeople() { + return people; + } + + public void setPeople(String people) { + this.people = people; + } + + public String getGroups() { + return groups; + } + + public void setGroups(String groups) { + this.groups = groups; + } + + public String getMetric() { + return metric; + } + + public void setMetric(String metric) { + this.metric = metric; + } + +} diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/CacheRefreshAttributeMapping.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/CacheRefreshAttributeMapping.java new file mode 100644 index 00000000000..2f5a334add6 --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/CacheRefreshAttributeMapping.java @@ -0,0 +1,22 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.model.config; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import java.io.Serializable; + + +/** + * Attribute mapping + * + * @author Yuriy Movchan Date: 10/23/2015 + */ +@JsonPropertyOrder({ "source", "destination" }) +public class CacheRefreshAttributeMapping extends io.jans.link.model.config.shared.CacheRefreshAttributeMapping implements Serializable { + +} diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/CacheRefreshConfiguration.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/CacheRefreshConfiguration.java new file mode 100644 index 00000000000..b9cbb14a527 --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/CacheRefreshConfiguration.java @@ -0,0 +1,29 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.model.config; + +import jakarta.enterprise.inject.Vetoed; + +/** + * Cache refresh configuration + * + * @author Yuriy Movchan Date: 07.13.2011 + */ +@Vetoed +public class CacheRefreshConfiguration extends io.jans.link.model.config.shared.CacheRefreshConfiguration implements Configuration { + + private KeycloakConfiguration keycloakConfiguration; + + public KeycloakConfiguration getKeycloakConfiguration() { + return keycloakConfiguration; + } + + public void setKeycloakConfiguration(KeycloakConfiguration keycloakConfiguration) { + this.keycloakConfiguration = keycloakConfiguration; + } + +} diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/Conf.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/Conf.java new file mode 100644 index 00000000000..58247591aad --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/Conf.java @@ -0,0 +1,76 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.model.config; + +import io.jans.orm.annotation.*; +import jakarta.enterprise.inject.Vetoed; + +/** + * @author Yuriy Movchan + * @version 0.1, 04/05/2023 + */ +@Vetoed +@DataEntry +@ObjectClass(value = "jansAppConf") +public class Conf { + @DN + private String dn; + + @JsonObject + @AttributeName(name = "jansConfDyn") + private AppConfiguration dynamic; + + @JsonObject + @AttributeName(name = "jansConfStatic") + private StaticConfiguration statics; + + @AttributeName(name = "jansRevision") + private long revision; + + public String getDn() { + return dn; + } + + public void setDn(String dn) { + this.dn = dn; + } + + public AppConfiguration getDynamic() { + return dynamic; + } + + public void setDynamic(AppConfiguration dynamic) { + this.dynamic = dynamic; + } + + public StaticConfiguration getStatics() { + return statics; + } + + public void setStatics(StaticConfiguration statics) { + this.statics = statics; + } + + public long getRevision() { + return revision; + } + + public void setRevision(long revision) { + this.revision = revision; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("Conf"); + sb.append("{dn='").append(dn).append('\''); + sb.append(", dynamic='").append(dynamic).append('\''); + sb.append(", static='").append(statics).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/Configuration.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/Configuration.java new file mode 100644 index 00000000000..6940f461dba --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/Configuration.java @@ -0,0 +1,16 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.model.config; + +/** + * base interface for all Jans Auth configurations + * + * @author Yuriy Movchan + * @version 04/12/2017 + */ +public interface Configuration extends io.jans.link.model.config.shared.Configuration { +} diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/KeycloakConfiguration.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/KeycloakConfiguration.java new file mode 100644 index 00000000000..0c8cb98480f --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/KeycloakConfiguration.java @@ -0,0 +1,87 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.model.config; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +/** + * Cache refresh configuration + * + * @author Shekhar + */ +@JsonPropertyOrder({"serverUrl", "realm", "clientId", "clientSecret", "grantType", "username", "keycloak"}) +@JsonIgnoreProperties( + ignoreUnknown = true +) +public class KeycloakConfiguration implements Configuration { + + private String serverUrl; + private String realm; + private String clientId; + private String clientSecret; + private String grantType; + private String username; + private String password; + + + public String getServerUrl() { + return serverUrl; + } + + public void setServerUrl(String serverUrl) { + this.serverUrl = serverUrl; + } + + public String getRealm() { + return realm; + } + + public void setRealm(String realm) { + this.realm = realm; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getGrantType() { + return grantType; + } + + public void setGrantType(String grantType) { + this.grantType = grantType; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/StaticConfiguration.java b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/StaticConfiguration.java new file mode 100644 index 00000000000..08699cacb34 --- /dev/null +++ b/jans-keycloak-link/model/src/main/java/io/jans/keycloak/link/model/config/StaticConfiguration.java @@ -0,0 +1,36 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.model.config; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.enterprise.inject.Vetoed; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; + +/** + * @author Yuriy Movchan + * @version 0.1, 04/05/2023 + */ +@Vetoed +@XmlRootElement(name = "static") +@XmlAccessorType(XmlAccessType.FIELD) +@JsonIgnoreProperties(ignoreUnknown = true) +public class StaticConfiguration implements Configuration { + + @XmlElement(name = "base-dn") + private BaseDnConfiguration baseDn; + + public BaseDnConfiguration getBaseDn() { + return baseDn; + } + + public void setBaseDn(BaseDnConfiguration baseDn) { + this.baseDn = baseDn; + } +} diff --git a/jans-keycloak-link/model/src/main/resources/META-INF/maven/archetype.xml b/jans-keycloak-link/model/src/main/resources/META-INF/maven/archetype.xml new file mode 100644 index 00000000000..fd0715448e9 --- /dev/null +++ b/jans-keycloak-link/model/src/main/resources/META-INF/maven/archetype.xml @@ -0,0 +1,9 @@ + + model + + src/main/java/App.java + + + src/test/java/AppTest.java + + diff --git a/jans-keycloak-link/model/src/main/resources/archetype-resources/pom.xml b/jans-keycloak-link/model/src/main/resources/archetype-resources/pom.xml new file mode 100644 index 00000000000..d0f868a2ca2 --- /dev/null +++ b/jans-keycloak-link/model/src/main/resources/archetype-resources/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + $io.jans + $model + $1.0.15-SNAPSHOT + + + junit + junit + 3.8.1 + test + + + diff --git a/jans-keycloak-link/model/src/main/resources/archetype-resources/src/main/java/App.java b/jans-keycloak-link/model/src/main/resources/archetype-resources/src/main/java/App.java new file mode 100644 index 00000000000..95093fedd40 --- /dev/null +++ b/jans-keycloak-link/model/src/main/resources/archetype-resources/src/main/java/App.java @@ -0,0 +1,13 @@ +package $io.jans; + +/** + * Hello world! + * + */ +public class App +{ + public static void main( String[] args ) + { + System.out.println( "Hello World!" ); + } +} diff --git a/jans-keycloak-link/model/src/main/resources/archetype-resources/src/test/java/AppTest.java b/jans-keycloak-link/model/src/main/resources/archetype-resources/src/test/java/AppTest.java new file mode 100644 index 00000000000..a5987ebdc10 --- /dev/null +++ b/jans-keycloak-link/model/src/main/resources/archetype-resources/src/test/java/AppTest.java @@ -0,0 +1,38 @@ +package $io.jans; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/jans-keycloak-link/pom.xml b/jans-keycloak-link/pom.xml new file mode 100644 index 00000000000..46220e1ea34 --- /dev/null +++ b/jans-keycloak-link/pom.xml @@ -0,0 +1,110 @@ + + 4.0.0 + io.jans + jans-keycloak-link-parent + 1.0.19-SNAPSHOT + + model + service + server + + Jans-Keycloak-Link + pom + http://maven.apache.org + + + ${project.version} + 3.0.3 + UTF-8 + 11 + 11 + 11.0.8 + 5.1.0.Final + 6.2.4.Final + + + + + + mavencentral + maven central + https://repo1.maven.org/maven2 + + + jans + Janssen project repository + https://maven.jans.io/maven + + + bouncycastle + Bouncy Castle + https://repo1.maven.org/maven2/org/bouncycastle + + + repository.jboss.org + JBoss Repository + https://repository.jboss.org/nexus/content/groups/public-jboss/ + + + + + + + + io.jans + jans-bom + ${janssen.version} + import + pom + + + + + + + org.keycloak + keycloak-admin-client-jakarta + 21.1.1 + + + io.jans + jans-core-service + + + io.jans + jans-core-timer-weld + + + + + org.jboss.weld + weld-core-parent + ${weld.version} + pom + + + org.jboss.weld.servlet + weld-servlet-core + + + commons-beanutils + commons-beanutils + + + + + junit + junit + test + + + org.keycloak.bom + keycloak-adapter-bom + 21.0.1 + pom + + + + + diff --git a/jans-keycloak-link/server/pom.xml b/jans-keycloak-link/server/pom.xml new file mode 100644 index 00000000000..af3df870d48 --- /dev/null +++ b/jans-keycloak-link/server/pom.xml @@ -0,0 +1,100 @@ + + + jans-keycloak-link-parent + io.jans + 1.0.19-SNAPSHOT + + 4.0.0 + jans-keycloak-link-server + war + jans keycloak link server + http://maven.apache.org + + + io.jans + jans-keycloak-link-model + ${project.version} + + + io.jans + jans-keycloak-link-service + ${project.version} + + + junit + junit + 3.8.1 + test + + + + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/web.xml + + /jans + + + ${project.build.directory}/${project.build.finalName} + + 3 + + + + + + + + webapp-jetty + + + !jetty.disable + + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.3.1 + + + + + src/main/webapp + true + + **/*.xml + **/*.properties + + + + src/main/webapp + false + + **/*.xhtml + **/*.jsp + **/*.html + **/*.ico + + + + + src/main/webapp + true + + + + + + + + + diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/AppInitializer.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/AppInitializer.java new file mode 100644 index 00000000000..a2ba3a6f032 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/AppInitializer.java @@ -0,0 +1,246 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.server.service; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Properties; + +import io.jans.keycloak.link.timer.JansKeycloakLinkTimer; +import org.slf4j.Logger; + +import com.google.common.collect.Lists; + +import io.jans.exception.ConfigurationException; +import io.jans.keycloak.link.service.config.ApplicationFactory; +import io.jans.keycloak.link.service.config.ConfigurationFactory; +import io.jans.link.service.EncryptionService; +import io.jans.model.custom.script.CustomScriptType; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.model.PersistenceConfiguration; +import io.jans.orm.util.properties.FileConfiguration; +import io.jans.service.PythonService; +import io.jans.service.cdi.event.ApplicationInitialized; +import io.jans.service.cdi.event.ApplicationInitializedEvent; +import io.jans.service.cdi.event.LdapConfigurationReload; +import io.jans.service.cdi.util.CdiUtil; +import io.jans.service.custom.script.CustomScriptManager; +import io.jans.service.metric.inject.ReportMetric; +import io.jans.service.timer.QuartzSchedulerManager; +import io.jans.util.StringHelper; +import io.jans.util.security.SecurityProviderUtility; +import io.jans.util.security.StringEncrypter; +import io.jans.util.security.StringEncrypter.EncryptionException; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Event; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +/** + * + * Cache refresh server initializer + * @author Yuriy Movchan + * @version May 12, 2020 + */ +@ApplicationScoped +public class AppInitializer { + + @Inject + private Logger log; + + @Inject + private BeanManager beanManager; + + @Inject + private Event eventApplicationInitialized; + + @Inject + @Named(ApplicationFactory.PERSISTENCE_ENTRY_MANAGER_NAME) + private Instance persistenceEntryManagerInstance; + + @Inject + private ApplicationFactory applicationFactory; + + @Inject + private Instance encryptionServiceInstance; + + @Inject + private PythonService pythonService; + + @Inject + private CustomScriptManager customScriptManager; + + @Inject + private ConfigurationFactory configurationFactory; + + @Inject + private QuartzSchedulerManager quartzSchedulerManager; + + @Inject + private LoggerService loggerService; + + @Inject + private JansKeycloakLinkTimer jansKeycloakLinkTimer; + + @PostConstruct + public void createApplicationComponents() { + try { + SecurityProviderUtility.installBCProvider(); + } catch (ClassCastException ex) { + log.error("Failed to install BC provider properly"); + } + } + + public void applicationInitialized(@Observes @Initialized(ApplicationScoped.class) Object init) { + log.debug("Initializing application services"); + + configurationFactory.create(); + + PersistenceEntryManager localPersistenceEntryManager = persistenceEntryManagerInstance.get(); + log.trace("Attempting to use {}: {}", ApplicationFactory.PERSISTENCE_ENTRY_MANAGER_NAME, + localPersistenceEntryManager.getOperationService()); + + // Initialize python interpreter + pythonService + .initPythonInterpreter(configurationFactory.getBaseConfiguration().getString("pythonModulesDir", null)); + + // Initialize script manager + List supportedCustomScriptTypes = Lists.newArrayList(CustomScriptType.CACHE_REFRESH); + + // There is no Fido2 scripts yet + //supportedCustomScriptTypes.clear(); + + // Start timer + initSchedulerService(); + + configurationFactory.initTimer(); + loggerService.initTimer(); + customScriptManager.initTimer(supportedCustomScriptTypes); + jansKeycloakLinkTimer.initTimer(); + // Notify plugins about finish application initialization + eventApplicationInitialized.select(ApplicationInitialized.Literal.APPLICATION) + .fire(new ApplicationInitializedEvent()); + } + + protected void initSchedulerService() { + quartzSchedulerManager.start(); + + String disableScheduler = System.getProperties().getProperty("gluu.disable.scheduler"); + if ((disableScheduler != null) && Boolean.valueOf(disableScheduler)) { + this.log.warn("Suspending Quartz Scheduler Service..."); + quartzSchedulerManager.standby(); + return; + } + } + + @Produces + @ApplicationScoped + public StringEncrypter getStringEncrypter() { + String encodeSalt = configurationFactory.getCryptoConfigurationSalt(); + + if (StringHelper.isEmpty(encodeSalt)) { + throw new ConfigurationException("Encode salt isn't defined"); + } + + try { + StringEncrypter stringEncrypter = StringEncrypter.instance(encodeSalt); + + return stringEncrypter; + } catch (EncryptionException ex) { + throw new ConfigurationException("Failed to create StringEncrypter instance"); + } + } + + protected Properties preparePersistanceProperties() { + PersistenceConfiguration persistenceConfiguration = this.configurationFactory.getPersistenceConfiguration(); + FileConfiguration persistenceConfig = persistenceConfiguration.getConfiguration(); + Properties connectionProperties = (Properties) persistenceConfig.getProperties(); + + EncryptionService securityService = encryptionServiceInstance.get(); + Properties decryptedConnectionProperties = securityService.decryptAllProperties(connectionProperties); + return decryptedConnectionProperties; + } + + protected Properties prepareCustomPersistanceProperties(String configId) { + Properties connectionProperties = preparePersistanceProperties(); + if (StringHelper.isNotEmpty(configId)) { + // Replace properties names 'configId.xyz' to 'configId.xyz' in order to + // override default values + connectionProperties = (Properties) connectionProperties.clone(); + + String baseGroup = configId + "."; + for (Object key : connectionProperties.keySet()) { + String propertyName = (String) key; + if (propertyName.startsWith(baseGroup)) { + propertyName = propertyName.substring(baseGroup.length()); + + Object value = connectionProperties.get(key); + connectionProperties.put(propertyName, value); + } + } + } + + return connectionProperties; + } + + @Produces + @ApplicationScoped + @Named(ApplicationFactory.PERSISTENCE_ENTRY_MANAGER_NAME) + public PersistenceEntryManager createPersistenceEntryManager() { + Properties connectionProperties = preparePersistanceProperties(); + + PersistenceEntryManager persistenceEntryManager = applicationFactory.getPersistenceEntryManagerFactory() + .createEntryManager(connectionProperties); + log.info("Created {}: {} with operation service: {}", + new Object[] { ApplicationFactory.PERSISTENCE_ENTRY_MANAGER_NAME, persistenceEntryManager, + persistenceEntryManager.getOperationService() }); + + return persistenceEntryManager; + } + + public void recreatePersistenceEntryManager(@Observes @LdapConfigurationReload String event) { + recreatePersistanceEntryManagerImpl(persistenceEntryManagerInstance, + ApplicationFactory.PERSISTENCE_ENTRY_MANAGER_NAME); + + recreatePersistanceEntryManagerImpl(persistenceEntryManagerInstance, + ApplicationFactory.PERSISTENCE_METRIC_ENTRY_MANAGER_NAME, ReportMetric.Literal.INSTANCE); + } + + protected void recreatePersistanceEntryManagerImpl(Instance instance, + String persistenceEntryManagerName, Annotation... qualifiers) { + // Get existing application scoped instance + PersistenceEntryManager oldPersistenceEntryManager = CdiUtil.getContextBean(beanManager, + PersistenceEntryManager.class, persistenceEntryManagerName); + + // Close existing connections + closePersistenceEntryManager(oldPersistenceEntryManager, persistenceEntryManagerName); + + // Force to create new bean + PersistenceEntryManager persistenceEntryManager = instance.get(); + instance.destroy(persistenceEntryManager); + log.info("Recreated instance {}: {} with operation service: {}", persistenceEntryManagerName, + persistenceEntryManager, persistenceEntryManager.getOperationService()); + } + + private void closePersistenceEntryManager(PersistenceEntryManager oldPersistenceEntryManager, + String persistenceEntryManagerName) { + // Close existing connections + if ((oldPersistenceEntryManager != null) && (oldPersistenceEntryManager.getOperationService() != null)) { + log.debug("Attempting to destroy {}:{} with operation service: {}", persistenceEntryManagerName, + oldPersistenceEntryManager, oldPersistenceEntryManager.getOperationService()); + oldPersistenceEntryManager.destroy(); + log.debug("Destroyed {}:{} with operation service: {}", persistenceEntryManagerName, + oldPersistenceEntryManager, oldPersistenceEntryManager.getOperationService()); + } + } +} diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/AttributeService.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/AttributeService.java new file mode 100644 index 00000000000..97dffd9b7e3 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/AttributeService.java @@ -0,0 +1,31 @@ +package io.jans.keycloak.link.server.service; + +import io.jans.keycloak.link.model.config.AppConfiguration; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.interceptor.Interceptor; + +/** + * Provides operations with attributes + * + * @author Yuriy Movchan Date: 09/07/2023 + */ +@ApplicationScoped +@Priority(Interceptor.Priority.APPLICATION + 5) +public class AttributeService extends io.jans.link.service.AttributeService { + + private static final long serialVersionUID = 3502134792415981865L; + + @Inject + private AppConfiguration appConfiguration; + + public String getPersonCustomObjectClass() { + return appConfiguration.getPersonCustomObjectClass(); + } + + public String[] getPersonObjectClassTypes() { + return appConfiguration.getPersonObjectClassTypes(); + } + +} diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/CacheRefreshConfigurationService.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/CacheRefreshConfigurationService.java new file mode 100644 index 00000000000..b4e49975ca7 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/CacheRefreshConfigurationService.java @@ -0,0 +1,92 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.keycloak.link.server.service; + +import io.jans.keycloak.link.model.config.AppConfiguration; +import io.jans.keycloak.link.model.config.Conf; +import io.jans.keycloak.link.service.config.ConfigurationFactory; +import io.jans.orm.PersistenceEntryManager; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.slf4j.Logger; + +import java.io.Serializable; + +/** + * GluuConfiguration service + * + * @author Reda Zerrad Date: 08.10.2012 + */ +@ApplicationScoped +public class CacheRefreshConfigurationService implements Serializable { + + private Logger log; + + @Inject + private static final long serialVersionUID = 8842838732456296435L; + + @Inject + private PersistenceEntryManager persistenceEntryManager; + + @Inject + private ConfigurationFactory configurationFactory; + + /** + * Update configuration entry + * + * @param configuration + * GluuConfiguration + */ + public void updateConfiguration(AppConfiguration configuration) { + try { + Conf conf = getConfiguration(null); + conf.setDynamic(configuration); + persistenceEntryManager.merge(conf); + } catch (Exception e) { + log.info("", e); + } + } + + /** + * Get configuration + * + * @return Configuration + * @throws Exception + */ + public Conf getConfiguration(String[] returnAttributes) { + Conf result = null; + result = persistenceEntryManager.find(getDnForConfiguration(), Conf.class, returnAttributes); + + return result; + } + + /** + * Get all configurations + * + * @return List of attributes + * @throws Exception + */ + public AppConfiguration getCacheRefreshConfiguration() { + String dn = getDnForConfiguration(); + Conf conf = persistenceEntryManager.find(dn, Conf.class, null); + return conf.getDynamic(); + } + + /** + * Build DN string for configuration + * + * @return DN string for specified configuration or DN for configurations branch + * if inum is null + * @throws Exception + */ + public String getDnForConfiguration() { + String dn = configurationFactory.getBaseConfiguration().getString("keycloakLink_ConfigurationEntryDN"); + return dn; + } + + +} diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/CustomScriptService.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/CustomScriptService.java new file mode 100644 index 00000000000..d5b16be9724 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/CustomScriptService.java @@ -0,0 +1,29 @@ +package io.jans.keycloak.link.server.service; + +import io.jans.keycloak.link.model.config.StaticConfiguration; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Alternative; +import jakarta.inject.Inject; +import jakarta.interceptor.Interceptor; + +/** + * Operations with custom scripts + * + * @author Yuriy Movchan Date: 09/07/2023 + */ +@ApplicationScoped +@Alternative +@Priority(Interceptor.Priority.APPLICATION + 5) +public class CustomScriptService extends io.jans.link.service.custom.CustomScriptService { + + @Inject + private StaticConfiguration staticConfiguration; + + private static final long serialVersionUID = -5283102477313448031L; + + public String baseDn() { + return staticConfiguration.getBaseDn().getScripts(); + } + +} diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/KeycloakService.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/KeycloakService.java new file mode 100644 index 00000000000..6ddcc525e7a --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/KeycloakService.java @@ -0,0 +1,73 @@ +package io.jans.keycloak.link.server.service; + +import io.jans.keycloak.link.model.config.CacheRefreshConfiguration; +import io.jans.keycloak.link.model.config.KeycloakConfiguration; +import io.jans.keycloak.link.service.config.ConfigurationFactory; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.admin.client.resource.RealmsResource; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.slf4j.Logger; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@ApplicationScoped +public class KeycloakService implements Serializable{ + + @Inject + private Logger log; + @Inject + private ConfigurationFactory configurationFactory; + + public Keycloak getKeycloakInstance() { + CacheRefreshConfiguration cacheRefreshConfiguration = configurationFactory.getAppConfiguration(); + + KeycloakConfiguration keycloakConfiguration = cacheRefreshConfiguration.getKeycloakConfiguration(); + + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + Keycloak instance = KeycloakBuilder.builder() + .serverUrl(keycloakConfiguration.getServerUrl()) + .realm(keycloakConfiguration.getRealm()) + .clientId(keycloakConfiguration.getClientId()) + .clientSecret(keycloakConfiguration.getClientSecret()) + //.grantType(OAuth2Constants.CLIENT_CREDENTIALS) + .grantType(keycloakConfiguration.getGrantType()) + .username(keycloakConfiguration.getUsername()) + .password(keycloakConfiguration.getPassword()) + //.resteasyClient(new ResteasyClientBuilderImpl().connectionPoolSize(10).build()) + .build(); + log.info("getKeycloakInstance() instance::"+instance.getClass()); + + return instance; + } + + @Produces + @ApplicationScoped + public List getUserList() { + List finalUsersResourceList = new ArrayList<>(); + + log.info("getUserList() instance::"+getKeycloakInstance().getClass()); + RealmsResource RealmsResource = getKeycloakInstance().realms(); + List RealmResource = RealmsResource.findAll();//realm(); + + for(RealmRepresentation realmRepresentation : RealmResource){ + log.info("check " + realmRepresentation.getRealm()); + String realm = realmRepresentation.getRealm(); + List usersResourceList = getKeycloakInstance().realm(realm).users().list(); + finalUsersResourceList.addAll(usersResourceList); + } + + for(UserRepresentation userRepresentation : finalUsersResourceList){ + log.info("username " + userRepresentation.getUsername()); + } + + return finalUsersResourceList; + } +} diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/LoggerService.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/LoggerService.java new file mode 100644 index 00000000000..3e6068dcdd0 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/LoggerService.java @@ -0,0 +1,49 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.server.service; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +import static org.apache.commons.lang3.BooleanUtils.isTrue; + +import io.jans.keycloak.link.model.config.AppConfiguration; + +/** + * Logger service + * + * @author Yuriy Movchan Date: 08/19/2018 + */ +@ApplicationScoped +@Named +public class LoggerService extends io.jans.service.logger.LoggerService { + + @Inject + private AppConfiguration appConfiguration; + + @Override + public boolean isDisableJdkLogger() { + return isTrue(appConfiguration.getDisableJdkLogger()); + } + + @Override + public String getLoggingLevel() { + return appConfiguration.getLoggingLevel(); + } + + @Override + public String getExternalLoggerConfiguration() { + return appConfiguration.getExternalLoggerConfiguration(); + } + + @Override + public String getLoggingLayout() { + return appConfiguration.getLoggingLayout(); + } + +} diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/OrganizationService.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/OrganizationService.java new file mode 100644 index 00000000000..23fa1c9d2c1 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/OrganizationService.java @@ -0,0 +1,27 @@ +package io.jans.keycloak.link.server.service; + +import io.jans.keycloak.link.model.config.AppConfiguration; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.interceptor.Interceptor; + +/** + * Provides operations with organization + * + * @author Yuriy Movchan Date: 09/07/2023 + */ +@ApplicationScoped +@Priority(Interceptor.Priority.APPLICATION + 5) +public class OrganizationService extends io.jans.link.service.OrganizationService { + + private static final long serialVersionUID = 4502134792415981865L; + + @Inject + private AppConfiguration appConfiguration; + + public String getAppConfigurationBaseDn() { + return appConfiguration.getBaseDN(); + } + +} diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/SystemResteasyInitializer.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/SystemResteasyInitializer.java new file mode 100644 index 00000000000..f60b5be6590 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/server/service/SystemResteasyInitializer.java @@ -0,0 +1,33 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.server.service; + +import java.util.HashSet; +import java.util.Set; + +import io.jans.keycloak.link.ws.rs.HealthCheckController; +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Application; + +/** + * Integration with Resteasy + * + * @author Yuriy Movchan + * @version 0.1, 11/13/2020 + */ +@ApplicationPath("/sys") +public class SystemResteasyInitializer extends Application { + + @Override + public Set> getClasses() { + HashSet> classes = new HashSet>(); + classes.add(HealthCheckController.class); + + return classes; + } + +} diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/service/config/ApplicationFactory.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/service/config/ApplicationFactory.java new file mode 100644 index 00000000000..63e8978275d --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/service/config/ApplicationFactory.java @@ -0,0 +1,60 @@ +/* + * oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.keycloak.link.service.config; + +import org.slf4j.Logger; + +import io.jans.keycloak.link.model.config.AppConfiguration; +import io.jans.orm.PersistenceEntryManagerFactory; +import io.jans.orm.model.PersistenceConfiguration; +import io.jans.orm.service.PersistanceFactoryService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +/** + * Holds factory methods to create services + * + * @author Yuriy Movchan Date: 02/14/2017 + */ +@ApplicationScoped +public class ApplicationFactory { + + @Inject + private Logger log; + + @Inject + private ConfigurationFactory configurationFactory; + + @Inject + private PersistanceFactoryService persistanceFactoryService; + + @Inject + private AppConfiguration appConfiguration; + + public static final String PERSISTENCE_ENTRY_MANAGER_FACTORY_NAME = "persistenceEntryManagerFactory"; + + public static final String PERSISTENCE_ENTRY_MANAGER_NAME = "persistenceEntryManager"; + public static final String PERSISTENCE_METRIC_ENTRY_MANAGER_NAME = "persistenceMetricEntryManager"; + + public static final String PERSISTENCE_CENTRAL_ENTRY_MANAGER_NAME = "centralPersistenceEntryManager"; + + public static final String PERSISTENCE_METRIC_CONFIG_GROUP_NAME = "metric"; + + public PersistenceEntryManagerFactory getPersistenceEntryManagerFactory() { + PersistenceConfiguration persistenceConfiguration = configurationFactory.getPersistenceConfiguration(); + return persistanceFactoryService.getPersistenceEntryManagerFactory(persistenceConfiguration); + } + + public PersistenceEntryManagerFactory getPersistenceEntryManagerFactory(PersistenceConfiguration persistenceConfiguration) { + return persistanceFactoryService.getPersistenceEntryManagerFactory(persistenceConfiguration); + } + + public PersistenceEntryManagerFactory getPersistenceEntryManagerFactory(Class persistenceEntryManagerFactoryClass) { + return persistanceFactoryService.getPersistenceEntryManagerFactory(persistenceEntryManagerFactoryClass); + } + +} \ No newline at end of file diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/service/config/ConfigurationFactory.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/service/config/ConfigurationFactory.java new file mode 100644 index 00000000000..b9fcaa51f29 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/service/config/ConfigurationFactory.java @@ -0,0 +1,362 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.service.config; + +import io.jans.exception.ConfigurationException; +import io.jans.keycloak.link.model.config.*; +import io.jans.model.SmtpConfiguration; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.exception.BasePersistenceException; +import io.jans.orm.model.PersistenceConfiguration; +import io.jans.orm.service.PersistanceFactoryService; +import io.jans.service.cache.CacheConfiguration; +import io.jans.service.cdi.async.Asynchronous; +import io.jans.service.cdi.event.*; +import io.jans.service.timer.event.TimerEvent; +import io.jans.service.timer.schedule.TimerSchedule; +import io.jans.util.StringHelper; +import io.jans.util.properties.FileConfiguration; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; + +import java.io.File; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author Yuriy Movchan Date: 05/13/2020 + */ +@ApplicationScoped +public class ConfigurationFactory { + + @Inject + private Logger log; + + @Inject + private Event timerEvent; + + @Inject + private Event configurationUpdateEvent; + + @Inject + private Event event; + + @Inject + private Instance persistenceEntryManagerInstance; + + @Inject + private PersistanceFactoryService persistanceFactoryService; + + @Inject + private Instance configurationInstance; + + private SmtpConfiguration smtpConfiguration = new SmtpConfiguration(); + + private CacheConfiguration cacheConfiguration = new CacheConfiguration(); + + public final static String PERSISTENCE_CONFIGUARION_RELOAD_EVENT_TYPE = "persistenceConfigurationReloadEvent"; + public final static String BASE_CONFIGUARION_RELOAD_EVENT_TYPE = "baseConfigurationReloadEvent"; + + private final static int DEFAULT_INTERVAL = 30; // 30 seconds + + static { + if (System.getProperty("jans.base") != null) { + BASE_DIR = System.getProperty("jans.base"); + } else if ((System.getProperty("catalina.base") != null) && (System.getProperty("catalina.base.ignore") == null)) { + BASE_DIR = System.getProperty("catalina.base"); + } else if (System.getProperty("catalina.home") != null) { + BASE_DIR = System.getProperty("catalina.home"); + } else if (System.getProperty("jboss.home.dir") != null) { + BASE_DIR = System.getProperty("jboss.home.dir"); + } else { + BASE_DIR = null; + } + } + + private static final String BASE_DIR; + private static final String DIR = BASE_DIR + File.separator + "conf" + File.separator; + + private static final String BASE_PROPERTIES_FILE = DIR + "jans.properties"; + private static final String APP_PROPERTIES_FILE = DIR + "cache-refresh.properties"; + + private final String SALT_FILE_NAME = "salt"; + + private String confDir, saltFilePath; + + private boolean loaded = false; + + private FileConfiguration baseConfiguration; + + private PersistenceConfiguration persistenceConfiguration; + private AppConfiguration dynamicConf; + private StaticConfiguration staticConf; + private String cryptoConfigurationSalt; + + private AtomicBoolean isActive; + + private long baseConfigurationFileLastModifiedTime; + + private long loadedRevision = -1; + private boolean loadedFromLdap = true; + + @PostConstruct + public void init() { + this.isActive = new AtomicBoolean(true); + try { + this.persistenceConfiguration = persistanceFactoryService.loadPersistenceConfiguration(APP_PROPERTIES_FILE); + loadBaseConfiguration(); + + this.confDir = confDir(); + + String certsDir = this.baseConfiguration.getString("certsDir"); + if (StringHelper.isEmpty(certsDir)) { + certsDir = confDir; + } + this.saltFilePath = confDir + SALT_FILE_NAME; + + loadCryptoConfigurationSalt(); + } finally { + this.isActive.set(false); + } + } + + public void create() { + if (!createFromDb()) { + log.error("Failed to load configuration from LDAP. Please fix it!!!."); + throw new ConfigurationException("Failed to load configuration from LDAP."); + } else { + log.info("Configuration loaded successfully."); + } + } + + public void initTimer() { + log.debug("Initializing Configuration Timer"); + + final int delay = 30; + final int interval = DEFAULT_INTERVAL; + + timerEvent.fire(new TimerEvent(new TimerSchedule(delay, interval), new ConfigurationEvent(), + Scheduled.Literal.INSTANCE)); + } + + @Asynchronous + public void reloadConfigurationTimerEvent(@Observes @Scheduled ConfigurationEvent configurationEvent) { + if (this.isActive.get()) { + return; + } + + if (!this.isActive.compareAndSet(false, true)) { + return; + } + + try { + reloadConfiguration(); + } catch (Throwable ex) { + log.error("Exception happened while reloading application configuration", ex); + } finally { + this.isActive.set(false); + } + } + + private void reloadConfiguration() { + // Reload LDAP configuration if needed + PersistenceConfiguration newPersistenceConfiguration = persistanceFactoryService.loadPersistenceConfiguration(APP_PROPERTIES_FILE); + + if (newPersistenceConfiguration != null) { + if (!StringHelper.equalsIgnoreCase(this.persistenceConfiguration.getFileName(), newPersistenceConfiguration.getFileName()) || (newPersistenceConfiguration.getLastModifiedTime() > this.persistenceConfiguration.getLastModifiedTime())) { + // Reload configuration only if it was modified + this.persistenceConfiguration = newPersistenceConfiguration; + event.select(LdapConfigurationReload.Literal.INSTANCE).fire(PERSISTENCE_CONFIGUARION_RELOAD_EVENT_TYPE); + } + } + + // Reload Base configuration if needed + File baseConfiguration = new File(BASE_PROPERTIES_FILE); + if (baseConfiguration.exists()) { + final long lastModified = baseConfiguration.lastModified(); + if (lastModified > baseConfigurationFileLastModifiedTime) { + // Reload configuration only if it was modified + loadBaseConfiguration(); + event.select(BaseConfigurationReload.Literal.INSTANCE).fire(BASE_CONFIGUARION_RELOAD_EVENT_TYPE); + } + } + + if (!loadedFromLdap) { + return; + } + + reloadConfFromLdap(); + } + + private boolean isRevisionIncreased() { + final Conf conf = loadConfigurationFromLdap("jansRevision"); + if (conf == null) { + return false; + } + + log.trace("DB revision: " + conf.getRevision() + ", server revision:" + loadedRevision); + return conf.getRevision() > this.loadedRevision; + } + + private String confDir() { + final String confDir = this.baseConfiguration.getString("confDir", null); + if (StringUtils.isNotBlank(confDir)) { + return confDir; + } + + return DIR; + } + + public FileConfiguration getBaseConfiguration() { + return baseConfiguration; + } + + @Produces + @ApplicationScoped + public PersistenceConfiguration getPersistenceConfiguration() { + return persistenceConfiguration; + } + + @Produces + @ApplicationScoped + public AppConfiguration getAppConfiguration() { + return dynamicConf; + } + + @Produces + @ApplicationScoped + public StaticConfiguration getStaticConfiguration() { + return staticConf; + } + + @Produces + @ApplicationScoped + public SmtpConfiguration getSmtpConfiguration() { + return smtpConfiguration; + } + + @Produces + @ApplicationScoped + public CacheConfiguration getCacheConfiguration() { + return cacheConfiguration; + } + + public BaseDnConfiguration getBaseDn() { + return getStaticConfiguration().getBaseDn(); + } + + public String getCryptoConfigurationSalt() { + return cryptoConfigurationSalt; + } + + public boolean reloadConfFromLdap() { + if (!isRevisionIncreased()) { + return false; + } + + return createFromDb(); + } + + private boolean createFromDb() { + log.info("Loading configuration from '{}' DB...", baseConfiguration.getString("persistence.type")); + try { + final Conf c = loadConfigurationFromLdap(); + if (c != null) { + init(c); + + // Destroy old configuration + if (this.loaded) { + destroy(AppConfiguration.class); + destroy(StaticConfiguration.class); + } + + this.loaded = true; + configurationUpdateEvent.select(ConfigurationUpdate.Literal.INSTANCE).fire(dynamicConf); + + return true; + } + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + } + + throw new ConfigurationException("Unable to find configuration in DB... "); + } + + public void destroy(Class clazz) { + Instance confInstance = configurationInstance.select(clazz); + configurationInstance.destroy(confInstance.get()); + } + + private Conf loadConfigurationFromLdap(String... returnAttributes) { + final PersistenceEntryManager persistenceEntryManager = persistenceEntryManagerInstance.get(); + final String dn = this.baseConfiguration.getString("keycloakLink_ConfigurationEntryDN"); + try { + final Conf conf = persistenceEntryManager.find(dn, Conf.class, returnAttributes); + + return conf; + } catch (BasePersistenceException ex) { + log.error(ex.getMessage()); + } + + return null; + } + + private void init(Conf conf) { + initConfigurationConf(conf); + this.loadedRevision = conf.getRevision(); + } + + private void initConfigurationConf(Conf conf) { + if (conf.getDynamic() != null) { + dynamicConf = conf.getDynamic(); + } + if (conf.getStatics() != null) { + staticConf = conf.getStatics(); + } + } + + private void loadBaseConfiguration() { + this.baseConfiguration = createFileConfiguration(BASE_PROPERTIES_FILE, true); + + File baseConfiguration = new File(BASE_PROPERTIES_FILE); + this.baseConfigurationFileLastModifiedTime = baseConfiguration.lastModified(); + } + + public void loadCryptoConfigurationSalt() { + try { + FileConfiguration cryptoConfiguration = createFileConfiguration(saltFilePath, true); + + this.cryptoConfigurationSalt = cryptoConfiguration.getString("encodeSalt"); + } catch (Exception ex) { + log.error("Failed to load configuration from {}", saltFilePath, ex); + throw new ConfigurationException("Failed to load configuration from " + saltFilePath, ex); + } + } + + private FileConfiguration createFileConfiguration(String fileName, boolean isMandatory) { + try { + FileConfiguration fileConfiguration = new FileConfiguration(fileName); + + return fileConfiguration; + } catch (Exception ex) { + if (isMandatory) { + log.error("Failed to load configuration from {}", fileName, ex); + throw new ConfigurationException("Failed to load configuration from " + fileName, ex); + } + } + + return null; + } + + +} diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/timer/JansKeycloakLinkTimer.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/timer/JansKeycloakLinkTimer.java new file mode 100644 index 00000000000..13c64c95716 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/timer/JansKeycloakLinkTimer.java @@ -0,0 +1,937 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.keycloak.link.timer; + +import io.jans.keycloak.link.model.CacheCompoundKey; +import io.jans.keycloak.link.model.JansInumMap; +import io.jans.keycloak.link.model.config.AppConfiguration; +import io.jans.keycloak.link.model.config.CacheRefreshConfiguration; +import io.jans.keycloak.link.server.service.CacheRefreshConfigurationService; +import io.jans.keycloak.link.server.service.KeycloakService; +import io.jans.keycloak.link.service.CacheRefreshService; +import io.jans.link.service.CacheRefreshUpdateMethod; +import io.jans.keycloak.link.service.PersonService; +import io.jans.keycloak.link.service.config.ApplicationFactory; +import io.jans.keycloak.link.service.config.ConfigurationFactory; +import io.jans.link.constants.JansConstants; +import io.jans.link.event.CacheRefreshEvent; +import io.jans.link.external.ExternalCacheRefreshService; +import io.jans.link.model.*; +import io.jans.link.service.*; +import io.jans.model.GluuStatus; +import io.jans.model.JansCustomAttribute; +import io.jans.model.custom.script.model.bind.BindCredentials; +import io.jans.model.ldap.GluuLdapConfiguration; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.PersistenceEntryManagerFactory; +import io.jans.orm.exception.BasePersistenceException; +import io.jans.orm.exception.operation.SearchException; +import io.jans.orm.ldap.impl.LdapEntryManagerFactory; +import io.jans.orm.model.SearchScope; +import io.jans.orm.search.filter.Filter; +import io.jans.orm.util.ArrayHelper; +import io.jans.orm.util.StringHelper; +import io.jans.service.ObjectSerializationService; +import io.jans.service.SchemaService; +import io.jans.service.cdi.async.Asynchronous; +import io.jans.service.cdi.event.Scheduled; +import io.jans.service.timer.event.TimerEvent; +import io.jans.service.timer.schedule.TimerSchedule; +import io.jans.util.OxConstants; +import io.jans.util.Pair; +import io.jans.util.security.PropertiesDecrypter; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import org.apache.commons.beanutils.BeanUtilsBean2; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; +import org.slf4j.Logger; + +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Check periodically if source servers contains updates and trigger target + * server entry update if needed + * + * @author Yuriy Movchan Date: 05.05.2011 + */ +@ApplicationScoped +public class JansKeycloakLinkTimer extends BaseJansLinkTimer { + + private static final int DEFAULT_INTERVAL = 60; + + @Inject + private Logger log; + + @Inject + private Event timerEvent; + + @Inject + protected ApplicationFactory applicationFactory; + + @Inject + private ConfigurationFactory configurationFactory; + + @Inject + private CacheRefreshService cacheRefreshService; + + @Inject + private PersonService personService; + + @Inject + private PersistenceEntryManager ldapEntryManager; + + //@Inject + //private ConfigurationService configurationService; + + @Inject + private CacheRefreshSnapshotFileService cacheRefreshSnapshotFileService; + + @Inject + private ExternalCacheRefreshService externalCacheRefreshService; + + @Inject + private SchemaService schemaService; + + @Inject + private InumService inumService; + + @Inject + private AppConfiguration appConfiguration; + + @Inject + private CacheRefreshConfigurationService CacheRefrshConfigurationService; + + @Inject + private EncryptionService encryptionService; + + @Inject + private PairwiseIdService pairwiseIdService; + + @Inject + private FidoDeviceService fidoDeviceService; + + @Inject + private Fido2DeviceService fido2DeviceService; + + @Inject + private ObjectSerializationService objectSerializationService; + + @Inject + private KeycloakService keycloakService; + + private AtomicBoolean isActive; + private long lastFinishedTime; + + public void initTimer() { + log.info("Initializing Jans Keycloak Link Timer"); + this.isActive = new AtomicBoolean(false); + + // Clean up previous Inum cache + CacheRefreshConfiguration cacheRefreshConfiguration = getConfigurationFactory().getAppConfiguration(); + if (cacheRefreshConfiguration != null) { + String snapshotFolder = cacheRefreshConfiguration.getSnapshotFolder(); + if (StringHelper.isNotEmpty(snapshotFolder)) { + String inumCachePath = getInumCachePath(cacheRefreshConfiguration); + objectSerializationService.cleanup(inumCachePath); + } + } + + // Schedule to start cache refresh every 1 minute + timerEvent.fire(new TimerEvent(new TimerSchedule(DEFAULT_INTERVAL, DEFAULT_INTERVAL), new CacheRefreshEvent(), + Scheduled.Literal.INSTANCE)); + + this.lastFinishedTime = System.currentTimeMillis(); + } + + @Asynchronous + public void process(@Observes @Scheduled CacheRefreshEvent cacheRefreshEvent) { + if (this.isActive.get()) { + log.info("Another process is active"); + return; + } + + if (!this.isActive.compareAndSet(false, true)) { + log.info("Failed to start process exclusively"); + return; + } + + try { + } finally { + processInt(); + log.info("Allowing to run new process exclusively"); + this.isActive.set(false); + } + } + + public void processInt() { + log.info("test logger"); + AppConfiguration currentConfiguration = getConfigurationFactory().getAppConfiguration(); + try { + //GluuConfiguration currentConfiguration = getConfigurationService().getConfiguration(); + //GluuConfiguration currentConfiguration = new GluuConfiguration(); + currentConfiguration.setKeycloakLinkEnabled(true); + //currentConfiguration.setVdsCacheRefreshPollingInterval(); + currentConfiguration.setKeycloakLinkServerIpAddress("255.255.255.255"); + if (!isStartCacheRefresh(currentConfiguration)) { + log.info("Starting conditions aren't reached"); + return; + } + + processImpl(currentConfiguration); + updateStatus(currentConfiguration, System.currentTimeMillis()); + + this.lastFinishedTime = System.currentTimeMillis(); + } catch (Throwable ex) { + ex.printStackTrace(); + log.error("Exception happened while executing cache refresh synchronization"+ ex); + } + } + + private boolean isStartCacheRefresh(AppConfiguration currentConfiguration) { + if (!currentConfiguration.isKeycloakLinkEnabled()) { + return false; + } + + long poolingInterval = StringHelper.toInteger(currentConfiguration.getKeycloakLinkPollingInterval()) * 60 * 1000; + if (poolingInterval < 0) { + return false; + } + + String cacheRefreshServerIpAddress = currentConfiguration.getKeycloakLinkServerIpAddress(); + // if (StringHelper.isEmpty(cacheRefreshServerIpAddress)) { + // log.debug("There is no master Cache Refresh server"); + // return false; + // } + + // Compare server IP address with cacheRefreshServerIp + boolean cacheRefreshServer = false; + try { + Enumeration nets = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface networkInterface : Collections.list(nets)) { + Enumeration inetAddresses = networkInterface.getInetAddresses(); + for (InetAddress inetAddress : Collections.list(inetAddresses)) { + if (StringHelper.equals(cacheRefreshServerIpAddress, inetAddress.getHostAddress())) { + cacheRefreshServer = true; + break; + } + } + + if (cacheRefreshServer) { + break; + } + } + } catch (SocketException ex) { + log.error("Failed to enumerate server IP addresses"+ ex); + } + + if (!cacheRefreshServer) { + cacheRefreshServer = externalCacheRefreshService.executeExternalIsStartProcessMethods(); + cacheRefreshServer = true; + } + + if (!cacheRefreshServer) { + log.info("This server isn't master Cache Refresh server"); + return false; + } + + // Check if cache refresh specific configuration was loaded + if (currentConfiguration == null) { + log.info("Failed to start cache refresh. Can't loading configuration from oxTrustCacheRefresh.properties"); + return false; + } + + long timeDiffrence = System.currentTimeMillis() - this.lastFinishedTime; + + return timeDiffrence >= poolingInterval; + } + + private void processImpl(AppConfiguration currentConfiguration) + throws SearchException { + CacheRefreshUpdateMethod updateMethod = getUpdateMethod(currentConfiguration); + + // Prepare and check connections to keycloak servers + log.info("keycloak server connection check ::"); + + Keycloak keycloak = keycloakService.getKeycloakInstance(); + log.info("keycloak server connection check ::" + keycloak); + + LdapServerConnection inumDbServerConnection; + if (currentConfiguration.isDefaultInumServer()) { + GluuLdapConfiguration ldapInumConfiguration = new GluuLdapConfiguration(); + ldapInumConfiguration.setConfigId("local_inum"); + ldapInumConfiguration.setBaseDNsStringsList( + Arrays.asList(new String[] { JansConstants.JANS_KEYCLOAK_LINK_DEFAULT_BASE_DN })); + + inumDbServerConnection = prepareLdapServerConnection(currentConfiguration, ldapInumConfiguration, + true); + } else { + inumDbServerConnection = prepareLdapServerConnection(currentConfiguration, + currentConfiguration.getInumConfig()); + } + + boolean isVdsUpdate = CacheRefreshUpdateMethod.VDS.equals(updateMethod); + LdapServerConnection targetServerConnection = null; + if (isVdsUpdate) { + targetServerConnection = prepareLdapServerConnection(currentConfiguration, + currentConfiguration.getTargetConfig()); + } + + try { + if ((currentConfiguration.getKeycloakConfiguration() == null) || (inumDbServerConnection == null)) { + log.error("Skipping cache refresh due to invalid server configuration"); + } else { + detectChangedEntries(currentConfiguration, null, + inumDbServerConnection, targetServerConnection, updateMethod); + } + } finally { + // Close connections to LDAP servers + if (!currentConfiguration.isDefaultInumServer()) { + try { + closeLdapServerConnection(inumDbServerConnection); + } catch (Exception e) { + log.error("Failed to close ldap connection "+ e); + // Nothing can be done + } + } + try { + if (isVdsUpdate) { + closeLdapServerConnection(targetServerConnection); + } + } catch (Exception e) { + log.error("Failed to close ldap connection "+ e); + // Nothing can be done + } + } + + return; + } + + @SuppressWarnings("unchecked") + private boolean detectChangedEntries( AppConfiguration currentConfiguration, LdapServerConnection[] sourceServerConnections, + LdapServerConnection inumDbServerConnection, LdapServerConnection targetServerConnection, + CacheRefreshUpdateMethod updateMethod) throws SearchException { + boolean isVDSMode = CacheRefreshUpdateMethod.VDS.equals(updateMethod); + + // Load all entries from Source servers + log.info("Attempting to load entries from source server"); + List sourceUserRepresentation = keycloakService.getUserList(); + log.info("Found '{}' entries in source source User Representation from keycloak : "+ sourceUserRepresentation.size()); + + List sourcePersons = loadSourceServerKeycloakEntriesWithoutLimits(currentConfiguration, sourceServerConnections,sourceUserRepresentation); + + log.info("Found '{}' entries in source server sourcePersons : "+ sourcePersons.size()); + + Map sourceUserRepresentPersonCacheCompoundKeyMap = getSourcePersonCompoundKeyMap( + currentConfiguration, sourcePersons); + log.info("Found '{}' unique entries in source server"+ sourceUserRepresentPersonCacheCompoundKeyMap.size()); + + // Load all inum entries + List inumMaps = null; + + // Load all inum entries from local disk cache + String inumCachePath = getInumCachePath(currentConfiguration); + Object loadedObject = objectSerializationService.loadObject(inumCachePath); + if (loadedObject != null) { + try { + inumMaps = (List) loadedObject; + log.info("Found '{}' entries in inum objects disk cache"+ inumMaps.size()); + } catch (Exception ex) { + log.error("Failed to convert to GluuInumMap list"+ ex); + objectSerializationService.cleanup(inumCachePath); + } + } + + if (inumMaps == null) { + // Load all inum entries from LDAP + inumMaps = loadInumServerEntries(currentConfiguration, inumDbServerConnection); + log.info("Found '{}' entries in inum server"+ inumMaps.size()); + } + + HashMap primaryKeyAttrValueInumMap = getPrimaryKeyAttrValueInumMap(inumMaps); + + // Go through Source entries and create new InumMap entries if needed + HashMap addedPrimaryKeyAttrValueInumMap = addNewInumServerEntries( + currentConfiguration, inumDbServerConnection, sourceUserRepresentPersonCacheCompoundKeyMap, + primaryKeyAttrValueInumMap); + + HashMap allPrimaryKeyAttrValueInumMap = getAllInumServerEntries( + primaryKeyAttrValueInumMap, addedPrimaryKeyAttrValueInumMap); + log.info("Count actual inum entries '{}' after updating inum server"+ allPrimaryKeyAttrValueInumMap.size()); + + HashMap currInumWithEntryHashCodeMap = getSourcePersonsHashCodesMap(inumDbServerConnection, + sourceUserRepresentPersonCacheCompoundKeyMap, allPrimaryKeyAttrValueInumMap); + log.info("Count actual source entries '{}' after calculating hash code"+ currInumWithEntryHashCodeMap.size()); + + // Create snapshots cache folder if needed + boolean result = cacheRefreshSnapshotFileService.prepareSnapshotsFolder(currentConfiguration); + if (!result) { + return false; + } + + // Load last snapshot into memory + Map prevInumWithEntryHashCodeMap = cacheRefreshSnapshotFileService + .readLastSnapshot(currentConfiguration); + + // Compare 2 snapshot and invoke update if needed + Set changedInums = getChangedInums(currInumWithEntryHashCodeMap, prevInumWithEntryHashCodeMap, + isVDSMode); + log.info("Found '{}' changed entries"+ changedInums.size()); + + // Load problem list from disk and add to changedInums + List problemInums = cacheRefreshSnapshotFileService.readProblemList(currentConfiguration); + if (problemInums != null) { + log.info("Loaded '{}' problem entries from problem file"+ problemInums.size()); + // Process inums from problem list too + changedInums.addAll(problemInums); + } + + List updatedInums = null; + if (isVDSMode) { + // Update request to VDS to update entries on target server + updatedInums = updateTargetEntriesViaVDS(currentConfiguration, targetServerConnection, changedInums); + } else { + updatedInums = updateTargetEntriesViaCopy(currentConfiguration, sourceUserRepresentPersonCacheCompoundKeyMap, + allPrimaryKeyAttrValueInumMap, changedInums); + } + + log.info("Updated '{}' entries"+ updatedInums.size()); + changedInums.removeAll(updatedInums); + log.info("Failed to update '{}' entries"+ changedInums.size()); + + // Persist snapshot to cache folder + result = cacheRefreshSnapshotFileService.createSnapshot(currentConfiguration, + currInumWithEntryHashCodeMap); + if (!result) { + return false; + } + + // Retain only specified number of snapshots + cacheRefreshSnapshotFileService.retainSnapshots(currentConfiguration, + currentConfiguration.getSnapshotMaxCount()); + + // Save changedInums as problem list to disk + currentConfiguration.setKeycloakLinkProblemCount(String.valueOf(changedInums.size())); + cacheRefreshSnapshotFileService.writeProblemList(currentConfiguration, changedInums); + + // Prepare list of persons for removal + List personsForRemoval = null; + + boolean keepExternalPerson = currentConfiguration.isKeepExternalPerson(); + log.info("Keep external persons: '{}'"+ keepExternalPerson); + if (keepExternalPerson) { + // Determine entries which need to remove + personsForRemoval = getRemovedPersons(currInumWithEntryHashCodeMap, prevInumWithEntryHashCodeMap); + } else { + // Process entries which don't exist in source server + + // Load all entries from Target server + List targetPersons = loadTargetServerEntries(currentConfiguration, getLdapEntryManager()); + log.info("Found '{}' entries in target server"+ targetPersons.size()); + + // Detect entries which need to remove + personsForRemoval = processTargetPersons(targetPersons, currInumWithEntryHashCodeMap); + } + log.info("Count entries '{}' for removal from target server"+ personsForRemoval.size()); + + // Remove entries from target server + HashMap inumInumMap = getInumInumMap(inumMaps); + Pair, List> removeTargetEntriesResult = removeTargetEntries(inumDbServerConnection, + getLdapEntryManager(), personsForRemoval, inumInumMap); + List removedPersonInums = removeTargetEntriesResult.getFirst(); + List removedGluuInumMaps = removeTargetEntriesResult.getSecond(); + log.info("Removed '{}' persons from target server"+ removedPersonInums.size()); + + // Prepare list of inum for serialization + ArrayList currentInumMaps = applyChangesToInumMap(inumInumMap, addedPrimaryKeyAttrValueInumMap, + removedGluuInumMaps); + + // Strore all inum entries into local disk cache + objectSerializationService.saveObject(inumCachePath, currentInumMaps); + + currentConfiguration + .setKeycloakLinkLastUpdateCount(String.valueOf(updatedInums.size() + removedPersonInums.size())); + + return true; + } + + private Pair, List> removeTargetEntries(LdapServerConnection inumDbServerConnection, + PersistenceEntryManager targetPersistenceEntryManager, List removedPersons, + HashMap inumInumMap) { + + Date runDate = new Date(this.lastFinishedTime); + + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + List result1 = new ArrayList(); + List result2 = new ArrayList(); + + for (GluuSimplePerson removedPerson : removedPersons) { + String inum = removedPerson.getStringAttribute(JansConstants.inum); + + // Update GluuInumMap if it exist + JansInumMap currentInumMap = inumInumMap.get(inum); + if (currentInumMap == null) { + log.warn("Can't find inum entry of person with DN: {}"+ removedPerson.getDn()); + } else { + JansInumMap removedInumMap = getMarkInumMapEntryAsRemoved(currentInumMap, + getLdapEntryManager().encodeTime(removedPerson.getDn(), runDate)); + try { + inumDbPersistenceEntryManager.merge(removedInumMap); + result2.add(removedInumMap.getInum()); + } catch (BasePersistenceException ex) { + log.error("Failed to update entry with inum '{}' and DN: {}"+ currentInumMap.getInum(), + currentInumMap.getDn(), ex); + continue; + } + } + + // Remove person from target server + try { + //ldap ORM + if(targetPersistenceEntryManager.hasBranchesSupport(removedPerson.getDn())){ + targetPersistenceEntryManager.removeRecursively(removedPerson.getDn(), GluuCustomPerson.class); + + }else { + //other ORM + targetPersistenceEntryManager.remove(removedPerson.getDn(), GluuCustomPerson.class); + + Filter pairwiseIdentifiersFilter = Filter.createEqualityFilter(JansConstants.oxAuthUserId, removedPerson.getDn()); + targetPersistenceEntryManager.remove(pairwiseIdService.getDnForPairWiseIdentifier(null, removedPerson.getDn()), GluuUserPairwiseIdentifier.class, pairwiseIdentifiersFilter,0); + + Filter equalityFilter = Filter.createEqualityFilter("personInum", removedPerson.getDn()); + targetPersistenceEntryManager.remove(fidoDeviceService.getDnForFidoDevice(removedPerson.getDn(),null), GluuCustomFidoDevice.class, equalityFilter,0); + + Filter equalityFido2DeviceFilter = Filter.createEqualityFilter("personInum", removedPerson.getDn()); + targetPersistenceEntryManager.remove(fido2DeviceService.getDnForFido2Device(null, removedPerson.getDn()), GluuFido2Device.class, equalityFido2DeviceFilter,0); + } + result1.add(inum); + } catch (BasePersistenceException ex) { + log.error("Failed to remove person entry with inum '{}' and DN: {}"+ inum, removedPerson.getDn(), ex); + continue; + } + + log.info("Person with DN: '{}' removed from target server"+ removedPerson.getDn()); + } + + return new Pair, List>(result1, result2); + } + + private JansInumMap getMarkInumMapEntryAsRemoved(JansInumMap currentInumMap, String date) { + JansInumMap clonedInumMap; + try { + clonedInumMap = (JansInumMap) BeanUtilsBean2.getInstance().cloneBean(currentInumMap); + } catch (Exception ex) { + log.error("Failed to prepare GluuInumMap for removal"+ ex); + return null; + } + + String suffix = "-" + date; + + String primaryKeyValues = clonedInumMap.getPrimaryKeyValues(); + String secondaryKeyValues = clonedInumMap.getSecondaryKeyValues(); + String tertiaryKeyValues = clonedInumMap.getTertiaryKeyValues(); + + if (StringHelper.isNotEmpty(primaryKeyValues)) { + markInumMapEntryKeyValuesAsRemoved(primaryKeyValues, suffix); + } + + if (StringHelper.isNotEmpty(secondaryKeyValues)) { + markInumMapEntryKeyValuesAsRemoved(secondaryKeyValues, suffix); + } + + if (StringHelper.isNotEmpty(tertiaryKeyValues)) { + markInumMapEntryKeyValuesAsRemoved(tertiaryKeyValues, suffix); + } + + clonedInumMap.setPrimaryKeyValues(primaryKeyValues); + clonedInumMap.setSecondaryKeyValues(secondaryKeyValues); + clonedInumMap.setTertiaryKeyValues(tertiaryKeyValues); + + clonedInumMap.setStatus(GluuStatus.INACTIVE); + + return clonedInumMap; + } + + private void markInumMapEntryKeyValuesAsRemoved(String keyValues, String suffix) { + //for (int i = 0; i < keyValues.length; i++) { + keyValues = keyValues + suffix; + + } + + private HashMap addNewInumServerEntries( + CacheRefreshConfiguration cacheRefreshConfiguration, LdapServerConnection inumDbServerConnection, + Map sourcePersonCacheCompoundKeyMap, + HashMap primaryKeyAttrValueInumMap) { + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + String inumbaseDn = inumDbServerConnection.getBaseDns()[0]; + + HashMap result = new HashMap(); + + String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); + for (Entry sourcePersonCacheCompoundKeyEntry : sourcePersonCacheCompoundKeyMap + .entrySet()) { + CacheCompoundKey cacheCompoundKey = sourcePersonCacheCompoundKeyEntry.getKey(); + GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyEntry.getValue(); + + if (log.isTraceEnabled()) { + log.trace("Checking source entry with key: '{}', and DN: {}"+ cacheCompoundKey, sourcePerson.getDn()); + } + + JansInumMap currentInumMap = primaryKeyAttrValueInumMap.get(cacheCompoundKey); + if (currentInumMap == null) { + String[] keyAttributesValues = getKeyAttributesValues(keyAttributesWithoutValues, sourcePerson); + currentInumMap = addGluuInumMap(inumbaseDn, inumDbPersistenceEntryManager, keyAttributesWithoutValues, + keyAttributesValues); + result.put(cacheCompoundKey, currentInumMap); + log.info("Added new inum entry for DN: {}"+ sourcePerson.getDn()); + } else { + log.trace("Inum entry for DN: '{}' exist"+ sourcePerson.getDn()); + } + } + + return result; + } + + private Map getSourcePersonCompoundKeyMap( + CacheRefreshConfiguration cacheRefreshConfiguration, List sourcePersons) { + Map result = new HashMap(); + Set duplicateKeys = new HashSet(); + + String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); + for (GluuSimplePerson sourcePerson : sourcePersons) { + String[] keyAttributesValues = getKeyAttributesValues(keyAttributesWithoutValues, sourcePerson); + CacheCompoundKey cacheCompoundKey = new CacheCompoundKey(keyAttributesValues); + + if (result.containsKey(cacheCompoundKey)) { + duplicateKeys.add(cacheCompoundKey); + } + + result.put(cacheCompoundKey, sourcePerson); + } + + for (CacheCompoundKey duplicateKey : duplicateKeys) { + log.error("Non-deterministic primary key. Skipping user with key: {}"+ duplicateKey); + result.remove(duplicateKey); + } + + return result; + } + + + private LdapServerConnection[] prepareLdapServerConnections(CacheRefreshConfiguration cacheRefreshConfiguration, + List ldapConfigurations) { + LdapServerConnection[] ldapServerConnections = new LdapServerConnection[ldapConfigurations.size()]; + for (int i = 0; i < ldapConfigurations.size(); i++) { + ldapServerConnections[i] = prepareLdapServerConnection(cacheRefreshConfiguration, + ldapConfigurations.get(i)); + if (ldapServerConnections[i] == null) { + return null; + } + } + + return ldapServerConnections; + } + + private LdapServerConnection prepareLdapServerConnection(CacheRefreshConfiguration cacheRefreshConfiguration, + GluuLdapConfiguration ldapConfiguration) { + return prepareLdapServerConnection(cacheRefreshConfiguration, ldapConfiguration, false); + } + + private LdapServerConnection prepareLdapServerConnection(CacheRefreshConfiguration cacheRefreshConfiguration, + GluuLdapConfiguration ldapConfiguration, boolean useLocalConnection) { + String ldapConfig = ldapConfiguration.getConfigId(); + + if (useLocalConnection) { + return new LdapServerConnection(ldapConfig, getLdapEntryManager(), getBaseDNs(ldapConfiguration)); + } + PersistenceEntryManagerFactory entryManagerFactory = applicationFactory + .getPersistenceEntryManagerFactory(LdapEntryManagerFactory.class); + String persistenceType = entryManagerFactory.getPersistenceType(); + + Properties ldapProperties = toLdapProperties(entryManagerFactory, ldapConfiguration); + Properties ldapDecryptedProperties = encryptionService.decryptAllProperties(ldapProperties); + + // Try to get updated password via script + BindCredentials bindCredentials = externalCacheRefreshService + .executeExternalGetBindCredentialsMethods(ldapConfig); + String bindPasswordPropertyKey = persistenceType + "#" + PropertiesDecrypter.BIND_PASSWORD; + if (bindCredentials != null) { + log.error("Using updated password which got from getBindCredentials method"); + ldapDecryptedProperties.setProperty(persistenceType + ".bindDN", bindCredentials.getBindDn()); + ldapDecryptedProperties.setProperty(bindPasswordPropertyKey, + bindCredentials.getBindPassword()); + } + + if (log.isTraceEnabled()) { + Properties clonedLdapDecryptedProperties = (Properties) ldapDecryptedProperties.clone(); + if (clonedLdapDecryptedProperties.getProperty(bindPasswordPropertyKey) != null) { + clonedLdapDecryptedProperties.setProperty(bindPasswordPropertyKey, "REDACTED"); + } + log.trace("Attempting to create PersistenceEntryManager with properties: {}"+ clonedLdapDecryptedProperties); + } + PersistenceEntryManager customPersistenceEntryManager = entryManagerFactory + .createEntryManager(ldapDecryptedProperties); + log.info("Created Cache Refresh PersistenceEntryManager: {}"+ customPersistenceEntryManager); + + if (!customPersistenceEntryManager.getOperationService().isConnected()) { + log.error("Failed to connect to LDAP server using configuration {}"+ ldapConfig); + return null; + } + + return new LdapServerConnection(ldapConfig, customPersistenceEntryManager, getBaseDNs(ldapConfiguration)); + } + + private String[] getKeyAttributesValues(String[] attrs, GluuSimplePerson person) { + String[] result = new String[attrs.length]; + try { + for (int i = 0; i < attrs.length; i++) { + //result[i] = (String) PropertyUtils.getProperty(person, attrs[i]); + result[i] = person.getStringAttributes(attrs[i])[0]; + } + } catch (Exception e) { + e.printStackTrace(); + log.error("Failed to close ldap connection "+ e.getMessage()); + } + + return result; + } + + private void updateStatus(AppConfiguration currentConfiguration, long lastRun) { + Date currentDateTime = new Date(); + currentConfiguration.setKeycloakLinkLastUpdate(currentDateTime); + currentConfiguration.setKeycloakLinkLastUpdateCount(currentConfiguration.getKeycloakLinkLastUpdateCount()); + currentConfiguration.setKeycloakLinkProblemCount(currentConfiguration.getKeycloakLinkProblemCount()); + CacheRefrshConfigurationService.updateConfiguration(currentConfiguration); + } + + public ConfigurationFactory getConfigurationFactory() { + return configurationFactory; + } + + public void setConfigurationFactory(ConfigurationFactory configurationFactory) { + this.configurationFactory = configurationFactory; + } + + private List loadSourceServerKeycloakEntriesWithoutLimits( + CacheRefreshConfiguration cacheRefreshConfiguration, LdapServerConnection[] sourceServerConnections, + List sourceUserRepresentations){ + List sourceJansSimplePerson = new ArrayList(); + try { + Filter customFilter = cacheRefreshService.createFilter(cacheRefreshConfiguration.getCustomLdapFilter()); + String[] keyAttributes = getCompoundKeyAttributes(cacheRefreshConfiguration); + String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); + String[] keyObjectClasses = getCompoundKeyObjectClasses(cacheRefreshConfiguration); + String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration); + + String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes); + + Set addedDns = new HashSet(); + + for (UserRepresentation userRepresentation : sourceUserRepresentations) { + GluuSimplePerson jansSimplePerson = new GluuSimplePerson(); + for (String attr : keyAttributes) { + JansCustomAttribute jansCustomAttribute = new JansCustomAttribute(); + jansCustomAttribute.setName(attr); + Field f = userRepresentation.getClass().getDeclaredField(attr); + f.setAccessible(true); + jansCustomAttribute.setValue((String) f.get(userRepresentation)); + + jansSimplePerson.getCustomAttributes().add(jansCustomAttribute); + } + for (String attr : sourceAttributes) { + JansCustomAttribute jansCustomAttribute = new JansCustomAttribute(); + jansCustomAttribute.setName(attr); + Field f = userRepresentation.getClass().getDeclaredField(attr); + f.setAccessible(true); + jansCustomAttribute.setValue((String) f.get(userRepresentation)); + + jansSimplePerson.getCustomAttributes().add(jansCustomAttribute); + } + jansSimplePerson.setDn(personService.getDnForPerson(personService.generateInumForNewPersonImpl())); + + sourceJansSimplePerson.add(jansSimplePerson); + + } + }catch(SearchException| NoSuchFieldException| IllegalAccessException e){ + log.error("Exception : "+ e.getMessage()); + e.printStackTrace(); + } + + return sourceJansSimplePerson; + } + private JansInumMap addGluuInumMap(String inumbBaseDn, PersistenceEntryManager inumDbPersistenceEntryManager, + String[] primaryKeyAttrName, String[] primaryKeyValues) { + String inum = cacheRefreshService.generateInumForNewInumMap(inumbBaseDn, inumDbPersistenceEntryManager); + String inumDn = cacheRefreshService.getDnForInum(inumbBaseDn, inum); + + JansInumMap inumMap = new JansInumMap(); + inumMap.setDn(inumDn); + inumMap.setInum(inum); + inumMap.setPrimaryKeyAttrName(primaryKeyAttrName[0]); + inumMap.setPrimaryKeyValues(primaryKeyValues[0]); + if (primaryKeyAttrName.length > 1) { + inumMap.setSecondaryKeyAttrName(primaryKeyAttrName[1]); + inumMap.setSecondaryKeyValues(primaryKeyValues[1]); + } + if (primaryKeyAttrName.length > 2) { + inumMap.setTertiaryKeyAttrName(primaryKeyAttrName[2]); + inumMap.setTertiaryKeyValues(primaryKeyValues[2]); + } + inumMap.setStatus(GluuStatus.ACTIVE); + cacheRefreshService.addInumMap(inumDbPersistenceEntryManager, inumMap); + + return inumMap; + } + + public AtomicBoolean getIsActive() { + return isActive; + } + + public void setIsActive(AtomicBoolean isActive) { + this.isActive = isActive; + } + + public List loadInumServerEntries(io.jans.link.model.config.shared.CacheRefreshConfiguration cacheRefreshConfiguration, + LdapServerConnection inumDbServerConnection) { + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + String inumbaseDn = inumDbServerConnection.getBaseDns()[0]; + + Filter filterObjectClass = Filter.createEqualityFilter(OxConstants.OBJECT_CLASS, + JansConstants.objectClassInumMap); + Filter filterStatus = Filter.createNOTFilter( + Filter.createEqualityFilter(JansConstants.jansStatus, GluuStatus.INACTIVE.getValue())); + Filter filter = Filter.createANDFilter(filterObjectClass, filterStatus); + + return inumDbPersistenceEntryManager.findEntries(inumbaseDn, io.jans.keycloak.link.model.JansInumMap.class, filter, SearchScope.SUB, null, + null, 0, 0, cacheRefreshConfiguration.getLdapSearchSizeLimit()); + } + + private HashMap getAllInumServerEntries( + HashMap primaryKeyAttrValueInumMap, + HashMap addedPrimaryKeyAttrValueInumMap) { + HashMap result = new HashMap(); + + result.putAll(primaryKeyAttrValueInumMap); + result.putAll(addedPrimaryKeyAttrValueInumMap); + + return result; + } + + private HashMap getPrimaryKeyAttrValueInumMap(List inumMaps) { + HashMap result = new HashMap(); + + for (io.jans.keycloak.link.model.JansInumMap inumMap : inumMaps) { + result.put(new io.jans.keycloak.link.model.CacheCompoundKey(inumMap.getPrimaryKeyValues(), inumMap.getSecondaryKeyValues(), + inumMap.getTertiaryKeyValues()), inumMap); + } + + return result; + } + + public HashMap getSourcePersonsHashCodesMap(LdapServerConnection inumDbServerConnection, + Map sourcePersonCacheCompoundKeyMap, + HashMap primaryKeyAttrValueInumMap) { + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + + HashMap result = new HashMap(); + + for (Map.Entry sourcePersonCacheCompoundKeyEntry : sourcePersonCacheCompoundKeyMap + .entrySet()) { + io.jans.keycloak.link.model.CacheCompoundKey cacheCompoundKey = sourcePersonCacheCompoundKeyEntry.getKey(); + GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyEntry.getValue(); + + io.jans.keycloak.link.model.JansInumMap currentInumMap = primaryKeyAttrValueInumMap.get(cacheCompoundKey); + + result.put(currentInumMap.getInum(), inumDbPersistenceEntryManager.getHashCode(sourcePerson)); + } + + return result; + } + + public List updateTargetEntriesViaCopy(io.jans.link.model.config.shared.CacheRefreshConfiguration cacheRefreshConfiguration, + Map sourcePersonCacheCompoundKeyMap, + HashMap primaryKeyAttrValueInumMap, Set changedInums) { + HashMap inumCacheCompoundKeyMap = getInumCacheCompoundKeyMap( + primaryKeyAttrValueInumMap); + Map targetServerAttributesMapping = getTargetServerAttributesMapping(cacheRefreshConfiguration); + String[] customObjectClasses = appConfiguration.getPersonObjectClassTypes(); + + List result = new ArrayList(); + + if (!validateTargetServerSchema(cacheRefreshConfiguration, targetServerAttributesMapping, + customObjectClasses)) { + return result; + } + + for (String targetInum : changedInums) { + io.jans.keycloak.link.model.CacheCompoundKey compoundKey = inumCacheCompoundKeyMap.get(targetInum); + if (compoundKey == null) { + continue; + } + + GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyMap.get(compoundKey); + if (sourcePerson == null) { + continue; + } + + if (updateTargetEntryViaCopy(sourcePerson, targetInum, customObjectClasses, + targetServerAttributesMapping)) { + result.add(targetInum); + } + } + + return result; + } + + public HashMap getInumInumMap(List inumMaps) { + HashMap result = new HashMap(); + + for (io.jans.keycloak.link.model.JansInumMap inumMap : inumMaps) { + result.put(inumMap.getInum(), inumMap); + } + + return result; + } + + public ArrayList applyChangesToInumMap(HashMap inumInumMap, + HashMap addedPrimaryKeyAttrValueInumMap, List removedGluuInumMaps) { + log.info("There are '{}' entries before updating inum list"+ inumInumMap.size()); + for (String removedGluuInumMap : removedGluuInumMaps) { + inumInumMap.remove(removedGluuInumMap); + } + log.info("There are '{}' entries after removal '{}' entries" + inumInumMap.size() +" : " +removedGluuInumMaps.size()); + + ArrayList currentInumMaps = new ArrayList(inumInumMap.values()); + currentInumMaps.addAll(addedPrimaryKeyAttrValueInumMap.values()); + log.info("There are '{}' entries after adding '{}' entries"+ currentInumMaps.size()+" : " + + addedPrimaryKeyAttrValueInumMap.size()); + + return currentInumMaps; + } + + public HashMap getInumCacheCompoundKeyMap( + HashMap primaryKeyAttrValueInumMap) { + HashMap result = new HashMap(); + + for (Map.Entry primaryKeyAttrValueInumMapEntry : primaryKeyAttrValueInumMap + .entrySet()) { + result.put(primaryKeyAttrValueInumMapEntry.getValue().getInum(), primaryKeyAttrValueInumMapEntry.getKey()); + } + + return result; + } + + +} \ No newline at end of file diff --git a/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/ws/rs/HealthCheckController.java b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/ws/rs/HealthCheckController.java new file mode 100644 index 00000000000..f4a17509f51 --- /dev/null +++ b/jans-keycloak-link/server/src/main/java/io/jans/keycloak/link/ws/rs/HealthCheckController.java @@ -0,0 +1,42 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.ws.rs; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import io.jans.orm.PersistenceEntryManager; + +/** + * Health check controller + * + * @author Yuriy Movchan + * @version Jul 24, 2020 + */ +@ApplicationScoped +@Path("/") +public class HealthCheckController { + + @Inject + private PersistenceEntryManager persistenceEntryManager; + + @GET + @POST + @Path("/health-check") + @Produces(MediaType.APPLICATION_JSON) + public String healthCheckController() { + boolean isConnected = persistenceEntryManager.getOperationService().isConnected(); + String dbStatus = isConnected ? "online" : "offline"; + return "{\"status\": \"running\", \"db_status\":\"" + dbStatus + "\"}"; + } + +} diff --git a/jans-keycloak-link/server/src/main/resources/META-INF/beans.xml b/jans-keycloak-link/server/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..ddc64918ba0 --- /dev/null +++ b/jans-keycloak-link/server/src/main/resources/META-INF/beans.xml @@ -0,0 +1,6 @@ + + + diff --git a/jans-keycloak-link/server/src/main/resources/log4j2.xml b/jans-keycloak-link/server/src/main/resources/log4j2.xml new file mode 100644 index 00000000000..15477256c37 --- /dev/null +++ b/jans-keycloak-link/server/src/main/resources/log4j2.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jans-keycloak-link/server/src/main/resources/quartz.properties b/jans-keycloak-link/server/src/main/resources/quartz.properties new file mode 100644 index 00000000000..12cef917f4a --- /dev/null +++ b/jans-keycloak-link/server/src/main/resources/quartz.properties @@ -0,0 +1,4 @@ +org.quartz.scheduler.instanceName=JansConfigScheduler +org.quartz.threadPool.threadCount=5 +org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore +org.quartz.scheduler.skipUpdateCheck=true diff --git a/jans-keycloak-link/server/src/main/webapp/WEB-INF/jetty-env.xml b/jans-keycloak-link/server/src/main/webapp/WEB-INF/jetty-env.xml new file mode 100644 index 00000000000..5760ddc83dd --- /dev/null +++ b/jans-keycloak-link/server/src/main/webapp/WEB-INF/jetty-env.xml @@ -0,0 +1,15 @@ + + + + + + BeanManager + + + jakarta.enterprise.inject.spi.BeanManager + org.jboss.weld.resources.ManagerObjectFactory + + + + + \ No newline at end of file diff --git a/jans-keycloak-link/server/src/main/webapp/WEB-INF/jetty-web.xml b/jans-keycloak-link/server/src/main/webapp/WEB-INF/jetty-web.xml new file mode 100644 index 00000000000..01ab2b18c8e --- /dev/null +++ b/jans-keycloak-link/server/src/main/webapp/WEB-INF/jetty-web.xml @@ -0,0 +1,29 @@ + + + + + + + + -org.eclipse.jetty.util.Decorator + + + -org.eclipse.jetty.util.DecoratedObjectFactory + + + -org.eclipse.jetty.server.handler.ContextHandler. + + + -org.eclipse.jetty.server.handler.ContextHandler + + + -org.eclipse.jetty.servlet.ServletContextHandler + + + + + + \ No newline at end of file diff --git a/jans-keycloak-link/server/src/main/webapp/WEB-INF/web.xml b/jans-keycloak-link/server/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..637cc82d9ef --- /dev/null +++ b/jans-keycloak-link/server/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,43 @@ + + + Jans Keycloak Migration server + + + + resteasy.patchfilter.disabled + true + + + + + org.eclipse.jetty.servlet.Default.dirAllowed + false + + + + + org.jboss.weld.development + true + + + + org.jboss.weld.environment.servlet.Listener + + + + + org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap + + + + + Object factory for the CDI Bean Manager + BeanManager + jakarta.enterprise.inject.spi.BeanManager + + + + diff --git a/jans-keycloak-link/server/src/main/webapp/index.jsp b/jans-keycloak-link/server/src/main/webapp/index.jsp new file mode 100644 index 00000000000..c38169bb958 --- /dev/null +++ b/jans-keycloak-link/server/src/main/webapp/index.jsp @@ -0,0 +1,5 @@ + + +

Hello World!

+ + diff --git a/jans-keycloak-link/service/pom.xml b/jans-keycloak-link/service/pom.xml new file mode 100644 index 00000000000..f79e43444e1 --- /dev/null +++ b/jans-keycloak-link/service/pom.xml @@ -0,0 +1,38 @@ + + + jans-keycloak-link-parent + io.jans + 1.0.19-SNAPSHOT + + 4.0.0 + + jans-keycloak-link-service + jar + + jans-keycloak-link-service + http://maven.apache.org + + + UTF-8 + + + + + io.jans + jans-keycloak-link-model + ${project.version} + + + io.jans + jans-link-service + ${project.version} + + + junit + junit + 3.8.1 + test + + + diff --git a/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/CacheRefreshService.java b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/CacheRefreshService.java new file mode 100644 index 00000000000..aaa190c02f5 --- /dev/null +++ b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/CacheRefreshService.java @@ -0,0 +1,191 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.keycloak.link.service; + +import io.jans.link.constants.JansConstants; +import io.jans.link.model.GluuCustomPerson; +import io.jans.link.model.GluuSimplePerson; +import io.jans.keycloak.link.model.JansInumMap; +import io.jans.link.service.InumService; +import io.jans.model.JansCustomAttribute; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.exception.operation.SearchException; +import io.jans.orm.ldap.impl.LdapFilterConverter; +import io.jans.orm.search.filter.Filter; +import io.jans.orm.util.ArrayHelper; +import io.jans.util.OxConstants; +import io.jans.util.StringHelper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.slf4j.Logger; + +import java.util.*; +import java.util.Map.Entry; + +/** + * Provides cache refresh related operations + * + * @author Yuriy Movchan Date: 07.04.2011 + */ +@ApplicationScoped +public class CacheRefreshService{ + + public CacheRefreshService() { + } + + @Inject + private Logger log; + + @Inject + private LdapFilterConverter ldapFilterConverter; + + @Inject + private InumService inumService; + + public Filter createFilter(String customLdapFilter) throws SearchException { + if (StringHelper.isEmpty(customLdapFilter)) { + return null; + } + + return ldapFilterConverter.convertRawLdapFilterToFilter(customLdapFilter); + } + + public Filter createFilter(String[] keyAttributes, String[] keyObjectClasses, String keyAttributeStart, Filter customFilter) throws SearchException { + if ((keyAttributes == null) || (keyObjectClasses == null)) { + return null; + } + + List filters = new ArrayList(); + for (int i = 0; i < keyAttributes.length; i++) { + String filterString = keyAttributes[i]; + + if (filterString.contains("=")) { + filters.add(ldapFilterConverter.convertRawLdapFilterToFilter(filterString)); + // } else { + // filters.add(Filter.createPresenceFilter(filterString)); + } + + // Limit result list + if ((i == 0) && (keyAttributeStart != null)) { + int index = filterString.indexOf('='); + if (index != -1) { + filterString = filterString.substring(0, index); + } + + filterString = String.format("%s=%s*", filterString, keyAttributeStart); + filters.add(ldapFilterConverter.convertRawLdapFilterToFilter(filterString)); + } + } + + for (String keyObjectClass : keyObjectClasses) { + filters.add(Filter.createEqualityFilter(OxConstants.OBJECT_CLASS, keyObjectClass)); + } + + if (customFilter != null) { + filters.add(customFilter); + } + + return Filter.createANDFilter(filters); + } + + public Filter createObjectClassPresenceFilter() { + return Filter.createPresenceFilter(OxConstants.OBJECT_CLASS); + } + + public void addInumMap(PersistenceEntryManager ldapEntryManager, JansInumMap inumMap) { + ldapEntryManager.persist(inumMap); + } + + public boolean containsInumMap(PersistenceEntryManager ldapEntryManager, String dn) { + return ldapEntryManager.contains(dn, JansInumMap.class); + } + + public String generateInumForNewInumMap(String inumbBaseDn, PersistenceEntryManager ldapEntryManager) { + String newInum; + String newDn; + do { + newInum = generateInumForNewInumMapImpl(); + newDn = getDnForInum(inumbBaseDn, newInum); + } while (containsInumMap(ldapEntryManager, newDn)); + + return newInum; + } + + public String getDnForInum(String baseDn, String inum) { + return String.format("inum=%s,%s", inum, baseDn); + } + + private String generateInumForNewInumMapImpl() { + String inum = inumService.generateInums(JansConstants.INUM_TYPE_PEOPLE_SLUG); + return inum; + } + + public void setTargetEntryAttributes(GluuSimplePerson sourcePerson, Map targetServerAttributesMapping, + GluuCustomPerson targetPerson) { + //try { + // Collect all attributes to single map + Map customAttributesMap = new HashMap(); + for (JansCustomAttribute sourceCustomAttribute : sourcePerson.getCustomAttributes()) { + customAttributesMap.put(StringHelper.toLowerCase(sourceCustomAttribute.getName()), sourceCustomAttribute); + } + + List resultAttributes = new ArrayList(); + + // Add attributes configured via mapping + Set processedAttributeNames = new HashSet<>(); + + + + + for (Entry targetServerAttributeEntry : targetServerAttributesMapping.entrySet()) { + String sourceKeyAttributeName = StringHelper.toLowerCase(targetServerAttributeEntry.getValue()); + String targetKeyAttributeName = targetServerAttributeEntry.getKey(); + + processedAttributeNames.add(sourceKeyAttributeName); + + + + JansCustomAttribute gluuCustomAttribute = customAttributesMap.get(sourceKeyAttributeName); + if (gluuCustomAttribute != null) { + String[] values = gluuCustomAttribute.getStringValues(); + String[] clonedValue = ArrayHelper.arrayClone(values); + + //String[] clonedValue = new String[0]; + + //clonedValue = (String[]) PropertyUtils.getProperty(sourcePerson, targetKeyAttributeName); + + + JansCustomAttribute gluuCustomAttributeCopy = new JansCustomAttribute(targetKeyAttributeName, clonedValue); + gluuCustomAttributeCopy.setName(targetKeyAttributeName); + resultAttributes.add(gluuCustomAttributeCopy); + } + } + + // Set destination entry attributes + for (Entry sourceCustomAttributeEntry : customAttributesMap.entrySet()) { + if (!processedAttributeNames.contains(sourceCustomAttributeEntry.getKey())) { + targetPerson.setAttribute(sourceCustomAttributeEntry.getValue()); + } + } + + for (JansCustomAttribute resultAttribute : resultAttributes) { + targetPerson.setAttribute(resultAttribute); + } + /*} catch (IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw new RuntimeException(e); + }*/ + + } + +} diff --git a/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/CacheRefreshUpdateMethod.java b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/CacheRefreshUpdateMethod.java new file mode 100644 index 00000000000..da36bd40685 --- /dev/null +++ b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/CacheRefreshUpdateMethod.java @@ -0,0 +1,64 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.keycloak.link.service; + +import io.jans.orm.annotation.AttributeEnum; + +import java.util.HashMap; +import java.util.Map; + +/** + * Cache refresh update methods + * + * @author Yuriy Movchan Date: 06.27.2012 + */ +public enum CacheRefreshUpdateMethod implements AttributeEnum { + + VDS("vds", "VDS"), COPY("copy", "Copy"); + + private boolean booleanValue; + private String value; + private String displayName; + + private static Map mapByValues = new HashMap(); + static { + for (CacheRefreshUpdateMethod enumType : values()) { + mapByValues.put(enumType.getValue(), enumType); + } + } + + private CacheRefreshUpdateMethod(String value, String displayName) { + this.value = value; + this.displayName = displayName; + } + + public String getValue() { + return value; + } + + public boolean isBooleanValue() { + return booleanValue; + } + + public static CacheRefreshUpdateMethod getByValue(String value) { + return mapByValues.get(value); + } + + public String getDisplayName() { + return displayName; + } + + public Enum resolveByValue(String value) { + return getByValue(value); + } + + @Override + public String toString() { + return value; + } + +} diff --git a/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/IPersonService.java b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/IPersonService.java new file mode 100644 index 00000000000..d7f12493cb4 --- /dev/null +++ b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/IPersonService.java @@ -0,0 +1,27 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ +package io.jans.keycloak.link.service; + +import io.jans.link.model.GluuCustomPerson; + +public interface IPersonService { + + public abstract GluuCustomPerson findPersonByDn(String dn, String... returnAttributes); + + public abstract boolean contains(String dn); + + /** + * Build DN string for person + * + * @param inum + * Inum + * @return DN string for specified person or DN for persons branch if inum is + * null + * @throws Exception + */ + public abstract String getDnForPerson(String inum); + +} \ No newline at end of file diff --git a/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/PersonService.java b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/PersonService.java new file mode 100644 index 00000000000..fefb47dacca --- /dev/null +++ b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/PersonService.java @@ -0,0 +1,130 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.keycloak.link.service; + +import io.jans.link.model.GluuCustomPerson; +import io.jans.link.service.OrganizationService; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.search.filter.Filter; +import io.jans.util.StringHelper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.slf4j.Logger; + +import java.io.Serializable; +import java.time.Instant; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +/** + * Provides operations with persons + * + * @author Yuriy Movchan Date: 10.13.2010 + */ +@ApplicationScoped +public class PersonService implements Serializable, IPersonService { + + private static final long serialVersionUID = 6685720517520443399L; + + @Inject + private Logger log; + + @Inject + private PersistenceEntryManager persistenceEntryManager; + + @Inject + private OrganizationService organizationService; + + + public void addPersonWithoutCheck(GluuCustomPerson person) { + person.setCreationDate(new Date()); + persistenceEntryManager.persist(person); + } + + public void updatePersonWithoutCheck(GluuCustomPerson person) { + Date updateDate = new Date(); + person.setUpdatedAt(updateDate); + if (person.getAttribute("oxTrustMetaLastModified") != null) { + person.setAttribute("oxTrustMetaLastModified", Instant.ofEpochMilli(updateDate.getTime()).toString()); + } + persistenceEntryManager.merge(person); + } + + /* + * (non-Javadoc) + * + * @see + * org.gluu.oxtrust.ldap.service.IPersonService#findPersonByDn(java.lang.String, + * java.lang.String) + */ + @Override + public GluuCustomPerson findPersonByDn(String dn, String... returnAttributes) { + return persistenceEntryManager.find(dn, GluuCustomPerson.class, returnAttributes); + } + + /* + * (non-Javadoc) + * + * @see org.gluu.oxtrust.ldap.service.IPersonService#contains(java.lang.String) + */ + @Override + public boolean contains(String dn) { + return persistenceEntryManager.contains(dn, GluuCustomPerson.class); + } + + /* + * (non-Javadoc) + * + * @see + * org.gluu.oxtrust.ldap.service.IPersonService#getDnForPerson(java.lang.String) + */ + @Override + public String getDnForPerson(String inum) { + String orgDn = organizationService.getDnForOrganization(); + if (StringHelper.isEmpty(inum)) { + return String.format("ou=person,%s", orgDn); + } + + return String.format("inum=%s,ou=people,%s", inum, orgDn); + } + + /** + * Generate new inum for person + * + * @return New inum for person + * @throws Exception + */ + public String generateInumForNewPersonImpl() { + + String id = null; + return id == null ? UUID.randomUUID().toString() : id; + + } + + public List getPersonsByUid(String uid, String... returnAttributes) { + log.debug("Getting user information from DB: userId = {}", uid); + + if (StringHelper.isEmpty(uid)) { + return null; + } + + String personDn = getDnForPerson(null); + Filter userUidFilter; + + userUidFilter = Filter.createEqualityFilter(Filter.createLowercaseFilter("uid"), + StringHelper.toLowerCase(uid)); + + + List entries = persistenceEntryManager.findEntries(personDn, + GluuCustomPerson.class, userUidFilter, returnAttributes); + log.debug("Found {} entries for userId = {}", entries.size(), uid); + + return entries; + } + +} \ No newline at end of file diff --git a/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/status/cdi/event/StatusCheckerTimerEvent.java b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/status/cdi/event/StatusCheckerTimerEvent.java new file mode 100644 index 00000000000..6a74e1c97ea --- /dev/null +++ b/jans-keycloak-link/service/src/main/java/io/jans/keycloak/link/service/status/cdi/event/StatusCheckerTimerEvent.java @@ -0,0 +1,14 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.keycloak.link.service.status.cdi.event; + +public class StatusCheckerTimerEvent { + + public StatusCheckerTimerEvent() {} + + +} diff --git a/jans-keycloak-link/service/src/main/resources/META-INF/beans.xml b/jans-keycloak-link/service/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..b7930c568e8 --- /dev/null +++ b/jans-keycloak-link/service/src/main/resources/META-INF/beans.xml @@ -0,0 +1,6 @@ + + + diff --git a/jans-keycloak-link/service/src/test/java/io/jans/AppTest.java b/jans-keycloak-link/service/src/test/java/io/jans/AppTest.java new file mode 100644 index 00000000000..f5d7b695468 --- /dev/null +++ b/jans-keycloak-link/service/src/test/java/io/jans/AppTest.java @@ -0,0 +1,38 @@ +package io.jans; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/jans-link/model/src/main/java/io/jans/link/constants/JansConstants.java b/jans-link/model/src/main/java/io/jans/link/constants/JansConstants.java index 044278aae8b..aed27de00ea 100644 --- a/jans-link/model/src/main/java/io/jans/link/constants/JansConstants.java +++ b/jans-link/model/src/main/java/io/jans/link/constants/JansConstants.java @@ -43,6 +43,7 @@ public final class JansConstants extends OxConstants { public static final String CACHE_ATTRIBUTE_ORIGIN_KEY_LIST = "attributeOriginList"; public static final String CACHE_ORGANIZATION_CUSTOM_MESSAGE_KEY = "organizationCustomMessage"; public static final String CACHE_REFRESH_DEFAULT_BASE_DN = "ou=link,o=site"; + public static final String JANS_KEYCLOAK_LINK_DEFAULT_BASE_DN = "ou=keycloak-link,o=site"; public static final String INUM_TYPE_PEOPLE_SLUG = "people"; diff --git a/jans-link/server/src/main/java/io/jans/link/CacheRefreshEvent.java b/jans-link/model/src/main/java/io/jans/link/event/CacheRefreshEvent.java similarity index 56% rename from jans-link/server/src/main/java/io/jans/link/CacheRefreshEvent.java rename to jans-link/model/src/main/java/io/jans/link/event/CacheRefreshEvent.java index 7998829c833..920693d46b5 100644 --- a/jans-link/server/src/main/java/io/jans/link/CacheRefreshEvent.java +++ b/jans-link/model/src/main/java/io/jans/link/event/CacheRefreshEvent.java @@ -1,4 +1,4 @@ -package io.jans.link; +package io.jans.link.event; public class CacheRefreshEvent { } diff --git a/jans-link/model/src/main/java/io/jans/link/model/config/CacheRefreshAttributeMapping.java b/jans-link/model/src/main/java/io/jans/link/model/config/CacheRefreshAttributeMapping.java index 40f1b47a6ad..99f77b0f7b7 100644 --- a/jans-link/model/src/main/java/io/jans/link/model/config/CacheRefreshAttributeMapping.java +++ b/jans-link/model/src/main/java/io/jans/link/model/config/CacheRefreshAttributeMapping.java @@ -6,9 +6,11 @@ package io.jans.link.model.config; +import java.io.Serializable; + import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import java.io.Serializable; +import jakarta.enterprise.inject.Vetoed; /** @@ -16,36 +18,8 @@ * * @author Yuriy Movchan Date: 10/23/2015 */ +@Vetoed @JsonPropertyOrder({ "source", "destination" }) -public class CacheRefreshAttributeMapping implements Serializable { - - private static final long serialVersionUID = 8040484460012448855L; - - private String source; - private String destination; - - public CacheRefreshAttributeMapping() { - } - - public CacheRefreshAttributeMapping(String source, String destination) { - this.source = source; - this.destination = destination; - } - - public String getSource() { - return source; - } - - public void setSource(String source) { - this.source = source; - } - - public String getDestination() { - return destination; - } - - public void setDestination(String destination) { - this.destination = destination; - } +public class CacheRefreshAttributeMapping extends io.jans.link.model.config.shared.CacheRefreshAttributeMapping implements Serializable { } diff --git a/jans-link/model/src/main/java/io/jans/link/model/config/CacheRefreshConfiguration.java b/jans-link/model/src/main/java/io/jans/link/model/config/CacheRefreshConfiguration.java index 6536cd62dd6..ce09f89ab6f 100644 --- a/jans-link/model/src/main/java/io/jans/link/model/config/CacheRefreshConfiguration.java +++ b/jans-link/model/src/main/java/io/jans/link/model/config/CacheRefreshConfiguration.java @@ -6,10 +6,7 @@ package io.jans.link.model.config; -import io.jans.model.ldap.GluuLdapConfiguration; - import jakarta.enterprise.inject.Vetoed; -import java.util.List; /** * Cache refresh configuration @@ -17,151 +14,6 @@ * @author Yuriy Movchan Date: 07.13.2011 */ @Vetoed -public class CacheRefreshConfiguration implements Configuration { - - private List sourceConfigs; - private GluuLdapConfiguration inumConfig; - private GluuLdapConfiguration targetConfig; - - private int ldapSearchSizeLimit; - - private List keyAttributes; - private List keyObjectClasses; - private List sourceAttributes; - - private String customLdapFilter; - - private String updateMethod; - - private boolean defaultInumServer; - - private boolean keepExternalPerson; - - private boolean useSearchLimit; - - private List attributeMapping; - - private String snapshotFolder; - private int snapshotMaxCount; - - public List getSourceConfigs() { - return sourceConfigs; - } - - public void setSourceConfigs(List sourceConfigs) { - this.sourceConfigs = sourceConfigs; - } - - public GluuLdapConfiguration getInumConfig() { - return inumConfig; - } - - public void setInumConfig(GluuLdapConfiguration inumConfig) { - this.inumConfig = inumConfig; - } - - public GluuLdapConfiguration getTargetConfig() { - return targetConfig; - } - - public void setTargetConfig(GluuLdapConfiguration targetConfig) { - this.targetConfig = targetConfig; - } - - public int getLdapSearchSizeLimit() { - return ldapSearchSizeLimit; - } - - public void setLdapSearchSizeLimit(int ldapSearchSizeLimit) { - this.ldapSearchSizeLimit = ldapSearchSizeLimit; - } - - public List getKeyAttributes() { - return keyAttributes; - } - - public void setKeyAttributes(List keyAttributes) { - this.keyAttributes = keyAttributes; - } - - public List getKeyObjectClasses() { - return keyObjectClasses; - } - - public void setKeyObjectClasses(List keyObjectClasses) { - this.keyObjectClasses = keyObjectClasses; - } - - public List getSourceAttributes() { - return sourceAttributes; - } - - public void setSourceAttributes(List sourceAttributes) { - this.sourceAttributes = sourceAttributes; - } - - public String getCustomLdapFilter() { - return customLdapFilter; - } - - public void setCustomLdapFilter(String customLdapFilter) { - this.customLdapFilter = customLdapFilter; - } - - public String getUpdateMethod() { - return updateMethod; - } - - public void setUpdateMethod(String updateMethod) { - this.updateMethod = updateMethod; - } - - public boolean isKeepExternalPerson() { - return keepExternalPerson; - } - - public void setKeepExternalPerson(boolean keepExternalPerson) { - this.keepExternalPerson = keepExternalPerson; - } - - public boolean isDefaultInumServer() { - return defaultInumServer; - } - - public void setDefaultInumServer(boolean defaultInumServer) { - this.defaultInumServer = defaultInumServer; - } - - public boolean isUseSearchLimit() { - return useSearchLimit; - } - - public void setUseSearchLimit(boolean useSearchLimit) { - this.useSearchLimit = useSearchLimit; - } - - public List getAttributeMapping() { - return attributeMapping; - } - - public void setAttributeMapping(List attributeMapping) { - this.attributeMapping = attributeMapping; - } - - public String getSnapshotFolder() { - return snapshotFolder; - } - - public void setSnapshotFolder(String snapshotFolder) { - this.snapshotFolder = snapshotFolder; - } - - public int getSnapshotMaxCount() { - return snapshotMaxCount; - } - - public void setSnapshotMaxCount(int snapshotMaxCount) { - this.snapshotMaxCount = snapshotMaxCount; - } +public class CacheRefreshConfiguration extends io.jans.link.model.config.shared.CacheRefreshConfiguration implements Configuration { } diff --git a/jans-link/model/src/main/java/io/jans/link/model/config/Configuration.java b/jans-link/model/src/main/java/io/jans/link/model/config/Configuration.java index 7b87a4974e0..6ae6384399e 100644 --- a/jans-link/model/src/main/java/io/jans/link/model/config/Configuration.java +++ b/jans-link/model/src/main/java/io/jans/link/model/config/Configuration.java @@ -12,5 +12,5 @@ * @author Yuriy Movchan * @version 04/12/2017 */ -public interface Configuration { +public interface Configuration extends io.jans.link.model.config.shared.Configuration { } diff --git a/jans-link/model/src/main/java/io/jans/link/model/config/shared/BaseJansInumMap.java b/jans-link/model/src/main/java/io/jans/link/model/config/shared/BaseJansInumMap.java new file mode 100644 index 00000000000..d471880ceb0 --- /dev/null +++ b/jans-link/model/src/main/java/io/jans/link/model/config/shared/BaseJansInumMap.java @@ -0,0 +1,58 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.link.model.config.shared; + +import io.jans.model.GluuStatus; +import io.jans.orm.annotation.AttributeName; +import io.jans.orm.annotation.DataEntry; +import io.jans.orm.annotation.ObjectClass; +import io.jans.orm.model.base.Entry; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * GluuInumMap + * + * @author Yuriy Movchan Date: 07.13.2011 + */ +@DataEntry(sortBy = { "inum" }) +@ObjectClass(value = "jansInumMap") +public class BaseJansInumMap extends Entry implements Serializable { + + private static final long serialVersionUID = -2190480357430436503L; + + @AttributeName(ignoreDuringUpdate = true) + private String inum; + + @AttributeName(name = "jansStatus") + private GluuStatus status; + + public String getInum() { + return inum; + } + + public void setInum(String inum) { + this.inum = inum; + } + + public GluuStatus getStatus() { + return status; + } + + public void setStatus(GluuStatus status) { + this.status = status; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GluuInumMap [inum=").append(inum).append(", status=").append(status).append("]"); + return builder.toString(); + } + +} diff --git a/jans-link/model/src/main/java/io/jans/link/model/config/shared/CacheRefreshAttributeMapping.java b/jans-link/model/src/main/java/io/jans/link/model/config/shared/CacheRefreshAttributeMapping.java new file mode 100644 index 00000000000..ee075701fe1 --- /dev/null +++ b/jans-link/model/src/main/java/io/jans/link/model/config/shared/CacheRefreshAttributeMapping.java @@ -0,0 +1,54 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.link.model.config.shared; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import jakarta.enterprise.inject.Vetoed; + +import java.io.Serializable; + + +/** + * Attribute mapping + * + * @author Yuriy Movchan Date: 10/23/2015 + */ +@Vetoed +@JsonPropertyOrder({ "source", "destination" }) +public class CacheRefreshAttributeMapping implements Serializable { + + private static final long serialVersionUID = 8040484460012448855L; + + private String source; + private String destination; + + public CacheRefreshAttributeMapping() { + } + + public CacheRefreshAttributeMapping(String source, String destination) { + this.source = source; + this.destination = destination; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + +} diff --git a/jans-link/model/src/main/java/io/jans/link/model/config/shared/CacheRefreshConfiguration.java b/jans-link/model/src/main/java/io/jans/link/model/config/shared/CacheRefreshConfiguration.java new file mode 100644 index 00000000000..8aabc63c730 --- /dev/null +++ b/jans-link/model/src/main/java/io/jans/link/model/config/shared/CacheRefreshConfiguration.java @@ -0,0 +1,167 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.link.model.config.shared; + +import io.jans.model.ldap.GluuLdapConfiguration; + +import jakarta.enterprise.inject.Vetoed; +import java.util.List; + +/** + * Cache refresh configuration + * + * @author Yuriy Movchan Date: 07.13.2011 + */ +@Vetoed +public class CacheRefreshConfiguration implements Configuration { + + private List sourceConfigs; + private GluuLdapConfiguration inumConfig; + private GluuLdapConfiguration targetConfig; + + private int ldapSearchSizeLimit; + + private List keyAttributes; + private List keyObjectClasses; + private List sourceAttributes; + + private String customLdapFilter; + + private String updateMethod; + + private boolean defaultInumServer; + + private boolean keepExternalPerson; + + private boolean useSearchLimit; + + private List attributeMapping; + + private String snapshotFolder; + private int snapshotMaxCount; + + public List getSourceConfigs() { + return sourceConfigs; + } + + public void setSourceConfigs(List sourceConfigs) { + this.sourceConfigs = sourceConfigs; + } + + public GluuLdapConfiguration getInumConfig() { + return inumConfig; + } + + public void setInumConfig(GluuLdapConfiguration inumConfig) { + this.inumConfig = inumConfig; + } + + public GluuLdapConfiguration getTargetConfig() { + return targetConfig; + } + + public void setTargetConfig(GluuLdapConfiguration targetConfig) { + this.targetConfig = targetConfig; + } + + public int getLdapSearchSizeLimit() { + return ldapSearchSizeLimit; + } + + public void setLdapSearchSizeLimit(int ldapSearchSizeLimit) { + this.ldapSearchSizeLimit = ldapSearchSizeLimit; + } + + public List getKeyAttributes() { + return keyAttributes; + } + + public void setKeyAttributes(List keyAttributes) { + this.keyAttributes = keyAttributes; + } + + public List getKeyObjectClasses() { + return keyObjectClasses; + } + + public void setKeyObjectClasses(List keyObjectClasses) { + this.keyObjectClasses = keyObjectClasses; + } + + public List getSourceAttributes() { + return sourceAttributes; + } + + public void setSourceAttributes(List sourceAttributes) { + this.sourceAttributes = sourceAttributes; + } + + public String getCustomLdapFilter() { + return customLdapFilter; + } + + public void setCustomLdapFilter(String customLdapFilter) { + this.customLdapFilter = customLdapFilter; + } + + public String getUpdateMethod() { + return updateMethod; + } + + public void setUpdateMethod(String updateMethod) { + this.updateMethod = updateMethod; + } + + public boolean isKeepExternalPerson() { + return keepExternalPerson; + } + + public void setKeepExternalPerson(boolean keepExternalPerson) { + this.keepExternalPerson = keepExternalPerson; + } + + public boolean isDefaultInumServer() { + return defaultInumServer; + } + + public void setDefaultInumServer(boolean defaultInumServer) { + this.defaultInumServer = defaultInumServer; + } + + public boolean isUseSearchLimit() { + return useSearchLimit; + } + + public void setUseSearchLimit(boolean useSearchLimit) { + this.useSearchLimit = useSearchLimit; + } + + public List getAttributeMapping() { + return attributeMapping; + } + + public void setAttributeMapping(List attributeMapping) { + this.attributeMapping = attributeMapping; + } + + public String getSnapshotFolder() { + return snapshotFolder; + } + + public void setSnapshotFolder(String snapshotFolder) { + this.snapshotFolder = snapshotFolder; + } + + public int getSnapshotMaxCount() { + return snapshotMaxCount; + } + + public void setSnapshotMaxCount(int snapshotMaxCount) { + this.snapshotMaxCount = snapshotMaxCount; + } + +} diff --git a/jans-link/model/src/main/java/io/jans/link/model/config/shared/Configuration.java b/jans-link/model/src/main/java/io/jans/link/model/config/shared/Configuration.java new file mode 100644 index 00000000000..b7fabb626d1 --- /dev/null +++ b/jans-link/model/src/main/java/io/jans/link/model/config/shared/Configuration.java @@ -0,0 +1,16 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.link.model.config.shared; + +/** + * base interface for all Jans Auth configurations + * + * @author Yuriy Movchan + * @version 04/12/2017 + */ +public interface Configuration { +} diff --git a/jans-link/server/src/main/java/io/jans/link/service/AppInitializer.java b/jans-link/server/src/main/java/io/jans/link/server/service/AppInitializer.java similarity index 97% rename from jans-link/server/src/main/java/io/jans/link/service/AppInitializer.java rename to jans-link/server/src/main/java/io/jans/link/server/service/AppInitializer.java index 3f315b934e0..8c2a0253980 100644 --- a/jans-link/server/src/main/java/io/jans/link/service/AppInitializer.java +++ b/jans-link/server/src/main/java/io/jans/link/server/service/AppInitializer.java @@ -4,19 +4,20 @@ * Copyright (c) 2020, Janssen Project */ -package io.jans.link.service; +package io.jans.link.server.service; import java.lang.annotation.Annotation; import java.util.List; import java.util.Properties; +import io.jans.link.timer.JansLinkTimer; import org.slf4j.Logger; import com.google.common.collect.Lists; +import io.jans.link.service.EncryptionService; import io.jans.link.service.config.ApplicationFactory; import io.jans.link.service.config.ConfigurationFactory; -import io.jans.link.timer.CacheRefreshTimer; import io.jans.exception.ConfigurationException; import io.jans.model.custom.script.CustomScriptType; import io.jans.orm.PersistenceEntryManager; @@ -90,7 +91,7 @@ public class AppInitializer { private LoggerService loggerService; @Inject - private CacheRefreshTimer cacheRefreshTimer; + private JansLinkTimer jansLinkTimer; @PostConstruct public void createApplicationComponents() { @@ -126,7 +127,7 @@ public void applicationInitialized(@Observes @Initialized(ApplicationScoped.clas configurationFactory.initTimer(); loggerService.initTimer(); customScriptManager.initTimer(supportedCustomScriptTypes); - cacheRefreshTimer.initTimer(); + jansLinkTimer.initTimer(); // Notify plugins about finish application initialization eventApplicationInitialized.select(ApplicationInitialized.Literal.APPLICATION) .fire(new ApplicationInitializedEvent()); diff --git a/jans-link/server/src/main/java/io/jans/link/server/service/AttributeService.java b/jans-link/server/src/main/java/io/jans/link/server/service/AttributeService.java new file mode 100644 index 00000000000..001cad89666 --- /dev/null +++ b/jans-link/server/src/main/java/io/jans/link/server/service/AttributeService.java @@ -0,0 +1,31 @@ +package io.jans.link.server.service; + +import io.jans.link.model.config.AppConfiguration; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.interceptor.Interceptor; + +/** + * Provides operations with attributes + * + * @author Yuriy Movchan Date: 09/07/2023 + */ +@ApplicationScoped +@Priority(Interceptor.Priority.APPLICATION + 5) +public class AttributeService extends io.jans.link.service.AttributeService { + + private static final long serialVersionUID = 3502134792415981865L; + + @Inject + private AppConfiguration appConfiguration; + + public String getPersonCustomObjectClass() { + return appConfiguration.getPersonCustomObjectClass(); + } + + public String[] getPersonObjectClassTypes() { + return appConfiguration.getPersonObjectClassTypes(); + } + +} diff --git a/jans-link/service/src/main/java/io/jans/link/service/CacheRefrshConfigurationService.java b/jans-link/server/src/main/java/io/jans/link/server/service/CacheRefrshConfigurationService.java similarity index 98% rename from jans-link/service/src/main/java/io/jans/link/service/CacheRefrshConfigurationService.java rename to jans-link/server/src/main/java/io/jans/link/server/service/CacheRefrshConfigurationService.java index b00b0d52b50..b5cd6c8fb62 100644 --- a/jans-link/service/src/main/java/io/jans/link/service/CacheRefrshConfigurationService.java +++ b/jans-link/server/src/main/java/io/jans/link/server/service/CacheRefrshConfigurationService.java @@ -4,7 +4,7 @@ * Copyright (c) 2014, Gluu */ -package io.jans.link.service; +package io.jans.link.server.service; import java.io.Serializable; diff --git a/jans-link/server/src/main/java/io/jans/link/server/service/CustomScriptService.java b/jans-link/server/src/main/java/io/jans/link/server/service/CustomScriptService.java new file mode 100644 index 00000000000..246a525c7ad --- /dev/null +++ b/jans-link/server/src/main/java/io/jans/link/server/service/CustomScriptService.java @@ -0,0 +1,29 @@ +package io.jans.link.server.service; + +import io.jans.link.model.config.StaticConfiguration; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Alternative; +import jakarta.inject.Inject; +import jakarta.interceptor.Interceptor; + +/** + * Operations with custom scripts + * + * @author Yuriy Movchan Date: 09/07/2023 + */ +@ApplicationScoped +@Alternative +@Priority(Interceptor.Priority.APPLICATION + 5) +public class CustomScriptService extends io.jans.link.service.custom.CustomScriptService { + + @Inject + private StaticConfiguration staticConfiguration; + + private static final long serialVersionUID = -5283102477313448031L; + + public String baseDn() { + return staticConfiguration.getBaseDn().getScripts(); + } + +} diff --git a/jans-link/service/src/main/java/io/jans/link/service/LoggerService.java b/jans-link/server/src/main/java/io/jans/link/server/service/LoggerService.java similarity index 96% rename from jans-link/service/src/main/java/io/jans/link/service/LoggerService.java rename to jans-link/server/src/main/java/io/jans/link/server/service/LoggerService.java index 6a0a4d574d0..09cfa8c2467 100644 --- a/jans-link/service/src/main/java/io/jans/link/service/LoggerService.java +++ b/jans-link/server/src/main/java/io/jans/link/server/service/LoggerService.java @@ -4,7 +4,7 @@ * Copyright (c) 2020, Janssen Project */ -package io.jans.link.service; +package io.jans.link.server.service; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; diff --git a/jans-link/server/src/main/java/io/jans/link/server/service/OrganizationService.java b/jans-link/server/src/main/java/io/jans/link/server/service/OrganizationService.java new file mode 100644 index 00000000000..a8a6bfe9125 --- /dev/null +++ b/jans-link/server/src/main/java/io/jans/link/server/service/OrganizationService.java @@ -0,0 +1,27 @@ +package io.jans.link.server.service; + +import io.jans.link.model.config.AppConfiguration; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.interceptor.Interceptor; + +/** + * Provides operations with organization + * + * @author Yuriy Movchan Date: 09/07/2023 + */ +@ApplicationScoped +@Priority(Interceptor.Priority.APPLICATION + 5) +public class OrganizationService extends io.jans.link.service.OrganizationService { + + private static final long serialVersionUID = 4502134792415981865L; + + @Inject + private AppConfiguration appConfiguration; + + public String getAppConfigurationBaseDn() { + return appConfiguration.getBaseDN(); + } + +} diff --git a/jans-link/server/src/main/java/io/jans/link/service/SystemResteasyInitializer.java b/jans-link/server/src/main/java/io/jans/link/server/service/SystemResteasyInitializer.java similarity index 95% rename from jans-link/server/src/main/java/io/jans/link/service/SystemResteasyInitializer.java rename to jans-link/server/src/main/java/io/jans/link/server/service/SystemResteasyInitializer.java index 1fc93477f84..b30f8fa6176 100644 --- a/jans-link/server/src/main/java/io/jans/link/service/SystemResteasyInitializer.java +++ b/jans-link/server/src/main/java/io/jans/link/server/service/SystemResteasyInitializer.java @@ -4,7 +4,7 @@ * Copyright (c) 2020, Janssen Project */ -package io.jans.link.service; +package io.jans.link.server.service; import java.util.HashSet; import java.util.Set; diff --git a/jans-link/service/src/main/java/io/jans/link/service/config/ApplicationFactory.java b/jans-link/server/src/main/java/io/jans/link/service/config/ApplicationFactory.java similarity index 100% rename from jans-link/service/src/main/java/io/jans/link/service/config/ApplicationFactory.java rename to jans-link/server/src/main/java/io/jans/link/service/config/ApplicationFactory.java diff --git a/jans-link/service/src/main/java/io/jans/link/service/config/ConfigurationFactory.java b/jans-link/server/src/main/java/io/jans/link/service/config/ConfigurationFactory.java similarity index 100% rename from jans-link/service/src/main/java/io/jans/link/service/config/ConfigurationFactory.java rename to jans-link/server/src/main/java/io/jans/link/service/config/ConfigurationFactory.java diff --git a/jans-link/server/src/main/java/io/jans/link/timer/CacheRefreshTimer.java b/jans-link/server/src/main/java/io/jans/link/timer/CacheRefreshTimer.java deleted file mode 100644 index 5bd38a1d9da..00000000000 --- a/jans-link/server/src/main/java/io/jans/link/timer/CacheRefreshTimer.java +++ /dev/null @@ -1,1376 +0,0 @@ -/* - * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. - * - * Copyright (c) 2014, Gluu - */ - -package io.jans.link.timer; - -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -//import javax.inject.Inject; -//import javax.inject.Named; - -import io.jans.link.CacheRefreshEvent; -import io.jans.link.constants.JansConstants; -import io.jans.link.external.ExternalCacheRefreshService; -import io.jans.link.model.*; -import io.jans.link.model.config.AppConfiguration; -import io.jans.link.model.config.CacheRefreshConfiguration; -import io.jans.link.model.config.CacheRefreshAttributeMapping; -import io.jans.link.service.*; -import io.jans.link.service.config.ApplicationFactory; -import io.jans.link.service.config.ConfigurationFactory; -import io.jans.link.util.PropertyUtil; -import io.jans.model.GluuStatus; -import io.jans.model.JansCustomAttribute; -import io.jans.model.SchemaEntry; -import io.jans.model.custom.script.model.bind.BindCredentials; -import io.jans.model.ldap.GluuLdapConfiguration; -import io.jans.orm.PersistenceEntryManager; -import io.jans.orm.PersistenceEntryManagerFactory; -import io.jans.orm.annotation.ObjectClass; -import io.jans.orm.exception.BasePersistenceException; -import io.jans.orm.exception.EntryPersistenceException; -import io.jans.orm.exception.operation.SearchException; -import io.jans.orm.ldap.impl.LdapEntryManagerFactory; -import io.jans.orm.ldap.operation.LdapOperationService; -import io.jans.orm.model.SearchScope; -import io.jans.orm.model.base.DummyEntry; -import io.jans.orm.operation.PersistenceOperationService; -import io.jans.orm.search.filter.Filter; -import io.jans.orm.util.ArrayHelper; -import io.jans.orm.util.StringHelper; -import io.jans.service.AttributeService; -import io.jans.service.ObjectSerializationService; -import io.jans.service.SchemaService; -import io.jans.service.cdi.async.Asynchronous; -import io.jans.service.cdi.event.Scheduled; -import io.jans.service.timer.event.TimerEvent; -import io.jans.service.timer.schedule.TimerSchedule; -import io.jans.util.OxConstants; -import io.jans.util.Pair; -import io.jans.util.security.PropertiesDecrypter; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Event; -import jakarta.enterprise.event.Observes; -import jakarta.inject.Inject; -import org.apache.commons.beanutils.BeanUtilsBean2; -import org.apache.commons.io.FilenameUtils; -import org.slf4j.Logger; - -/** - * Check periodically if source servers contains updates and trigger target - * server entry update if needed - * - * @author Yuriy Movchan Date: 05.05.2011 - */ -@ApplicationScoped -public class CacheRefreshTimer { - - private static final String LETTERS_FOR_SEARCH = "abcdefghijklmnopqrstuvwxyz1234567890."; - private static final String[] TARGET_PERSON_RETURN_ATTRIBUTES = { JansConstants.inum }; - - private static final int DEFAULT_INTERVAL = 60; - - @Inject - private Logger log; - - @Inject - private Event timerEvent; - - @Inject - protected ApplicationFactory applicationFactory; - - @Inject - protected AttributeService attributeService; - - @Inject - private ConfigurationFactory configurationFactory; - - @Inject - private CacheRefreshService cacheRefreshService; - - @Inject - private PersonService personService; - - @Inject - private PersistenceEntryManager ldapEntryManager; - - @Inject - private CacheRefreshSnapshotFileService cacheRefreshSnapshotFileService; - - @Inject - private ExternalCacheRefreshService externalCacheRefreshService; - - @Inject - private SchemaService schemaService; - - @Inject - private InumService inumService; - - @Inject - private AppConfiguration appConfiguration; - - @Inject - private CacheRefrshConfigurationService CacheRefrshConfigurationService; - - @Inject - private EncryptionService encryptionService; - - @Inject - private PairwiseIdService pairwiseIdService; - - @Inject - private FidoDeviceService fidoDeviceService; - - @Inject - private Fido2DeviceService fido2DeviceService; - - @Inject - private ObjectSerializationService objectSerializationService; - - private AtomicBoolean isActive; - private long lastFinishedTime; - - public void initTimer() { - log.info("Initializing Cache Refresh Timer"); - this.isActive = new AtomicBoolean(false); - - // Clean up previous Inum cache - CacheRefreshConfiguration cacheRefreshConfiguration = getConfigurationFactory().getAppConfiguration(); - if (cacheRefreshConfiguration != null) { - String snapshotFolder = cacheRefreshConfiguration.getSnapshotFolder(); - if (StringHelper.isNotEmpty(snapshotFolder)) { - String inumCachePath = getInumCachePath(cacheRefreshConfiguration); - objectSerializationService.cleanup(inumCachePath); - } - } - - // Schedule to start cache refresh every 1 minute - timerEvent.fire(new TimerEvent(new TimerSchedule(DEFAULT_INTERVAL, DEFAULT_INTERVAL), new CacheRefreshEvent(), - Scheduled.Literal.INSTANCE)); - - this.lastFinishedTime = System.currentTimeMillis(); - } - - @Asynchronous - public void process(@Observes @Scheduled CacheRefreshEvent cacheRefreshEvent) { - if (this.isActive.get()) { - log.info("Another process is active"); - return; - } - - if (!this.isActive.compareAndSet(false, true)) { - log.info("Failed to start process exclusively"); - return; - } - - try { - processInt(); - } finally { - log.info("Allowing to run new process exclusively"); - this.isActive.set(false); - } - } - - public void processInt() { - AppConfiguration currentConfiguration = getConfigurationFactory().getAppConfiguration(); - try { - currentConfiguration.setServerIpAddress("255.255.255.255"); - if (!isStartCacheRefresh(currentConfiguration)) { - log.info("Starting conditions aren't reached"); - return; - } - - processImpl(currentConfiguration); - updateStatus(currentConfiguration, System.currentTimeMillis()); - - this.lastFinishedTime = System.currentTimeMillis(); - } catch (Throwable ex) { - ex.printStackTrace(); - log.info("Exception happened while executing cache refresh synchronization"+ ex); - } - } - - private boolean isStartCacheRefresh(AppConfiguration currentConfiguration) { - if (!currentConfiguration.isLinkEnabled()) { - return false; - } - - long poolingInterval = StringHelper.toInteger(currentConfiguration.getPollingInterval()); - if (poolingInterval < 0) { - return false; - } - - if(null == currentConfiguration.getSourceConfigs()){ - log.info("Source Config is null, nothing to load "); - return false; - } - - String cacheRefreshServerIpAddress = currentConfiguration.getServerIpAddress(); - // if (StringHelper.isEmpty(cacheRefreshServerIpAddress)) { - // log.debug("There is no master Cache Refresh server"); - // return false; - // } - - // Compare server IP address with cacheRefreshServerIp - boolean cacheRefreshServer = false; - try { - Enumeration nets = NetworkInterface.getNetworkInterfaces(); - for (NetworkInterface networkInterface : Collections.list(nets)) { - Enumeration inetAddresses = networkInterface.getInetAddresses(); - for (InetAddress inetAddress : Collections.list(inetAddresses)) { - if (StringHelper.equals(cacheRefreshServerIpAddress, inetAddress.getHostAddress())) { - cacheRefreshServer = true; - break; - } - } - - if (cacheRefreshServer) { - break; - } - } - } catch (SocketException ex) { - log.error("Failed to enumerate server IP addresses"+ ex); - } - - if (!cacheRefreshServer) { - cacheRefreshServer = externalCacheRefreshService.executeExternalIsStartProcessMethods(); - cacheRefreshServer = true; - } - - if (!cacheRefreshServer) { - log.info("This server isn't master Cache Refresh server"); - return false; - } - - // Check if cache refresh specific configuration was loaded - if (currentConfiguration == null) { - log.info("Failed to start cache refresh. Can't loading configuration from oxTrustCacheRefresh.properties"); - return false; - } - - long timeDiffrence = System.currentTimeMillis() - this.lastFinishedTime; - timeDiffrence = timeDiffrence/1000; - - return timeDiffrence >= poolingInterval; - } - - private void processImpl(AppConfiguration currentConfiguration) - throws SearchException { - CacheRefreshUpdateMethod updateMethod = getUpdateMethod(currentConfiguration); - - // Prepare and check connections to LDAP servers - LdapServerConnection[] sourceServerConnections = prepareLdapServerConnections(currentConfiguration, - currentConfiguration.getSourceConfigs()); - - LdapServerConnection inumDbServerConnection; - if (currentConfiguration.isDefaultInumServer()) { - GluuLdapConfiguration ldapInumConfiguration = new GluuLdapConfiguration(); - ldapInumConfiguration.setConfigId("local_inum"); - ldapInumConfiguration.setBaseDNsStringsList( - Arrays.asList(new String[] { JansConstants.CACHE_REFRESH_DEFAULT_BASE_DN })); - - inumDbServerConnection = prepareLdapServerConnection(currentConfiguration, ldapInumConfiguration, - true); - } else { - inumDbServerConnection = prepareLdapServerConnection(currentConfiguration, - currentConfiguration.getInumConfig()); - } - - boolean isVdsUpdate = CacheRefreshUpdateMethod.VDS.equals(updateMethod); - LdapServerConnection targetServerConnection = null; - if (isVdsUpdate) { - targetServerConnection = prepareLdapServerConnection(currentConfiguration, - currentConfiguration.getTargetConfig()); - } - - try { - if ((sourceServerConnections == null) || (inumDbServerConnection == null) - || (isVdsUpdate && (targetServerConnection == null))) { - log.error("Skipping cache refresh due to invalid server configuration"); - } else { - detectChangedEntries(currentConfiguration, sourceServerConnections, - inumDbServerConnection, targetServerConnection, updateMethod); - } - } finally { - // Close connections to LDAP servers - try { - closeLdapServerConnection(sourceServerConnections); - } catch (Exception e) { - // Nothing can be done - } - - if (!currentConfiguration.isDefaultInumServer()) { - try { - closeLdapServerConnection(inumDbServerConnection); - } catch (Exception e) { - // Nothing can be done - } - } - try { - if (isVdsUpdate) { - closeLdapServerConnection(targetServerConnection); - } - } catch (Exception e) { - // Nothing can be done - } - } - - return; - } - - @SuppressWarnings("unchecked") - private boolean detectChangedEntries( AppConfiguration currentConfiguration, LdapServerConnection[] sourceServerConnections, - LdapServerConnection inumDbServerConnection, LdapServerConnection targetServerConnection, - CacheRefreshUpdateMethod updateMethod) throws SearchException { - boolean isVDSMode = CacheRefreshUpdateMethod.VDS.equals(updateMethod); - - // Load all entries from Source servers - log.info("Attempting to load entries from source server"); - List sourcePersons; - - if (currentConfiguration.isUseSearchLimit()) { - sourcePersons = loadSourceServerEntries(currentConfiguration, sourceServerConnections); - } else { - sourcePersons = loadSourceServerEntriesWithoutLimits(currentConfiguration, sourceServerConnections); - } - - log.info("Found '{}' entries in source server"+ sourcePersons.size()); - - Map sourcePersonCacheCompoundKeyMap = getSourcePersonCompoundKeyMap( - currentConfiguration, sourcePersons); - log.info("Found '{}' unique entries in source server"+ sourcePersonCacheCompoundKeyMap.size()); - - // Load all inum entries - List inumMaps = null; - - // Load all inum entries from local disk cache - String inumCachePath = getInumCachePath(currentConfiguration); - Object loadedObject = objectSerializationService.loadObject(inumCachePath); - if (loadedObject != null) { - try { - inumMaps = (List) loadedObject; - log.info("Found '{}' entries in inum objects disk cache"+ inumMaps.size()); - } catch (Exception ex) { - log.error("Failed to convert to GluuInumMap list"+ ex); - objectSerializationService.cleanup(inumCachePath); - } - } - - if (inumMaps == null) { - // Load all inum entries from LDAP - inumMaps = loadInumServerEntries(currentConfiguration, inumDbServerConnection); - log.info("Found '{}' entries in inum server"+ inumMaps.size()); - } - - HashMap primaryKeyAttrValueInumMap = getPrimaryKeyAttrValueInumMap(inumMaps); - - // Go through Source entries and create new InumMap entries if needed - HashMap addedPrimaryKeyAttrValueInumMap = addNewInumServerEntries( - currentConfiguration, inumDbServerConnection, sourcePersonCacheCompoundKeyMap, - primaryKeyAttrValueInumMap); - - HashMap allPrimaryKeyAttrValueInumMap = getAllInumServerEntries( - primaryKeyAttrValueInumMap, addedPrimaryKeyAttrValueInumMap); - log.info("Count actual inum entries '{}' after updating inum server"+ allPrimaryKeyAttrValueInumMap.size()); - - HashMap currInumWithEntryHashCodeMap = getSourcePersonsHashCodesMap(inumDbServerConnection, - sourcePersonCacheCompoundKeyMap, allPrimaryKeyAttrValueInumMap); - log.info("Count actual source entries '{}' after calculating hash code"+ currInumWithEntryHashCodeMap.size()); - - // Create snapshots cache folder if needed - boolean result = cacheRefreshSnapshotFileService.prepareSnapshotsFolder(currentConfiguration); - if (!result) { - return false; - } - - // Load last snapshot into memory - Map prevInumWithEntryHashCodeMap = cacheRefreshSnapshotFileService - .readLastSnapshot(currentConfiguration); - - // Compare 2 snapshot and invoke update if needed - Set changedInums = getChangedInums(currInumWithEntryHashCodeMap, prevInumWithEntryHashCodeMap, - isVDSMode); - log.info("Found '{}' changed entries"+ changedInums.size()); - - // Load problem list from disk and add to changedInums - List problemInums = cacheRefreshSnapshotFileService.readProblemList(currentConfiguration); - if (problemInums != null) { - log.info("Loaded '{}' problem entries from problem file"+ problemInums.size()); - // Process inums from problem list too - changedInums.addAll(problemInums); - } - - List updatedInums = null; - if (isVDSMode) { - // Update request to VDS to update entries on target server - updatedInums = updateTargetEntriesViaVDS(currentConfiguration, targetServerConnection, changedInums); - } else { - updatedInums = updateTargetEntriesViaCopy(currentConfiguration, sourcePersonCacheCompoundKeyMap, - allPrimaryKeyAttrValueInumMap, changedInums); - } - - log.info("Updated '{}' entries"+ updatedInums.size()); - changedInums.removeAll(updatedInums); - log.info("Failed to update '{}' entries"+ changedInums.size()); - - // Persist snapshot to cache folder - result = cacheRefreshSnapshotFileService.createSnapshot(currentConfiguration, - currInumWithEntryHashCodeMap); - if (!result) { - return false; - } - - // Retain only specified number of snapshots - cacheRefreshSnapshotFileService.retainSnapshots(currentConfiguration, - currentConfiguration.getSnapshotMaxCount()); - - // Save changedInums as problem list to disk - currentConfiguration.setProblemCount(String.valueOf(changedInums.size())); - cacheRefreshSnapshotFileService.writeProblemList(currentConfiguration, changedInums); - - // Prepare list of persons for removal - List personsForRemoval = null; - - boolean keepExternalPerson = currentConfiguration.isKeepExternalPerson(); - log.info("Keep external persons: '{}'"+ keepExternalPerson); - if (keepExternalPerson) { - // Determine entries which need to remove - personsForRemoval = getRemovedPersons(currInumWithEntryHashCodeMap, prevInumWithEntryHashCodeMap); - } else { - // Process entries which don't exist in source server - - // Load all entries from Target server - List targetPersons = loadTargetServerEntries(currentConfiguration, getLdapEntryManager()); - log.info("Found '{}' entries in target server"+ targetPersons.size()); - - // Detect entries which need to remove - personsForRemoval = processTargetPersons(targetPersons, currInumWithEntryHashCodeMap); - } - log.info("Count entries '{}' for removal from target server"+ personsForRemoval.size()); - - // Remove entries from target server - HashMap inumInumMap = getInumInumMap(inumMaps); - Pair, List> removeTargetEntriesResult = removeTargetEntries(inumDbServerConnection, - getLdapEntryManager(), personsForRemoval, inumInumMap); - List removedPersonInums = removeTargetEntriesResult.getFirst(); - List removedGluuInumMaps = removeTargetEntriesResult.getSecond(); - log.info("Removed '{}' persons from target server"+ removedPersonInums.size()); - - // Prepare list of inum for serialization - ArrayList currentInumMaps = applyChangesToInumMap(inumInumMap, addedPrimaryKeyAttrValueInumMap, - removedGluuInumMaps); - - // Strore all inum entries into local disk cache - objectSerializationService.saveObject(inumCachePath, currentInumMaps); - - currentConfiguration - .setLastUpdateCount(String.valueOf(updatedInums.size() + removedPersonInums.size())); - - return true; - } - - private ArrayList applyChangesToInumMap(HashMap inumInumMap, - HashMap addedPrimaryKeyAttrValueInumMap, List removedGluuInumMaps) { - log.info("There are '{}' entries before updating inum list"+ inumInumMap.size()); - for (String removedGluuInumMap : removedGluuInumMaps) { - inumInumMap.remove(removedGluuInumMap); - } - log.info("There are '{}' entries after removal '{}' entries" + inumInumMap.size() +" : " +removedGluuInumMaps.size()); - - ArrayList currentInumMaps = new ArrayList(inumInumMap.values()); - currentInumMaps.addAll(addedPrimaryKeyAttrValueInumMap.values()); - log.info("There are '{}' entries after adding '{}' entries"+ currentInumMaps.size()+" : " + - addedPrimaryKeyAttrValueInumMap.size()); - - return currentInumMaps; - } - - private Set getChangedInums(HashMap currInumWithEntryHashCodeMap, - Map prevInumWithEntryHashCodeMap, boolean includeDeleted) { - // Find chaged inums - Set changedInums = null; - // First time run - if (prevInumWithEntryHashCodeMap == null) { - changedInums = new HashSet(currInumWithEntryHashCodeMap.keySet()); - } else { - changedInums = new HashSet(); - - // Add all inums which not exist in new snapshot - if (includeDeleted) { - for (String prevInumKey : prevInumWithEntryHashCodeMap.keySet()) { - if (!currInumWithEntryHashCodeMap.containsKey(prevInumKey)) { - changedInums.add(prevInumKey); - } - } - } - - // Add all new inums and changed inums - for (Entry currEntry : currInumWithEntryHashCodeMap.entrySet()) { - String currInumKey = currEntry.getKey(); - Integer prevHashCode = prevInumWithEntryHashCodeMap.get(currInumKey); - if ((prevHashCode == null) - || ((prevHashCode != null) && !(prevHashCode.equals(currEntry.getValue())))) { - changedInums.add(currInumKey); - } - } - } - return changedInums; - } - - private List getRemovedPersons(HashMap currInumWithEntryHashCodeMap, - Map prevInumWithEntryHashCodeMap) { - // First time run - if (prevInumWithEntryHashCodeMap == null) { - return new ArrayList(0); - } - - // Add all inums which not exist in new snapshot - Set deletedInums = new HashSet(); - for (String prevInumKey : prevInumWithEntryHashCodeMap.keySet()) { - if (!currInumWithEntryHashCodeMap.containsKey(prevInumKey)) { - deletedInums.add(prevInumKey); - } - } - - List deletedPersons = new ArrayList(deletedInums.size()); - for (String deletedInum : deletedInums) { - GluuSimplePerson person = new GluuSimplePerson(); - String personDn = personService.getDnForPerson(deletedInum); - person.setDn(personDn); - - List customAttributes = new ArrayList(); - customAttributes.add(new JansCustomAttribute(JansConstants.inum, deletedInum)); - person.setCustomAttributes(customAttributes); - - deletedPersons.add(person); - } - - return deletedPersons; - } - - private List updateTargetEntriesViaVDS(CacheRefreshConfiguration cacheRefreshConfiguration, - LdapServerConnection targetServerConnection, Set changedInums) { - List result = new ArrayList(); - - PersistenceEntryManager targetPersistenceEntryManager = targetServerConnection.getPersistenceEntryManager(); - Filter filter = cacheRefreshService.createObjectClassPresenceFilter(); - for (String changedInum : changedInums) { - String baseDn = "action=synchronizecache," + personService.getDnForPerson(changedInum); - try { - targetPersistenceEntryManager.findEntries(baseDn, DummyEntry.class, filter, SearchScope.SUB, null, - null, 0, 0, cacheRefreshConfiguration.getLdapSearchSizeLimit()); - result.add(changedInum); - log.info("Updated entry with inum {}"+ changedInum); - } catch (BasePersistenceException ex) { - log.error("Failed to update entry with inum '{}' using baseDN {}"+ changedInum, baseDn, ex); - } - } - - return result; - } - - private List updateTargetEntriesViaCopy(CacheRefreshConfiguration cacheRefreshConfiguration, - Map sourcePersonCacheCompoundKeyMap, - HashMap primaryKeyAttrValueInumMap, Set changedInums) { - HashMap inumCacheCompoundKeyMap = getInumCacheCompoundKeyMap( - primaryKeyAttrValueInumMap); - Map targetServerAttributesMapping = getTargetServerAttributesMapping(cacheRefreshConfiguration); - String[] customObjectClasses = appConfiguration.getPersonObjectClassTypes(); - - List result = new ArrayList(); - - if (!validateTargetServerSchema(cacheRefreshConfiguration, targetServerAttributesMapping, - customObjectClasses)) { - return result; - } - - for (String targetInum : changedInums) { - CacheCompoundKey compoundKey = inumCacheCompoundKeyMap.get(targetInum); - if (compoundKey == null) { - continue; - } - - GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyMap.get(compoundKey); - if (sourcePerson == null) { - continue; - } - - if (updateTargetEntryViaCopy(sourcePerson, targetInum, customObjectClasses, - targetServerAttributesMapping)) { - result.add(targetInum); - } - } - - return result; - } - - private boolean validateTargetServerSchema(CacheRefreshConfiguration cacheRefreshConfiguration, - Map targetServerAttributesMapping, String[] customObjectClasses) { - // Get list of return attributes - String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); - String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration); - String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes); - - GluuSimplePerson sourcePerson = new GluuSimplePerson(); - for (String returnAttribute : returnAttributes) { - sourcePerson.setAttribute(returnAttribute, "Test"); - } - - String targetInum = inumService.generateInums(JansConstants.INUM_TYPE_PEOPLE_SLUG, false); - String targetPersonDn = personService.getDnForPerson(targetInum); - - GluuCustomPerson targetPerson = new GluuCustomPerson(); - targetPerson.setDn(targetPersonDn); - targetPerson.setInum(targetInum); - targetPerson.setStatus(appConfiguration.getSupportedUserStatus().get(0)); - targetPerson.setCustomObjectClasses(customObjectClasses); - - // Update list of return attributes according mapping - cacheRefreshService.setTargetEntryAttributes(sourcePerson, targetServerAttributesMapping, targetPerson); - - // Execute interceptor script - externalCacheRefreshService.executeExternalUpdateUserMethods(targetPerson); - boolean executionResult = externalCacheRefreshService.executeExternalUpdateUserMethods(targetPerson); - if (!executionResult) { - log.error("Failed to execute Cache Refresh scripts for person '{}'"+ targetInum); - return false; - } - - // Validate target server attributes - List customAttributes = targetPerson.getCustomAttributes(); - - List targetAttributes = new ArrayList(customAttributes.size()); - for (JansCustomAttribute customAttribute : customAttributes) { - targetAttributes.add(customAttribute.getName()); - } - - List targetObjectClasses = Arrays - .asList(getLdapEntryManager().getObjectClasses(targetPerson, GluuCustomPerson.class)); - - return validateTargetServerSchema(targetObjectClasses, targetAttributes); - } - - private boolean validateTargetServerSchema(List targetObjectClasses, List targetAttributes) { - SchemaEntry schemaEntry = schemaService.getSchema(); - if (schemaEntry == null) { - // Destination server not requires schema validation - return true; - } - - Set objectClassesAttributesSet = schemaService.getObjectClassesAttributes(schemaEntry, - targetObjectClasses.toArray(new String[0])); - - Set targetAttributesSet = new LinkedHashSet(); - for (String attrbute : targetAttributes) { - targetAttributesSet.add(StringHelper.toLowerCase(attrbute)); - } - - targetAttributesSet.removeAll(objectClassesAttributesSet); - - if (targetAttributesSet.size() == 0) { - return true; - } - - log.error("Skipping target entries update. Destination server schema doesn't has next attributes: '{}', target OC: '{}', target OC attributes: '{}'"+ - targetAttributesSet, targetObjectClasses.toArray(new String[0]), objectClassesAttributesSet); - - return false; - } - - private boolean updateTargetEntryViaCopy(GluuSimplePerson sourcePerson, String targetInum, - String[] targetCustomObjectClasses, Map targetServerAttributesMapping) { - String targetPersonDn = personService.getDnForPerson(targetInum); - GluuCustomPerson targetPerson = null; - boolean updatePerson; - if (personService.contains(targetPersonDn)) { - try { - targetPerson = personService.findPersonByDn(targetPersonDn); - log.info("Found person by inum '{}'"+ targetInum); - } catch (EntryPersistenceException ex) { - log.error("Failed to find person '{}'"+ targetInum, ex); - return false; - } - updatePerson = true; - } else { - targetPerson = new GluuCustomPerson(); - targetPerson.setDn(targetPersonDn); - targetPerson.setInum(targetInum); - targetPerson.setStatus(appConfiguration.getSupportedUserStatus().get(0)); - updatePerson = false; - } - - if (PersistenceEntryManager.PERSITENCE_TYPES.ldap.name().equals(getLdapEntryManager().getPersistenceType())) { - targetPerson.setCustomObjectClasses(targetCustomObjectClasses); - } - - targetPerson.setSourceServerName(sourcePerson.getSourceServerName()); - targetPerson.setSourceServerUserDn(sourcePerson.getDn()); - - cacheRefreshService.setTargetEntryAttributes(sourcePerson, targetServerAttributesMapping, targetPerson); - - // Execute interceptor script - boolean executionResult = externalCacheRefreshService.executeExternalUpdateUserMethods(targetPerson); - if (!executionResult) { - log.error("Failed to execute Cache Refresh scripts for person '{}'"+ targetInum); - return false; - } - - try { - if (updatePerson) { - personService.updatePersonWithoutCheck(targetPerson); - log.info("Updated person '{}'"+ targetInum); - } else { - personService.addPersonWithoutCheck(targetPerson); - log.info("Added new person '{}'"+ targetInum); - } - } catch (Exception ex) { - ex.printStackTrace(); - String test = updatePerson ? "update" : "add"; - log.error("Failed to '{}' person '{}'" + test + targetInum + ex); - return false; - } - - return true; - } - - private HashMap getInumCacheCompoundKeyMap( - HashMap primaryKeyAttrValueInumMap) { - HashMap result = new HashMap(); - - for (Entry primaryKeyAttrValueInumMapEntry : primaryKeyAttrValueInumMap - .entrySet()) { - result.put(primaryKeyAttrValueInumMapEntry.getValue().getInum(), primaryKeyAttrValueInumMapEntry.getKey()); - } - - return result; - } - - private Pair, List> removeTargetEntries(LdapServerConnection inumDbServerConnection, - PersistenceEntryManager targetPersistenceEntryManager, List removedPersons, - HashMap inumInumMap) { - - Date runDate = new Date(this.lastFinishedTime); - - PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); - List result1 = new ArrayList(); - List result2 = new ArrayList(); - - for (GluuSimplePerson removedPerson : removedPersons) { - String inum = removedPerson.getStringAttribute(JansConstants.inum); - - // Update GluuInumMap if it exist - JansInumMap currentInumMap = inumInumMap.get(inum); - if (currentInumMap == null) { - log.warn("Can't find inum entry of person with DN: {}"+ removedPerson.getDn()); - } else { - JansInumMap removedInumMap = getMarkInumMapEntryAsRemoved(currentInumMap, - getLdapEntryManager().encodeTime(removedPerson.getDn(), runDate)); - try { - inumDbPersistenceEntryManager.merge(removedInumMap); - result2.add(removedInumMap.getInum()); - } catch (BasePersistenceException ex) { - log.error("Failed to update entry with inum '{}' and DN: {}"+ currentInumMap.getInum(), - currentInumMap.getDn(), ex); - continue; - } - } - - // Remove person from target server - try { - //ldap ORM - if(targetPersistenceEntryManager.hasBranchesSupport(removedPerson.getDn())){ - targetPersistenceEntryManager.removeRecursively(removedPerson.getDn(), GluuCustomPerson.class); - - }else { - //other ORM - targetPersistenceEntryManager.remove(removedPerson.getDn(), GluuCustomPerson.class); - - Filter pairwiseIdentifiersFilter = Filter.createEqualityFilter(JansConstants.oxAuthUserId, removedPerson.getDn()); - targetPersistenceEntryManager.remove(pairwiseIdService.getDnForPairWiseIdentifier(null, removedPerson.getDn()), GluuUserPairwiseIdentifier.class, pairwiseIdentifiersFilter,0); - - Filter equalityFilter = Filter.createEqualityFilter("personInum", removedPerson.getDn()); - targetPersistenceEntryManager.remove(fidoDeviceService.getDnForFidoDevice(removedPerson.getDn(),null), GluuCustomFidoDevice.class, equalityFilter,0); - - Filter equalityFido2DeviceFilter = Filter.createEqualityFilter("personInum", removedPerson.getDn()); - targetPersistenceEntryManager.remove(fido2DeviceService.getDnForFido2Device(null, removedPerson.getDn()), GluuFido2Device.class, equalityFido2DeviceFilter,0); - } - result1.add(inum); - } catch (BasePersistenceException ex) { - log.error("Failed to remove person entry with inum '{}' and DN: {}"+ inum, removedPerson.getDn(), ex); - continue; - } - - log.info("Person with DN: '{}' removed from target server"+ removedPerson.getDn()); - } - - return new Pair, List>(result1, result2); - } - - private JansInumMap getMarkInumMapEntryAsRemoved(JansInumMap currentInumMap, String date) { - JansInumMap clonedInumMap; - try { - clonedInumMap = (JansInumMap) BeanUtilsBean2.getInstance().cloneBean(currentInumMap); - } catch (Exception ex) { - log.error("Failed to prepare GluuInumMap for removal"+ ex); - return null; - } - - String suffix = "-" + date; - - String[] primaryKeyValues = ArrayHelper.arrayClone(clonedInumMap.getPrimaryKeyValues()); - String[] secondaryKeyValues = ArrayHelper.arrayClone(clonedInumMap.getSecondaryKeyValues()); - String[] tertiaryKeyValues = ArrayHelper.arrayClone(clonedInumMap.getTertiaryKeyValues()); - - if (ArrayHelper.isNotEmpty(primaryKeyValues)) { - markInumMapEntryKeyValuesAsRemoved(primaryKeyValues, suffix); - } - - if (ArrayHelper.isNotEmpty(secondaryKeyValues)) { - markInumMapEntryKeyValuesAsRemoved(secondaryKeyValues, suffix); - } - - if (ArrayHelper.isNotEmpty(tertiaryKeyValues)) { - markInumMapEntryKeyValuesAsRemoved(tertiaryKeyValues, suffix); - } - - clonedInumMap.setPrimaryKeyValues(primaryKeyValues); - clonedInumMap.setSecondaryKeyValues(secondaryKeyValues); - clonedInumMap.setTertiaryKeyValues(tertiaryKeyValues); - - clonedInumMap.setStatus(GluuStatus.INACTIVE); - - return clonedInumMap; - } - - private void markInumMapEntryKeyValuesAsRemoved(String[] keyValues, String suffix) { - for (int i = 0; i < keyValues.length; i++) { - keyValues[i] = keyValues[i] + suffix; - } - } - - private List loadInumServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration, - LdapServerConnection inumDbServerConnection) { - PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); - String inumbaseDn = inumDbServerConnection.getBaseDns()[0]; - - Filter filterObjectClass = Filter.createEqualityFilter(OxConstants.OBJECT_CLASS, - JansConstants.objectClassInumMap); - Filter filterStatus = Filter.createNOTFilter( - Filter.createEqualityFilter(JansConstants.jansStatus, GluuStatus.INACTIVE.getValue())); - Filter filter = Filter.createANDFilter(filterObjectClass, filterStatus); - - return inumDbPersistenceEntryManager.findEntries(inumbaseDn, JansInumMap.class, filter, SearchScope.SUB, null, - null, 0, 0, cacheRefreshConfiguration.getLdapSearchSizeLimit()); - } - - private List loadSourceServerEntriesWithoutLimits( - CacheRefreshConfiguration cacheRefreshConfiguration, LdapServerConnection[] sourceServerConnections) - throws SearchException { - Filter customFilter = cacheRefreshService.createFilter(cacheRefreshConfiguration.getCustomLdapFilter()); - String[] keyAttributes = getCompoundKeyAttributes(cacheRefreshConfiguration); - String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); - String[] keyObjectClasses = getCompoundKeyObjectClasses(cacheRefreshConfiguration); - String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration); - - String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes); - - Set addedDns = new HashSet(); - - List sourcePersons = new ArrayList(); - for (LdapServerConnection sourceServerConnection : sourceServerConnections) { - String sourceServerName = sourceServerConnection.getSourceServerName(); - - PersistenceEntryManager sourcePersistenceEntryManager = sourceServerConnection.getPersistenceEntryManager(); - String[] baseDns = sourceServerConnection.getBaseDns(); - Filter filter = cacheRefreshService.createFilter(keyAttributes, keyObjectClasses, "", customFilter); - if (log.isTraceEnabled()) { - log.trace("Using next filter to load entris from source server: {}"+ filter); - } - - for (String baseDn : baseDns) { - List currentSourcePersons = sourcePersistenceEntryManager.findEntries(baseDn, - GluuSimplePerson.class, filter, SearchScope.SUB, returnAttributes, null, 0, 0, - cacheRefreshConfiguration.getLdapSearchSizeLimit()); - - // Add to result and ignore root entry if needed - for (GluuSimplePerson currentSourcePerson : currentSourcePersons) { - currentSourcePerson.setSourceServerName(sourceServerName); - // if (!StringHelper.equalsIgnoreCase(baseDn, - // currentSourcePerson.getDn())) { - String currentSourcePersonDn = currentSourcePerson.getDn().toLowerCase(); - if (!addedDns.contains(currentSourcePersonDn)) { - sourcePersons.add(currentSourcePerson); - addedDns.add(currentSourcePersonDn); - } - // } - } - } - } - - return sourcePersons; - } - - private List loadSourceServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration, - LdapServerConnection[] sourceServerConnections) throws SearchException { - Filter customFilter = cacheRefreshService.createFilter(cacheRefreshConfiguration.getCustomLdapFilter()); - String[] keyAttributes = getCompoundKeyAttributes(cacheRefreshConfiguration); - String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); - String[] keyObjectClasses = getCompoundKeyObjectClasses(cacheRefreshConfiguration); - String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration); - - String[] twoLettersArray = createTwoLettersArray(); - String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes); - - Set addedDns = new HashSet(); - - List sourcePersons = new ArrayList(); - for (LdapServerConnection sourceServerConnection : sourceServerConnections) { - String sourceServerName = sourceServerConnection.getSourceServerName(); - - PersistenceEntryManager sourcePersistenceEntryManager = sourceServerConnection.getPersistenceEntryManager(); - String[] baseDns = sourceServerConnection.getBaseDns(); - for (String keyAttributeStart : twoLettersArray) { - Filter filter = cacheRefreshService.createFilter(keyAttributes, keyObjectClasses, keyAttributeStart, - customFilter); - if (log.isDebugEnabled()) { - log.trace("Using next filter to load entris from source server: {}"+ filter); - } - - for (String baseDn : baseDns) { - List currentSourcePersons = sourcePersistenceEntryManager.findEntries(baseDn, - GluuSimplePerson.class, filter, SearchScope.SUB, returnAttributes, null, 0, 0, - cacheRefreshConfiguration.getLdapSearchSizeLimit()); - - // Add to result and ignore root entry if needed - for (GluuSimplePerson currentSourcePerson : currentSourcePersons) { - currentSourcePerson.setSourceServerName(sourceServerName); - // if (!StringHelper.equalsIgnoreCase(baseDn, - // currentSourcePerson.getDn())) { - String currentSourcePersonDn = currentSourcePerson.getDn().toLowerCase(); - if (!addedDns.contains(currentSourcePersonDn)) { - sourcePersons.add(currentSourcePerson); - addedDns.add(currentSourcePersonDn); - } - // } - } - } - } - } - - return sourcePersons; - } - - private List loadTargetServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration, - PersistenceEntryManager targetPersistenceEntryManager) { - Filter filter = Filter.createEqualityFilter(OxConstants.OBJECT_CLASS, JansConstants.objectClassPerson); - - return targetPersistenceEntryManager.findEntries(personService.getDnForPerson(null), TypedGluuSimplePerson.class, - filter, SearchScope.SUB, TARGET_PERSON_RETURN_ATTRIBUTES, null, 0, 0, - cacheRefreshConfiguration.getLdapSearchSizeLimit()); - } - - private JansInumMap addGluuInumMap(String inumbBaseDn, PersistenceEntryManager inumDbPersistenceEntryManager, - String[] primaryKeyAttrName, String[][] primaryKeyValues) { - String inum = cacheRefreshService.generateInumForNewInumMap(inumbBaseDn, inumDbPersistenceEntryManager); - String inumDn = cacheRefreshService.getDnForInum(inumbBaseDn, inum); - - JansInumMap inumMap = new JansInumMap(); - inumMap.setDn(inumDn); - inumMap.setInum(inum); - inumMap.setPrimaryKeyAttrName(primaryKeyAttrName[0]); - inumMap.setPrimaryKeyValues(primaryKeyValues[0]); - if (primaryKeyAttrName.length > 1) { - inumMap.setSecondaryKeyAttrName(primaryKeyAttrName[1]); - inumMap.setSecondaryKeyValues(primaryKeyValues[1]); - } - if (primaryKeyAttrName.length > 2) { - inumMap.setTertiaryKeyAttrName(primaryKeyAttrName[2]); - inumMap.setTertiaryKeyValues(primaryKeyValues[2]); - } - inumMap.setStatus(GluuStatus.ACTIVE); - cacheRefreshService.addInumMap(inumDbPersistenceEntryManager, inumMap); - - return inumMap; - } - - private HashMap addNewInumServerEntries( - CacheRefreshConfiguration cacheRefreshConfiguration, LdapServerConnection inumDbServerConnection, - Map sourcePersonCacheCompoundKeyMap, - HashMap primaryKeyAttrValueInumMap) { - PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); - String inumbaseDn = inumDbServerConnection.getBaseDns()[0]; - - HashMap result = new HashMap(); - - String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); - for (Entry sourcePersonCacheCompoundKeyEntry : sourcePersonCacheCompoundKeyMap - .entrySet()) { - CacheCompoundKey cacheCompoundKey = sourcePersonCacheCompoundKeyEntry.getKey(); - GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyEntry.getValue(); - - if (log.isTraceEnabled()) { - log.trace("Checking source entry with key: '{}', and DN: {}"+ cacheCompoundKey, sourcePerson.getDn()); - } - - JansInumMap currentInumMap = primaryKeyAttrValueInumMap.get(cacheCompoundKey); - if (currentInumMap == null) { - String[][] keyAttributesValues = getKeyAttributesValues(keyAttributesWithoutValues, sourcePerson); - currentInumMap = addGluuInumMap(inumbaseDn, inumDbPersistenceEntryManager, keyAttributesWithoutValues, - keyAttributesValues); - result.put(cacheCompoundKey, currentInumMap); - log.info("Added new inum entry for DN: {}"+ sourcePerson.getDn()); - } else { - log.trace("Inum entry for DN: '{}' exist"+ sourcePerson.getDn()); - } - } - - return result; - } - - private HashMap getAllInumServerEntries( - HashMap primaryKeyAttrValueInumMap, - HashMap addedPrimaryKeyAttrValueInumMap) { - HashMap result = new HashMap(); - - result.putAll(primaryKeyAttrValueInumMap); - result.putAll(addedPrimaryKeyAttrValueInumMap); - - return result; - } - - private HashMap getSourcePersonsHashCodesMap(LdapServerConnection inumDbServerConnection, - Map sourcePersonCacheCompoundKeyMap, - HashMap primaryKeyAttrValueInumMap) { - PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); - - HashMap result = new HashMap(); - - for (Entry sourcePersonCacheCompoundKeyEntry : sourcePersonCacheCompoundKeyMap - .entrySet()) { - CacheCompoundKey cacheCompoundKey = sourcePersonCacheCompoundKeyEntry.getKey(); - GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyEntry.getValue(); - - JansInumMap currentInumMap = primaryKeyAttrValueInumMap.get(cacheCompoundKey); - - result.put(currentInumMap.getInum(), inumDbPersistenceEntryManager.getHashCode(sourcePerson)); - } - - return result; - } - - private List processTargetPersons(List targetPersons, - HashMap currInumWithEntryHashCodeMap) { - List result = new ArrayList(); - - for (GluuSimplePerson targetPerson : targetPersons) { - String personInum = targetPerson.getStringAttribute(JansConstants.inum); - if (!currInumWithEntryHashCodeMap.containsKey(personInum)) { - log.info("Person with such DN: '{}' isn't present on source server"+ targetPerson.getDn()); - result.add(targetPerson); - } - } - - return result; - } - - private HashMap getPrimaryKeyAttrValueInumMap(List inumMaps) { - HashMap result = new HashMap(); - - for (JansInumMap inumMap : inumMaps) { - result.put(new CacheCompoundKey(inumMap.getPrimaryKeyValues(), inumMap.getSecondaryKeyValues(), - inumMap.getTertiaryKeyValues()), inumMap); - } - - return result; - } - - private HashMap getInumInumMap(List inumMaps) { - HashMap result = new HashMap(); - - for (JansInumMap inumMap : inumMaps) { - result.put(inumMap.getInum(), inumMap); - } - - return result; - } - - private Map getSourcePersonCompoundKeyMap( - CacheRefreshConfiguration cacheRefreshConfiguration, List sourcePersons) { - Map result = new HashMap(); - Set duplicateKeys = new HashSet(); - - String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); - for (GluuSimplePerson sourcePerson : sourcePersons) { - String[][] keyAttributesValues = getKeyAttributesValues(keyAttributesWithoutValues, sourcePerson); - CacheCompoundKey cacheCompoundKey = new CacheCompoundKey(keyAttributesValues); - - if (result.containsKey(cacheCompoundKey)) { - duplicateKeys.add(cacheCompoundKey); - } - - result.put(cacheCompoundKey, sourcePerson); - } - - for (CacheCompoundKey duplicateKey : duplicateKeys) { - log.error("Non-deterministic primary key. Skipping user with key: {}"+ duplicateKey); - result.remove(duplicateKey); - } - - return result; - } - - private LdapServerConnection[] prepareLdapServerConnections(CacheRefreshConfiguration cacheRefreshConfiguration, - List ldapConfigurations) { - if(null == ldapConfigurations ){ - return null; - } - - LdapServerConnection[] ldapServerConnections = new LdapServerConnection[ldapConfigurations.size()]; - for (int i = 0; i < ldapConfigurations.size(); i++) { - ldapServerConnections[i] = prepareLdapServerConnection(cacheRefreshConfiguration, - ldapConfigurations.get(i)); - if (ldapServerConnections[i] == null) { - return null; - } - } - - return ldapServerConnections; - } - - private LdapServerConnection prepareLdapServerConnection(CacheRefreshConfiguration cacheRefreshConfiguration, - GluuLdapConfiguration ldapConfiguration) { - return prepareLdapServerConnection(cacheRefreshConfiguration, ldapConfiguration, false); - } - - private LdapServerConnection prepareLdapServerConnection(CacheRefreshConfiguration cacheRefreshConfiguration, - GluuLdapConfiguration ldapConfiguration, boolean useLocalConnection) { - String ldapConfig = ldapConfiguration.getConfigId(); - - if (useLocalConnection) { - return new LdapServerConnection(ldapConfig, getLdapEntryManager(), getBaseDNs(ldapConfiguration)); - } - PersistenceEntryManagerFactory entryManagerFactory = applicationFactory - .getPersistenceEntryManagerFactory(LdapEntryManagerFactory.class); - String persistenceType = entryManagerFactory.getPersistenceType(); - - Properties ldapProperties = toLdapProperties(entryManagerFactory, ldapConfiguration); - Properties ldapDecryptedProperties = encryptionService.decryptAllProperties(ldapProperties); - - // Try to get updated password via script - BindCredentials bindCredentials = externalCacheRefreshService - .executeExternalGetBindCredentialsMethods(ldapConfig); - String bindPasswordPropertyKey = persistenceType + "#" + PropertiesDecrypter.BIND_PASSWORD; - if (bindCredentials != null) { - log.error("Using updated password which got from getBindCredentials method"); - ldapDecryptedProperties.setProperty(persistenceType + ".bindDN", bindCredentials.getBindDn()); - ldapDecryptedProperties.setProperty(bindPasswordPropertyKey, - bindCredentials.getBindPassword()); - } - - if (log.isTraceEnabled()) { - Properties clonedLdapDecryptedProperties = (Properties) ldapDecryptedProperties.clone(); - if (clonedLdapDecryptedProperties.getProperty(bindPasswordPropertyKey) != null) { - clonedLdapDecryptedProperties.setProperty(bindPasswordPropertyKey, "REDACTED"); - } - log.trace("Attempting to create PersistenceEntryManager with properties: {}"+ clonedLdapDecryptedProperties); - } - PersistenceEntryManager customPersistenceEntryManager = entryManagerFactory - .createEntryManager(ldapDecryptedProperties); - log.info("Created Cache Refresh PersistenceEntryManager: {}"+ customPersistenceEntryManager); - - if (!customPersistenceEntryManager.getOperationService().isConnected()) { - log.error("Failed to connect to LDAP server using configuration {}"+ ldapConfig); - return null; - } - - return new LdapServerConnection(ldapConfig, customPersistenceEntryManager, getBaseDNs(ldapConfiguration)); - } - - private void closeLdapServerConnection(LdapServerConnection... ldapServerConnections) { - for (LdapServerConnection ldapServerConnection : ldapServerConnections) { - if ((ldapServerConnection != null) && (ldapServerConnection.getPersistenceEntryManager() != null)) { - ldapServerConnection.getPersistenceEntryManager().destroy(); - } - } - } - - private String[] createTwoLettersArray() { - char[] characters = LETTERS_FOR_SEARCH.toCharArray(); - int lettersCount = characters.length; - - String[] result = new String[lettersCount * lettersCount]; - for (int i = 0; i < lettersCount; i++) { - for (int j = 0; j < lettersCount; j++) { - result[i * lettersCount + j] = "" + characters[i] + characters[j]; - } - } - - return result; - } - - private String[][] getKeyAttributesValues(String[] attrs, GluuSimplePerson person) { - String[][] result = new String[attrs.length][]; - for (int i = 0; i < attrs.length; i++) { - result[i] = person.getStringAttributes(attrs[i]); - } - - return result; - } - - private void updateStatus(AppConfiguration currentConfiguration, long lastRun) { - Date currentDateTime = new Date(); - currentConfiguration.setLastUpdate(currentDateTime); - currentConfiguration.setLastUpdateCount(currentConfiguration.getLastUpdateCount()); - currentConfiguration.setProblemCount(currentConfiguration.getProblemCount()); - CacheRefrshConfigurationService.updateConfiguration(currentConfiguration); - } - - private String getInumCachePath(CacheRefreshConfiguration cacheRefreshConfiguration) { - return FilenameUtils.concat(cacheRefreshConfiguration.getSnapshotFolder(), "inum_cache.dat"); - } - - public PersistenceEntryManager getLdapEntryManager() { - return ldapEntryManager; - } - - public void setLdapEntryManager(PersistenceEntryManager ldapEntryManager) { - this.ldapEntryManager = ldapEntryManager; - } - - public ConfigurationFactory getConfigurationFactory() { - return configurationFactory; - } - - public void setConfigurationFactory(ConfigurationFactory configurationFactory) { - this.configurationFactory = configurationFactory; - } - - private class LdapServerConnection { - private String sourceServerName; - private PersistenceEntryManager ldapEntryManager; - private String[] baseDns; - - protected LdapServerConnection(String sourceServerName, PersistenceEntryManager ldapEntryManager, - String[] baseDns) { - this.sourceServerName = sourceServerName; - this.ldapEntryManager = ldapEntryManager; - this.baseDns = baseDns; - } - - public final String getSourceServerName() { - return sourceServerName; - } - - public final PersistenceEntryManager getPersistenceEntryManager() { - return ldapEntryManager; - } - - public final String[] getBaseDns() { - return baseDns; - } - } - - private CacheRefreshUpdateMethod getUpdateMethod(CacheRefreshConfiguration cacheRefreshConfiguration) { - String updateMethod = cacheRefreshConfiguration.getUpdateMethod(); - if (StringHelper.isEmpty(updateMethod)) { - return CacheRefreshUpdateMethod.COPY; - } - - return CacheRefreshUpdateMethod.getByValue(cacheRefreshConfiguration.getUpdateMethod()); - } - - private String[] getSourceAttributes(CacheRefreshConfiguration cacheRefreshConfiguration) { - return cacheRefreshConfiguration.getSourceAttributes().toArray(new String[0]); - } - - private String[] getCompoundKeyObjectClasses(CacheRefreshConfiguration cacheRefreshConfiguration) { - return cacheRefreshConfiguration.getKeyObjectClasses().toArray(new String[0]); - } - - private String[] getCompoundKeyAttributes(CacheRefreshConfiguration cacheRefreshConfiguration) { - return cacheRefreshConfiguration.getKeyAttributes().toArray(new String[0]); - } - - private String[] getCompoundKeyAttributesWithoutValues(CacheRefreshConfiguration cacheRefreshConfiguration) { - String[] result = cacheRefreshConfiguration.getKeyAttributes().toArray(new String[0]); - for (int i = 0; i < result.length; i++) { - int index = result[i].indexOf('='); - if (index != -1) { - result[i] = result[i].substring(0, index); - } - } - - return result; - } - - private Map getTargetServerAttributesMapping(CacheRefreshConfiguration cacheRefreshConfiguration) { - Map result = new HashMap(); - for (CacheRefreshAttributeMapping attributeMapping : cacheRefreshConfiguration.getAttributeMapping()) { - result.put(attributeMapping.getDestination(), attributeMapping.getSource()); - } - - return result; - } - - private Properties toLdapProperties(PersistenceEntryManagerFactory ldapEntryManagerFactory, - GluuLdapConfiguration ldapConfiguration) { - String persistenceType = ldapEntryManagerFactory.getPersistenceType(); - Properties ldapProperties = new Properties(); - ldapProperties.put(persistenceType + "#servers", - PropertyUtil.simplePropertiesToCommaSeparatedList(ldapConfiguration.getServers())); - ldapProperties.put(persistenceType + "#maxconnections", - Integer.toString(ldapConfiguration.getMaxConnections())); - ldapProperties.put(persistenceType + "#useSSL", Boolean.toString(ldapConfiguration.isUseSSL())); - ldapProperties.put(persistenceType + "#bindDN", ldapConfiguration.getBindDN()); - ldapProperties.put(persistenceType + "#bindPassword", ldapConfiguration.getBindPassword()); - - // Copy binary attributes list from main LDAP connection - PersistenceOperationService persistenceOperationService = getLdapEntryManager().getOperationService(); - if (persistenceOperationService instanceof LdapOperationService) { - ldapProperties.put(persistenceType + "#binaryAttributes", - PropertyUtil.stringsToCommaSeparatedList(((LdapOperationService) persistenceOperationService) - .getConnectionProvider().getBinaryAttributes())); - } - - return ldapProperties; - } - - private String[] getBaseDNs(GluuLdapConfiguration ldapConfiguration) { - return ldapConfiguration.getBaseDNsStringsList().toArray(new String[0]); - } - - @ObjectClass(value = "gluuPerson") - class TypedGluuSimplePerson extends GluuSimplePerson { - public TypedGluuSimplePerson() { - super(); - } - } - - public AtomicBoolean getIsActive() { - return isActive; - } - - public void setIsActive(AtomicBoolean isActive) { - this.isActive = isActive; - } - -} diff --git a/jans-link/server/src/main/java/io/jans/link/timer/JansLinkTimer.java b/jans-link/server/src/main/java/io/jans/link/timer/JansLinkTimer.java new file mode 100644 index 00000000000..49ebfa332e2 --- /dev/null +++ b/jans-link/server/src/main/java/io/jans/link/timer/JansLinkTimer.java @@ -0,0 +1,834 @@ +/* + * oxTrust is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2014, Gluu + */ + +package io.jans.link.timer; + +import io.jans.link.constants.JansConstants; +import io.jans.link.event.CacheRefreshEvent; +import io.jans.link.external.ExternalCacheRefreshService; +import io.jans.link.model.*; +import io.jans.link.model.config.AppConfiguration; +import io.jans.link.model.config.CacheRefreshConfiguration; +import io.jans.link.server.service.CacheRefrshConfigurationService; +import io.jans.link.service.*; +import io.jans.link.service.config.ApplicationFactory; +import io.jans.link.service.config.ConfigurationFactory; +import io.jans.model.GluuStatus; +import io.jans.model.custom.script.model.bind.BindCredentials; +import io.jans.model.ldap.GluuLdapConfiguration; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.PersistenceEntryManagerFactory; +import io.jans.orm.exception.BasePersistenceException; +import io.jans.orm.exception.operation.SearchException; +import io.jans.orm.ldap.impl.LdapEntryManagerFactory; +import io.jans.orm.model.SearchScope; +import io.jans.orm.search.filter.Filter; +import io.jans.orm.util.ArrayHelper; +import io.jans.orm.util.StringHelper; +import io.jans.service.ObjectSerializationService; +import io.jans.service.cdi.async.Asynchronous; +import io.jans.service.cdi.event.Scheduled; +import io.jans.service.timer.event.TimerEvent; +import io.jans.service.timer.schedule.TimerSchedule; +import io.jans.util.OxConstants; +import io.jans.util.Pair; +import io.jans.util.security.PropertiesDecrypter; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import org.apache.commons.beanutils.BeanUtilsBean2; +import org.slf4j.Logger; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Check periodically if source servers contains updates and trigger target + * server entry update if needed + * + * @author Yuriy Movchan Date: 05.05.2011 + */ +@ApplicationScoped +public class JansLinkTimer extends BaseJansLinkTimer { + + private static final String LETTERS_FOR_SEARCH = "abcdefghijklmnopqrstuvwxyz1234567890."; + private static final String[] TARGET_PERSON_RETURN_ATTRIBUTES = { JansConstants.inum }; + + private static final int DEFAULT_INTERVAL = 60; + + @Inject + private Logger log; + + @Inject + private Event timerEvent; + + @Inject + protected ApplicationFactory applicationFactory; + + @Inject + private AppConfiguration appConfiguration; + + @Inject + private ConfigurationFactory configurationFactory; + + @Inject + private CacheRefreshSnapshotFileService cacheRefreshSnapshotFileService; + + @Inject + private ExternalCacheRefreshService externalCacheRefreshService; + + @Inject + private CacheRefrshConfigurationService CacheRefrshConfigurationService; + + @Inject + private EncryptionService encryptionService; + + @Inject + private PairwiseIdService pairwiseIdService; + + @Inject + private FidoDeviceService fidoDeviceService; + + @Inject + private Fido2DeviceService fido2DeviceService; + + @Inject + private ObjectSerializationService objectSerializationService; + + private AtomicBoolean isActive; + private long lastFinishedTime; + + public void initTimer() { + log.info("Initializing jans link Cache Refresh Timer"); + this.isActive = new AtomicBoolean(false); + + // Clean up previous Inum cache + CacheRefreshConfiguration cacheRefreshConfiguration = getConfigurationFactory().getAppConfiguration(); + if (cacheRefreshConfiguration != null) { + String snapshotFolder = cacheRefreshConfiguration.getSnapshotFolder(); + if (StringHelper.isNotEmpty(snapshotFolder)) { + String inumCachePath = getInumCachePath(cacheRefreshConfiguration); + objectSerializationService.cleanup(inumCachePath); + } + } + + // Schedule to start cache refresh every 1 minute + timerEvent.fire(new TimerEvent(new TimerSchedule(DEFAULT_INTERVAL, DEFAULT_INTERVAL), new CacheRefreshEvent(), + Scheduled.Literal.INSTANCE)); + + this.lastFinishedTime = System.currentTimeMillis(); + } + + @Asynchronous + public void process(@Observes @Scheduled CacheRefreshEvent cacheRefreshEvent) { + if (this.isActive.get()) { + log.info("Another process is active"); + return; + } + + if (!this.isActive.compareAndSet(false, true)) { + log.info("Failed to start process exclusively"); + return; + } + + try { + processInt(); + } finally { + log.info("Allowing to run new process exclusively"); + this.isActive.set(false); + } + } + + public void processInt() { + AppConfiguration currentConfiguration = getConfigurationFactory().getAppConfiguration(); + try { + currentConfiguration.setServerIpAddress("255.255.255.255"); + if (!isStartCacheRefresh(currentConfiguration)) { + log.info("Starting conditions aren't reached"); + return; + } + + processImpl(currentConfiguration); + updateStatus(currentConfiguration, System.currentTimeMillis()); + + this.lastFinishedTime = System.currentTimeMillis(); + } catch (Throwable ex) { + ex.printStackTrace(); + log.info("Exception happened while executing cache refresh synchronization"+ ex); + } + } + + private boolean isStartCacheRefresh(AppConfiguration currentConfiguration) { + if (!currentConfiguration.isLinkEnabled()) { + return false; + } + + long poolingInterval = StringHelper.toInteger(currentConfiguration.getPollingInterval()); + if (poolingInterval < 0) { + return false; + } + + if(null == currentConfiguration.getSourceConfigs()){ + log.info("Source Config is null, nothing to load "); + return false; + } + + String cacheRefreshServerIpAddress = currentConfiguration.getServerIpAddress(); + // if (StringHelper.isEmpty(cacheRefreshServerIpAddress)) { + // log.debug("There is no master Cache Refresh server"); + // return false; + // } + + // Compare server IP address with cacheRefreshServerIp + boolean cacheRefreshServer = false; + try { + Enumeration nets = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface networkInterface : Collections.list(nets)) { + Enumeration inetAddresses = networkInterface.getInetAddresses(); + for (InetAddress inetAddress : Collections.list(inetAddresses)) { + if (StringHelper.equals(cacheRefreshServerIpAddress, inetAddress.getHostAddress())) { + cacheRefreshServer = true; + break; + } + } + + if (cacheRefreshServer) { + break; + } + } + } catch (SocketException ex) { + log.error("Failed to enumerate server IP addresses"+ ex); + } + + if (!cacheRefreshServer) { + cacheRefreshServer = externalCacheRefreshService.executeExternalIsStartProcessMethods(); + cacheRefreshServer = true; + } + + if (!cacheRefreshServer) { + log.info("This server isn't master Cache Refresh server"); + return false; + } + + // Check if cache refresh specific configuration was loaded + if (currentConfiguration == null) { + log.info("Failed to start cache refresh. Can't loading configuration from oxTrustCacheRefresh.properties"); + return false; + } + + long timeDiffrence = System.currentTimeMillis() - this.lastFinishedTime; + timeDiffrence = timeDiffrence/1000; + + return timeDiffrence >= poolingInterval; + } + + private void processImpl(AppConfiguration currentConfiguration) + throws SearchException { + CacheRefreshUpdateMethod updateMethod = getUpdateMethod(currentConfiguration); + + // Prepare and check connections to LDAP servers + LdapServerConnection[] sourceServerConnections = prepareLdapServerConnections(currentConfiguration, + currentConfiguration.getSourceConfigs()); + + LdapServerConnection inumDbServerConnection; + if (currentConfiguration.isDefaultInumServer()) { + GluuLdapConfiguration ldapInumConfiguration = new GluuLdapConfiguration(); + ldapInumConfiguration.setConfigId("local_inum"); + ldapInumConfiguration.setBaseDNsStringsList( + Arrays.asList(new String[] { JansConstants.CACHE_REFRESH_DEFAULT_BASE_DN })); + + inumDbServerConnection = prepareLdapServerConnection(currentConfiguration, ldapInumConfiguration, + true); + } else { + inumDbServerConnection = prepareLdapServerConnection(currentConfiguration, + currentConfiguration.getInumConfig()); + } + + boolean isVdsUpdate = CacheRefreshUpdateMethod.VDS.equals(updateMethod); + LdapServerConnection targetServerConnection = null; + if (isVdsUpdate) { + targetServerConnection = prepareLdapServerConnection(currentConfiguration, + currentConfiguration.getTargetConfig()); + } + + try { + if ((sourceServerConnections == null) || (inumDbServerConnection == null) + || (isVdsUpdate && (targetServerConnection == null))) { + log.error("Skipping cache refresh due to invalid server configuration"); + } else { + detectChangedEntries(currentConfiguration, sourceServerConnections, + inumDbServerConnection, targetServerConnection, updateMethod); + } + } finally { + // Close connections to LDAP servers + try { + closeLdapServerConnection(sourceServerConnections); + } catch (Exception e) { + // Nothing can be done + } + + if (!currentConfiguration.isDefaultInumServer()) { + try { + closeLdapServerConnection(inumDbServerConnection); + } catch (Exception e) { + // Nothing can be done + } + } + try { + if (isVdsUpdate) { + closeLdapServerConnection(targetServerConnection); + } + } catch (Exception e) { + // Nothing can be done + } + } + + return; + } + + @SuppressWarnings("unchecked") + private boolean detectChangedEntries( AppConfiguration currentConfiguration, LdapServerConnection[] sourceServerConnections, + LdapServerConnection inumDbServerConnection, LdapServerConnection targetServerConnection, + CacheRefreshUpdateMethod updateMethod) throws SearchException { + boolean isVDSMode = CacheRefreshUpdateMethod.VDS.equals(updateMethod); + + // Load all entries from Source servers + log.info("Attempting to load entries from source server"); + List sourcePersons; + + if (currentConfiguration.isUseSearchLimit()) { + sourcePersons = loadSourceServerEntries(currentConfiguration, sourceServerConnections); + } else { + sourcePersons = loadSourceServerEntriesWithoutLimits(currentConfiguration, sourceServerConnections); + } + + log.info("Found '{}' entries in source server"+ sourcePersons.size()); + + Map sourcePersonCacheCompoundKeyMap = getSourcePersonCompoundKeyMap( + currentConfiguration, sourcePersons); + log.info("Found '{}' unique entries in source server"+ sourcePersonCacheCompoundKeyMap.size()); + + // Load all inum entries + List inumMaps = null; + + // Load all inum entries from local disk cache + String inumCachePath = getInumCachePath(currentConfiguration); + Object loadedObject = objectSerializationService.loadObject(inumCachePath); + if (loadedObject != null) { + try { + inumMaps = (List) loadedObject; + log.info("Found '{}' entries in inum objects disk cache"+ inumMaps.size()); + } catch (Exception ex) { + log.error("Failed to convert to GluuInumMap list"+ ex); + objectSerializationService.cleanup(inumCachePath); + } + } + + if (inumMaps == null) { + // Load all inum entries from LDAP + inumMaps = loadInumServerEntries(currentConfiguration, inumDbServerConnection); + log.info("Found '{}' entries in inum server"+ inumMaps.size()); + } + + HashMap primaryKeyAttrValueInumMap = getPrimaryKeyAttrValueInumMap(inumMaps); + + // Go through Source entries and create new InumMap entries if needed + HashMap addedPrimaryKeyAttrValueInumMap = addNewInumServerEntries( + currentConfiguration, inumDbServerConnection, sourcePersonCacheCompoundKeyMap, + primaryKeyAttrValueInumMap); + + HashMap allPrimaryKeyAttrValueInumMap = getAllInumServerEntries( + primaryKeyAttrValueInumMap, addedPrimaryKeyAttrValueInumMap); + log.info("Count actual inum entries '{}' after updating inum server"+ allPrimaryKeyAttrValueInumMap.size()); + + HashMap currInumWithEntryHashCodeMap = getSourcePersonsHashCodesMap(inumDbServerConnection, + sourcePersonCacheCompoundKeyMap, allPrimaryKeyAttrValueInumMap); + log.info("Count actual source entries '{}' after calculating hash code"+ currInumWithEntryHashCodeMap.size()); + + // Create snapshots cache folder if needed + boolean result = cacheRefreshSnapshotFileService.prepareSnapshotsFolder(currentConfiguration); + if (!result) { + return false; + } + + // Load last snapshot into memory + Map prevInumWithEntryHashCodeMap = cacheRefreshSnapshotFileService + .readLastSnapshot(currentConfiguration); + + // Compare 2 snapshot and invoke update if needed + Set changedInums = getChangedInums(currInumWithEntryHashCodeMap, prevInumWithEntryHashCodeMap, + isVDSMode); + log.info("Found '{}' changed entries"+ changedInums.size()); + + // Load problem list from disk and add to changedInums + List problemInums = cacheRefreshSnapshotFileService.readProblemList(currentConfiguration); + if (problemInums != null) { + log.info("Loaded '{}' problem entries from problem file"+ problemInums.size()); + // Process inums from problem list too + changedInums.addAll(problemInums); + } + + List updatedInums = null; + if (isVDSMode) { + // Update request to VDS to update entries on target server + updatedInums = updateTargetEntriesViaVDS(currentConfiguration, targetServerConnection, changedInums); + } else { + updatedInums = updateTargetEntriesViaCopy(currentConfiguration, sourcePersonCacheCompoundKeyMap, + allPrimaryKeyAttrValueInumMap, changedInums); + } + + log.info("Updated '{}' entries"+ updatedInums.size()); + changedInums.removeAll(updatedInums); + log.info("Failed to update '{}' entries"+ changedInums.size()); + + // Persist snapshot to cache folder + result = cacheRefreshSnapshotFileService.createSnapshot(currentConfiguration, + currInumWithEntryHashCodeMap); + if (!result) { + return false; + } + + // Retain only specified number of snapshots + cacheRefreshSnapshotFileService.retainSnapshots(currentConfiguration, + currentConfiguration.getSnapshotMaxCount()); + + // Save changedInums as problem list to disk + currentConfiguration.setProblemCount(String.valueOf(changedInums.size())); + cacheRefreshSnapshotFileService.writeProblemList(currentConfiguration, changedInums); + + // Prepare list of persons for removal + List personsForRemoval = null; + + boolean keepExternalPerson = currentConfiguration.isKeepExternalPerson(); + log.info("Keep external persons: '{}'"+ keepExternalPerson); + if (keepExternalPerson) { + // Determine entries which need to remove + personsForRemoval = getRemovedPersons(currInumWithEntryHashCodeMap, prevInumWithEntryHashCodeMap); + } else { + // Process entries which don't exist in source server + + // Load all entries from Target server + List targetPersons = loadTargetServerEntries(currentConfiguration, getLdapEntryManager()); + log.info("Found '{}' entries in target server"+ targetPersons.size()); + + // Detect entries which need to remove + personsForRemoval = processTargetPersons(targetPersons, currInumWithEntryHashCodeMap); + } + log.info("Count entries '{}' for removal from target server"+ personsForRemoval.size()); + + // Remove entries from target server + HashMap inumInumMap = getInumInumMap(inumMaps); + Pair, List> removeTargetEntriesResult = removeTargetEntries(inumDbServerConnection, + getLdapEntryManager(), personsForRemoval, inumInumMap); + List removedPersonInums = removeTargetEntriesResult.getFirst(); + List removedGluuInumMaps = removeTargetEntriesResult.getSecond(); + log.info("Removed '{}' persons from target server"+ removedPersonInums.size()); + + // Prepare list of inum for serialization + ArrayList currentInumMaps = applyChangesToInumMap(inumInumMap, addedPrimaryKeyAttrValueInumMap, + removedGluuInumMaps); + + // Strore all inum entries into local disk cache + objectSerializationService.saveObject(inumCachePath, currentInumMaps); + + currentConfiguration + .setLastUpdateCount(String.valueOf(updatedInums.size() + removedPersonInums.size())); + + return true; + } + + public LdapServerConnection prepareLdapServerConnection(CacheRefreshConfiguration cacheRefreshConfiguration, + GluuLdapConfiguration ldapConfiguration) { + return prepareLdapServerConnection(cacheRefreshConfiguration, ldapConfiguration, false); + } + + private LdapServerConnection prepareLdapServerConnection(CacheRefreshConfiguration cacheRefreshConfiguration, + GluuLdapConfiguration ldapConfiguration, boolean useLocalConnection) { + String ldapConfig = ldapConfiguration.getConfigId(); + + if (useLocalConnection) { + return new LdapServerConnection(ldapConfig, getLdapEntryManager(), getBaseDNs(ldapConfiguration)); + } + PersistenceEntryManagerFactory entryManagerFactory = applicationFactory + .getPersistenceEntryManagerFactory(LdapEntryManagerFactory.class); + String persistenceType = entryManagerFactory.getPersistenceType(); + + Properties ldapProperties = toLdapProperties(entryManagerFactory, ldapConfiguration); + Properties ldapDecryptedProperties = encryptionService.decryptAllProperties(ldapProperties); + + // Try to get updated password via script + BindCredentials bindCredentials = externalCacheRefreshService + .executeExternalGetBindCredentialsMethods(ldapConfig); + String bindPasswordPropertyKey = persistenceType + "#" + PropertiesDecrypter.BIND_PASSWORD; + if (bindCredentials != null) { + log.error("Using updated password which got from getBindCredentials method"); + ldapDecryptedProperties.setProperty(persistenceType + ".bindDN", bindCredentials.getBindDn()); + ldapDecryptedProperties.setProperty(bindPasswordPropertyKey, + bindCredentials.getBindPassword()); + } + + if (log.isTraceEnabled()) { + Properties clonedLdapDecryptedProperties = (Properties) ldapDecryptedProperties.clone(); + if (clonedLdapDecryptedProperties.getProperty(bindPasswordPropertyKey) != null) { + clonedLdapDecryptedProperties.setProperty(bindPasswordPropertyKey, "REDACTED"); + } + log.trace("Attempting to create PersistenceEntryManager with properties: {}"+ clonedLdapDecryptedProperties); + } + PersistenceEntryManager customPersistenceEntryManager = entryManagerFactory + .createEntryManager(ldapDecryptedProperties); + log.info("Created Cache Refresh PersistenceEntryManager: {}"+ customPersistenceEntryManager); + + if (!customPersistenceEntryManager.getOperationService().isConnected()) { + log.error("Failed to connect to LDAP server using configuration {}"+ ldapConfig); + return null; + } + + return new LdapServerConnection(ldapConfig, customPersistenceEntryManager, getBaseDNs(ldapConfiguration)); + } + + private Pair, List> removeTargetEntries(LdapServerConnection inumDbServerConnection, + PersistenceEntryManager targetPersistenceEntryManager, List removedPersons, + HashMap inumInumMap) { + + Date runDate = new Date(this.lastFinishedTime); + + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + List result1 = new ArrayList(); + List result2 = new ArrayList(); + + for (GluuSimplePerson removedPerson : removedPersons) { + String inum = removedPerson.getStringAttribute(JansConstants.inum); + + // Update GluuInumMap if it exist + JansInumMap currentInumMap = inumInumMap.get(inum); + if (currentInumMap == null) { + log.warn("Can't find inum entry of person with DN: {}"+ removedPerson.getDn()); + } else { + JansInumMap removedInumMap = getMarkInumMapEntryAsRemoved(currentInumMap, + getLdapEntryManager().encodeTime(removedPerson.getDn(), runDate)); + try { + inumDbPersistenceEntryManager.merge(removedInumMap); + result2.add(removedInumMap.getInum()); + } catch (BasePersistenceException ex) { + log.error("Failed to update entry with inum '{}' and DN: {}"+ currentInumMap.getInum(), + currentInumMap.getDn(), ex); + continue; + } + } + + // Remove person from target server + try { + //ldap ORM + if(targetPersistenceEntryManager.hasBranchesSupport(removedPerson.getDn())){ + targetPersistenceEntryManager.removeRecursively(removedPerson.getDn(), GluuCustomPerson.class); + + }else { + //other ORM + targetPersistenceEntryManager.remove(removedPerson.getDn(), GluuCustomPerson.class); + + Filter pairwiseIdentifiersFilter = Filter.createEqualityFilter(JansConstants.oxAuthUserId, removedPerson.getDn()); + targetPersistenceEntryManager.remove(pairwiseIdService.getDnForPairWiseIdentifier(null, removedPerson.getDn()), GluuUserPairwiseIdentifier.class, pairwiseIdentifiersFilter,0); + + Filter equalityFilter = Filter.createEqualityFilter("personInum", removedPerson.getDn()); + targetPersistenceEntryManager.remove(fidoDeviceService.getDnForFidoDevice(removedPerson.getDn(),null), GluuCustomFidoDevice.class, equalityFilter,0); + + Filter equalityFido2DeviceFilter = Filter.createEqualityFilter("personInum", removedPerson.getDn()); + targetPersistenceEntryManager.remove(fido2DeviceService.getDnForFido2Device(null, removedPerson.getDn()), GluuFido2Device.class, equalityFido2DeviceFilter,0); + } + result1.add(inum); + } catch (BasePersistenceException ex) { + log.error("Failed to remove person entry with inum '{}' and DN: {}"+ inum, removedPerson.getDn(), ex); + continue; + } + + log.info("Person with DN: '{}' removed from target server"+ removedPerson.getDn()); + } + + return new Pair, List>(result1, result2); + } + + private JansInumMap getMarkInumMapEntryAsRemoved(JansInumMap currentInumMap, String date) { + JansInumMap clonedInumMap; + try { + clonedInumMap = (JansInumMap) BeanUtilsBean2.getInstance().cloneBean(currentInumMap); + } catch (Exception ex) { + log.error("Failed to prepare GluuInumMap for removal"+ ex); + return null; + } + + String suffix = "-" + date; + + String[] primaryKeyValues = ArrayHelper.arrayClone(clonedInumMap.getPrimaryKeyValues()); + String[] secondaryKeyValues = ArrayHelper.arrayClone(clonedInumMap.getSecondaryKeyValues()); + String[] tertiaryKeyValues = ArrayHelper.arrayClone(clonedInumMap.getTertiaryKeyValues()); + + if (ArrayHelper.isNotEmpty(primaryKeyValues)) { + markInumMapEntryKeyValuesAsRemoved(primaryKeyValues, suffix); + } + + if (ArrayHelper.isNotEmpty(secondaryKeyValues)) { + markInumMapEntryKeyValuesAsRemoved(secondaryKeyValues, suffix); + } + + if (ArrayHelper.isNotEmpty(tertiaryKeyValues)) { + markInumMapEntryKeyValuesAsRemoved(tertiaryKeyValues, suffix); + } + + clonedInumMap.setPrimaryKeyValues(primaryKeyValues); + clonedInumMap.setSecondaryKeyValues(secondaryKeyValues); + clonedInumMap.setTertiaryKeyValues(tertiaryKeyValues); + + clonedInumMap.setStatus(GluuStatus.INACTIVE); + + return clonedInumMap; + } + + private void markInumMapEntryKeyValuesAsRemoved(String[] keyValues, String suffix) { + for (int i = 0; i < keyValues.length; i++) { + keyValues[i] = keyValues[i] + suffix; + } + } + + private HashMap addNewInumServerEntries( + CacheRefreshConfiguration cacheRefreshConfiguration, LdapServerConnection inumDbServerConnection, + Map sourcePersonCacheCompoundKeyMap, + HashMap primaryKeyAttrValueInumMap) { + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + String inumbaseDn = inumDbServerConnection.getBaseDns()[0]; + + HashMap result = new HashMap(); + + String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); + for (Entry sourcePersonCacheCompoundKeyEntry : sourcePersonCacheCompoundKeyMap + .entrySet()) { + CacheCompoundKey cacheCompoundKey = sourcePersonCacheCompoundKeyEntry.getKey(); + GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyEntry.getValue(); + + if (log.isTraceEnabled()) { + log.trace("Checking source entry with key: '{}', and DN: {}"+ cacheCompoundKey, sourcePerson.getDn()); + } + + JansInumMap currentInumMap = primaryKeyAttrValueInumMap.get(cacheCompoundKey); + if (currentInumMap == null) { + String[][] keyAttributesValues = getKeyAttributesValues(keyAttributesWithoutValues, sourcePerson); + currentInumMap = addGluuInumMap(inumbaseDn, inumDbPersistenceEntryManager, keyAttributesWithoutValues, + keyAttributesValues); + result.put(cacheCompoundKey, currentInumMap); + log.info("Added new inum entry for DN: {}"+ sourcePerson.getDn()); + } else { + log.trace("Inum entry for DN: '{}' exist"+ sourcePerson.getDn()); + } + } + + return result; + } + + private Map getSourcePersonCompoundKeyMap( + CacheRefreshConfiguration cacheRefreshConfiguration, List sourcePersons) { + Map result = new HashMap(); + Set duplicateKeys = new HashSet(); + + String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); + for (GluuSimplePerson sourcePerson : sourcePersons) { + String[][] keyAttributesValues = getKeyAttributesValues(keyAttributesWithoutValues, sourcePerson); + CacheCompoundKey cacheCompoundKey = new CacheCompoundKey(keyAttributesValues); + + if (result.containsKey(cacheCompoundKey)) { + duplicateKeys.add(cacheCompoundKey); + } + + result.put(cacheCompoundKey, sourcePerson); + } + + for (CacheCompoundKey duplicateKey : duplicateKeys) { + log.error("Non-deterministic primary key. Skipping user with key: {}"+ duplicateKey); + result.remove(duplicateKey); + } + + return result; + } + + private LdapServerConnection[] prepareLdapServerConnections(CacheRefreshConfiguration cacheRefreshConfiguration, + List ldapConfigurations) { + if (null == ldapConfigurations) { + return null; + } + + LdapServerConnection[] ldapServerConnections = new LdapServerConnection[ldapConfigurations.size()]; + for (int i = 0; i < ldapConfigurations.size(); i++) { + ldapServerConnections[i] = prepareLdapServerConnection(cacheRefreshConfiguration, + ldapConfigurations.get(i)); + if (ldapServerConnections[i] == null) { + return null; + } + } + + return ldapServerConnections; + } + + private String[][] getKeyAttributesValues(String[] attrs, GluuSimplePerson person) { + String[][] result = new String[attrs.length][]; + for (int i = 0; i < attrs.length; i++) { + result[i] = person.getStringAttributes(attrs[i]); + } + + return result; + } + + private void updateStatus(AppConfiguration currentConfiguration, long lastRun) { + Date currentDateTime = new Date(); + currentConfiguration.setLastUpdate(currentDateTime); + currentConfiguration.setLastUpdateCount(currentConfiguration.getLastUpdateCount()); + currentConfiguration.setProblemCount(currentConfiguration.getProblemCount()); + CacheRefrshConfigurationService.updateConfiguration(currentConfiguration); + } + + public ConfigurationFactory getConfigurationFactory() { + return configurationFactory; + } + + public void setConfigurationFactory(ConfigurationFactory configurationFactory) { + this.configurationFactory = configurationFactory; + } + + public ArrayList applyChangesToInumMap(HashMap inumInumMap, + HashMap addedPrimaryKeyAttrValueInumMap, List removedGluuInumMaps) { + log.info("There are '{}' entries before updating inum list"+ inumInumMap.size()); + for (String removedGluuInumMap : removedGluuInumMaps) { + inumInumMap.remove(removedGluuInumMap); + } + log.info("There are '{}' entries after removal '{}' entries" + inumInumMap.size() +" : " +removedGluuInumMaps.size()); + + ArrayList currentInumMaps = new ArrayList(inumInumMap.values()); + currentInumMaps.addAll(addedPrimaryKeyAttrValueInumMap.values()); + log.info("There are '{}' entries after adding '{}' entries"+ currentInumMaps.size()+" : " + + addedPrimaryKeyAttrValueInumMap.size()); + + return currentInumMaps; + } + + public List updateTargetEntriesViaCopy(CacheRefreshConfiguration cacheRefreshConfiguration, + Map sourcePersonCacheCompoundKeyMap, + HashMap primaryKeyAttrValueInumMap, Set changedInums) { + HashMap inumCacheCompoundKeyMap = getInumCacheCompoundKeyMap( + primaryKeyAttrValueInumMap); + Map targetServerAttributesMapping = getTargetServerAttributesMapping(cacheRefreshConfiguration); + String[] customObjectClasses = appConfiguration.getPersonObjectClassTypes(); + + List result = new ArrayList(); + + if (!validateTargetServerSchema(cacheRefreshConfiguration, targetServerAttributesMapping, + customObjectClasses)) { + return result; + } + + for (String targetInum : changedInums) { + CacheCompoundKey compoundKey = inumCacheCompoundKeyMap.get(targetInum); + if (compoundKey == null) { + continue; + } + + GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyMap.get(compoundKey); + if (sourcePerson == null) { + continue; + } + + if (updateTargetEntryViaCopy(sourcePerson, targetInum, customObjectClasses, + targetServerAttributesMapping)) { + result.add(targetInum); + } + } + + return result; + } + + public HashMap getInumCacheCompoundKeyMap( + HashMap primaryKeyAttrValueInumMap) { + HashMap result = new HashMap(); + + for (Map.Entry primaryKeyAttrValueInumMapEntry : primaryKeyAttrValueInumMap + .entrySet()) { + result.put(primaryKeyAttrValueInumMapEntry.getValue().getInum(), primaryKeyAttrValueInumMapEntry.getKey()); + } + + return result; + } + + public List loadInumServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration, + LdapServerConnection inumDbServerConnection) { + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + String inumbaseDn = inumDbServerConnection.getBaseDns()[0]; + + Filter filterObjectClass = Filter.createEqualityFilter(OxConstants.OBJECT_CLASS, + JansConstants.objectClassInumMap); + Filter filterStatus = Filter.createNOTFilter( + Filter.createEqualityFilter(JansConstants.jansStatus, GluuStatus.INACTIVE.getValue())); + Filter filter = Filter.createANDFilter(filterObjectClass, filterStatus); + + return inumDbPersistenceEntryManager.findEntries(inumbaseDn, JansInumMap.class, filter, SearchScope.SUB, null, + null, 0, 0, cacheRefreshConfiguration.getLdapSearchSizeLimit()); + } + + public HashMap getAllInumServerEntries( + HashMap primaryKeyAttrValueInumMap, + HashMap addedPrimaryKeyAttrValueInumMap) { + HashMap result = new HashMap(); + + result.putAll(primaryKeyAttrValueInumMap); + result.putAll(addedPrimaryKeyAttrValueInumMap); + + return result; + } + + public HashMap getSourcePersonsHashCodesMap(LdapServerConnection inumDbServerConnection, + Map sourcePersonCacheCompoundKeyMap, + HashMap primaryKeyAttrValueInumMap) { + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + + HashMap result = new HashMap(); + + for (Map.Entry sourcePersonCacheCompoundKeyEntry : sourcePersonCacheCompoundKeyMap + .entrySet()) { + CacheCompoundKey cacheCompoundKey = sourcePersonCacheCompoundKeyEntry.getKey(); + GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyEntry.getValue(); + + JansInumMap currentInumMap = primaryKeyAttrValueInumMap.get(cacheCompoundKey); + + result.put(currentInumMap.getInum(), inumDbPersistenceEntryManager.getHashCode(sourcePerson)); + } + + return result; + } + + public HashMap getPrimaryKeyAttrValueInumMap(List inumMaps) { + HashMap result = new HashMap(); + + for (JansInumMap inumMap : inumMaps) { + result.put(new CacheCompoundKey(inumMap.getPrimaryKeyValues(), inumMap.getSecondaryKeyValues(), + inumMap.getTertiaryKeyValues()), inumMap); + } + + return result; + } + + public HashMap getInumInumMap(List inumMaps) { + HashMap result = new HashMap(); + + for (JansInumMap inumMap : inumMaps) { + result.put(inumMap.getInum(), inumMap); + } + + return result; + } + + + +} diff --git a/jans-link/server/src/main/resources/META-INF/beans.xml b/jans-link/server/src/main/resources/META-INF/beans.xml index ddc64918ba0..b7930c568e8 100644 --- a/jans-link/server/src/main/resources/META-INF/beans.xml +++ b/jans-link/server/src/main/resources/META-INF/beans.xml @@ -2,5 +2,5 @@ + bean-discovery-mode="annotated" version="3.0"> diff --git a/jans-link/server/src/test/java/io/jans/link/test/LdapSample.java b/jans-link/server/src/test/java/io/jans/link/test/LdapSample.java index 534f7c56e0c..ea165cad31c 100644 --- a/jans-link/server/src/test/java/io/jans/link/test/LdapSample.java +++ b/jans-link/server/src/test/java/io/jans/link/test/LdapSample.java @@ -7,7 +7,7 @@ package io.jans.link.test; import io.jans.link.service.config.ConfigurationFactory; -import io.jans.link.timer.CacheRefreshTimer; +import io.jans.link.timer.JansLinkTimer; import io.jans.orm.PersistenceEntryManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,7 +32,7 @@ public static void main(String[] args) { //gluucacherefresh //GluuConfiguration GluuConfiguration = new GluuConfiguration(); - CacheRefreshTimer cacheRefreshTimer = new CacheRefreshTimer(); + JansLinkTimer cacheRefreshTimer = new JansLinkTimer(); cacheRefreshTimer.setLdapEntryManager(entryManager); ConfigurationFactory configurationFactory = new ConfigurationFactory(); //configurationFactory.setPersistenceEntryManagerInstance(entryManager); diff --git a/jans-link/service/src/main/java/io/jans/link/service/AttributeService.java b/jans-link/service/src/main/java/io/jans/link/service/AttributeService.java index 0fb8940ca1d..e83bd634031 100644 --- a/jans-link/service/src/main/java/io/jans/link/service/AttributeService.java +++ b/jans-link/service/src/main/java/io/jans/link/service/AttributeService.java @@ -29,16 +29,12 @@ * * @author Yuriy Movchan Date: 10.13.2010 */ -@ApplicationScoped -public class AttributeService extends io.jans.service.AttributeService { +public abstract class AttributeService extends io.jans.service.AttributeService { private GluuUserRole[] attributeEditTypes = new GluuUserRole[] { GluuUserRole.ADMIN, GluuUserRole.USER }; private static final long serialVersionUID = 8223624816948822765L; - @Inject - private AppConfiguration appConfiguration; - @Inject private OrganizationService organizationService; @@ -73,10 +69,10 @@ public List getAllPersonAttributes(GluuUserRole gluuUserRole) { private List getAllPersonAtributesImpl(GluuUserRole gluuUserRole, Collection attributes) { List attributeList = new ArrayList(); - String[] objectClassTypes = appConfiguration.getPersonObjectClassTypes(); + String[] objectClassTypes = getPersonObjectClassTypes(); log.debug("objectClassTypes={}", Arrays.toString(objectClassTypes)); for (JansAttribute attribute : attributes) { - if (StringHelper.equalsIgnoreCase(attribute.getOrigin(), appConfiguration.getPersonCustomObjectClass()) + if (StringHelper.equalsIgnoreCase(attribute.getOrigin(), getPersonCustomObjectClass()) && (GluuUserRole.ADMIN == gluuUserRole)) { attribute.setCustom(true); attributeList.add(attribute); @@ -113,10 +109,10 @@ public boolean attributeWithSameNameDontExist(String name) { private List getAllPersonAtributes(GluuUserRole gluuUserRole, Collection attributes) { List attributeList = new ArrayList(); - String[] objectClassTypes = appConfiguration.getPersonObjectClassTypes(); + String[] objectClassTypes = getPersonObjectClassTypes(); log.debug("objectClassTypes={}", Arrays.toString(objectClassTypes)); for (JansAttribute attribute : attributes) { - if (StringHelper.equalsIgnoreCase(attribute.getOrigin(), appConfiguration.getPersonCustomObjectClass()) + if (StringHelper.equalsIgnoreCase(attribute.getOrigin(), getPersonCustomObjectClass()) && (GluuUserRole.ADMIN == gluuUserRole)) { attribute.setCustom(true); attributeList.add(attribute); @@ -158,9 +154,9 @@ public List getAllContactAttributes(GluuUserRole gluuUserRole) { private List getAllContactAtributesImpl(GluuUserRole gluuUserRole, Collection attributes) { List returnAttributeList = new ArrayList(); - String[] objectClassTypes = appConfiguration.getContactObjectClassTypes(); + String[] objectClassTypes = getPersonObjectClassTypes(); for (JansAttribute attribute : attributes) { - if (StringHelper.equalsIgnoreCase(attribute.getOrigin(), appConfiguration.getPersonCustomObjectClass()) + if (StringHelper.equalsIgnoreCase(attribute.getOrigin(), getPersonCustomObjectClass()) && (GluuUserRole.ADMIN == gluuUserRole)) { attribute.setCustom(true); returnAttributeList.add(attribute); @@ -345,9 +341,13 @@ public String getDnForAttribute(String inum) { * @return Current custom origin */ public String getCustomOrigin() { - return appConfiguration.getPersonCustomObjectClass(); + return getPersonCustomObjectClass(); } + public abstract String getPersonCustomObjectClass(); + + public abstract String[] getPersonObjectClassTypes(); + @Override protected List getAllAtributesImpl(String baseDn) { List attributeList = persistenceEntryManager.findEntries(baseDn, JansAttribute.class, null); @@ -455,11 +455,11 @@ private List getAllActiveAtributesImpl(GluuUserRole gluuUserRole) List attributeList = persistenceEntryManager.findEntries(getDnForAttribute(null), JansAttribute.class, filter); String customOrigin = getCustomOrigin(); - String[] objectClassTypes = appConfiguration.getPersonObjectClassTypes(); + String[] objectClassTypes = getPersonObjectClassTypes(); log.debug("objectClassTypes={}", Arrays.toString(objectClassTypes)); List returnAttributeList = new ArrayList(); for (JansAttribute attribute : attributeList) { - if (StringHelper.equalsIgnoreCase(attribute.getOrigin(), appConfiguration.getPersonCustomObjectClass()) + if (StringHelper.equalsIgnoreCase(attribute.getOrigin(), getPersonCustomObjectClass()) && (GluuUserRole.ADMIN == gluuUserRole)) { attribute.setCustom(true); returnAttributeList.add(attribute); @@ -503,7 +503,7 @@ public List searchAttributes(String pattern, int sizeLimit) throw } public List searchPersonAttributes(String pattern, int sizeLimit) throws Exception { - String[] objectClassTypes = appConfiguration.getPersonObjectClassTypes(); + String[] objectClassTypes = getPersonObjectClassTypes(); String[] targetArray = new String[] { pattern }; List originFilters = new ArrayList(); Filter displayNameFilter = Filter.createSubstringFilter(JansConstants.displayName, null, targetArray, null); diff --git a/jans-link/service/src/main/java/io/jans/link/service/BaseJansLinkTimer.java b/jans-link/service/src/main/java/io/jans/link/service/BaseJansLinkTimer.java new file mode 100644 index 00000000000..1152c70fa98 --- /dev/null +++ b/jans-link/service/src/main/java/io/jans/link/service/BaseJansLinkTimer.java @@ -0,0 +1,715 @@ +package io.jans.link.service; + +import io.jans.link.constants.JansConstants; +import io.jans.link.external.ExternalCacheRefreshService; +import io.jans.link.model.CacheCompoundKey; +import io.jans.link.model.GluuCustomPerson; +import io.jans.link.model.GluuSimplePerson; +import io.jans.link.model.JansInumMap; +import io.jans.link.model.config.AppConfiguration; +import io.jans.link.model.config.shared.CacheRefreshConfiguration; +import io.jans.link.model.config.shared.CacheRefreshAttributeMapping; +import io.jans.link.util.PropertyUtil; +import io.jans.model.GluuStatus; +import io.jans.model.JansCustomAttribute; +import io.jans.model.SchemaEntry; +import io.jans.model.ldap.GluuLdapConfiguration; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.PersistenceEntryManagerFactory; +import io.jans.orm.annotation.ObjectClass; +import io.jans.orm.exception.BasePersistenceException; +import io.jans.orm.exception.EntryPersistenceException; +import io.jans.orm.exception.operation.SearchException; +import io.jans.orm.ldap.operation.LdapOperationService; +import io.jans.orm.model.SearchScope; +import io.jans.orm.model.base.DummyEntry; +import io.jans.orm.operation.PersistenceOperationService; +import io.jans.orm.search.filter.Filter; +import io.jans.orm.util.ArrayHelper; +import io.jans.orm.util.StringHelper; +import io.jans.service.ObjectSerializationService; +import io.jans.service.SchemaService; +import io.jans.util.OxConstants; +import jakarta.inject.Inject; +import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + + +public abstract class BaseJansLinkTimer { + + private static final String LETTERS_FOR_SEARCH = "abcdefghijklmnopqrstuvwxyz1234567890."; + private static final String[] TARGET_PERSON_RETURN_ATTRIBUTES = { JansConstants.inum }; + @Inject + private Logger log; + @Inject + private CacheRefreshService cacheRefreshService; + @Inject + private PersonService personService; + @Inject + private PersistenceEntryManager ldapEntryManager; + @Inject + private CacheRefreshSnapshotFileService cacheRefreshSnapshotFileService; + @Inject + private ExternalCacheRefreshService externalCacheRefreshService; + @Inject + private SchemaService schemaService; + @Inject + private InumService inumService; + /*@Inject + private AppConfiguration appConfiguration;*/ + + @Inject + private EncryptionService encryptionService; + + @Inject + private PairwiseIdService pairwiseIdService; + + @Inject + private FidoDeviceService fidoDeviceService; + + @Inject + private Fido2DeviceService fido2DeviceService; + @Inject + private ObjectSerializationService objectSerializationService; + private AtomicBoolean isActive; + private long lastFinishedTime; + + + + /*public ArrayList applyChangesToInumMap(HashMap inumInumMap, + HashMap addedPrimaryKeyAttrValueInumMap, List removedGluuInumMaps) { + log.info("There are '{}' entries before updating inum list"+ inumInumMap.size()); + for (String removedGluuInumMap : removedGluuInumMaps) { + inumInumMap.remove(removedGluuInumMap); + } + log.info("There are '{}' entries after removal '{}' entries" + inumInumMap.size() +" : " +removedGluuInumMaps.size()); + + ArrayList currentInumMaps = new ArrayList(inumInumMap.values()); + currentInumMaps.addAll(addedPrimaryKeyAttrValueInumMap.values()); + log.info("There are '{}' entries after adding '{}' entries"+ currentInumMaps.size()+" : " + + addedPrimaryKeyAttrValueInumMap.size()); + + return currentInumMaps; + }*/ + + public Set getChangedInums(HashMap currInumWithEntryHashCodeMap, + Map prevInumWithEntryHashCodeMap, boolean includeDeleted) { + // Find chaged inums + Set changedInums = null; + // First time run + if (prevInumWithEntryHashCodeMap == null) { + changedInums = new HashSet(currInumWithEntryHashCodeMap.keySet()); + } else { + changedInums = new HashSet(); + + // Add all inums which not exist in new snapshot + if (includeDeleted) { + for (String prevInumKey : prevInumWithEntryHashCodeMap.keySet()) { + if (!currInumWithEntryHashCodeMap.containsKey(prevInumKey)) { + changedInums.add(prevInumKey); + } + } + } + + // Add all new inums and changed inums + for (Map.Entry currEntry : currInumWithEntryHashCodeMap.entrySet()) { + String currInumKey = currEntry.getKey(); + Integer prevHashCode = prevInumWithEntryHashCodeMap.get(currInumKey); + if ((prevHashCode == null) + || ((prevHashCode != null) && !(prevHashCode.equals(currEntry.getValue())))) { + changedInums.add(currInumKey); + } + } + } + return changedInums; + } + + public List getRemovedPersons(HashMap currInumWithEntryHashCodeMap, + Map prevInumWithEntryHashCodeMap) { + // First time run + if (prevInumWithEntryHashCodeMap == null) { + return new ArrayList(0); + } + + // Add all inums which not exist in new snapshot + Set deletedInums = new HashSet(); + for (String prevInumKey : prevInumWithEntryHashCodeMap.keySet()) { + if (!currInumWithEntryHashCodeMap.containsKey(prevInumKey)) { + deletedInums.add(prevInumKey); + } + } + + List deletedPersons = new ArrayList(deletedInums.size()); + for (String deletedInum : deletedInums) { + GluuSimplePerson person = new GluuSimplePerson(); + String personDn = personService.getDnForPerson(deletedInum); + person.setDn(personDn); + + List customAttributes = new ArrayList(); + customAttributes.add(new JansCustomAttribute(JansConstants.inum, deletedInum)); + person.setCustomAttributes(customAttributes); + + deletedPersons.add(person); + } + + return deletedPersons; + } + + public List updateTargetEntriesViaVDS(CacheRefreshConfiguration cacheRefreshConfiguration, + LdapServerConnection targetServerConnection, Set changedInums) { + List result = new ArrayList(); + + PersistenceEntryManager targetPersistenceEntryManager = targetServerConnection.getPersistenceEntryManager(); + Filter filter = cacheRefreshService.createObjectClassPresenceFilter(); + for (String changedInum : changedInums) { + String baseDn = "action=synchronizecache," + personService.getDnForPerson(changedInum); + try { + targetPersistenceEntryManager.findEntries(baseDn, DummyEntry.class, filter, SearchScope.SUB, null, + null, 0, 0, cacheRefreshConfiguration.getLdapSearchSizeLimit()); + result.add(changedInum); + log.info("Updated entry with inum {}"+ changedInum); + } catch (BasePersistenceException ex) { + log.error("Failed to update entry with inum '{}' using baseDN {}"+ changedInum, baseDn, ex); + } + } + + return result; + } + + /*public List updateTargetEntriesViaCopy(CacheRefreshConfiguration cacheRefreshConfiguration, + Map sourcePersonCacheCompoundKeyMap, + HashMap primaryKeyAttrValueInumMap, Set changedInums) { + HashMap inumCacheCompoundKeyMap = getInumCacheCompoundKeyMap( + primaryKeyAttrValueInumMap); + Map targetServerAttributesMapping = getTargetServerAttributesMapping(cacheRefreshConfiguration); + String[] customObjectClasses = appConfiguration.getPersonObjectClassTypes(); + + List result = new ArrayList(); + + if (!validateTargetServerSchema(cacheRefreshConfiguration, targetServerAttributesMapping, + customObjectClasses)) { + return result; + } + + for (String targetInum : changedInums) { + CacheCompoundKey compoundKey = inumCacheCompoundKeyMap.get(targetInum); + if (compoundKey == null) { + continue; + } + + GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyMap.get(compoundKey); + if (sourcePerson == null) { + continue; + } + + if (updateTargetEntryViaCopy(sourcePerson, targetInum, customObjectClasses, + targetServerAttributesMapping)) { + result.add(targetInum); + } + } + + return result; + }*/ + + public boolean validateTargetServerSchema(CacheRefreshConfiguration cacheRefreshConfiguration, + Map targetServerAttributesMapping, String[] customObjectClasses) { + // Get list of return attributes + String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); + String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration); + String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes); + + GluuSimplePerson sourcePerson = new GluuSimplePerson(); + for (String returnAttribute : returnAttributes) { + sourcePerson.setAttribute(returnAttribute, "Test"); + } + + String targetInum = inumService.generateInums(JansConstants.INUM_TYPE_PEOPLE_SLUG, false); + String targetPersonDn = personService.getDnForPerson(targetInum); + + GluuCustomPerson targetPerson = new GluuCustomPerson(); + targetPerson.setDn(targetPersonDn); + targetPerson.setInum(targetInum); + targetPerson.setStatus(GluuStatus.ACTIVE.getValue()); + targetPerson.setCustomObjectClasses(customObjectClasses); + + // Update list of return attributes according mapping + cacheRefreshService.setTargetEntryAttributes(sourcePerson, targetServerAttributesMapping, targetPerson); + + // Execute interceptor script + externalCacheRefreshService.executeExternalUpdateUserMethods(targetPerson); + boolean executionResult = externalCacheRefreshService.executeExternalUpdateUserMethods(targetPerson); + if (!executionResult) { + log.error("Failed to execute Cache Refresh scripts for person '{}'"+ targetInum); + return false; + } + + // Validate target server attributes + List customAttributes = targetPerson.getCustomAttributes(); + + List targetAttributes = new ArrayList(customAttributes.size()); + for (JansCustomAttribute customAttribute : customAttributes) { + targetAttributes.add(customAttribute.getName()); + } + + List targetObjectClasses = Arrays + .asList(getLdapEntryManager().getObjectClasses(targetPerson, GluuCustomPerson.class)); + + return validateTargetServerSchema(targetObjectClasses, targetAttributes); + } + + private boolean validateTargetServerSchema(List targetObjectClasses, List targetAttributes) { + SchemaEntry schemaEntry = schemaService.getSchema(); + if (schemaEntry == null) { + // Destination server not requires schema validation + return true; + } + + Set objectClassesAttributesSet = schemaService.getObjectClassesAttributes(schemaEntry, + targetObjectClasses.toArray(new String[0])); + + Set targetAttributesSet = new LinkedHashSet(); + for (String attrbute : targetAttributes) { + targetAttributesSet.add(StringHelper.toLowerCase(attrbute)); + } + + targetAttributesSet.removeAll(objectClassesAttributesSet); + + if (targetAttributesSet.size() == 0) { + return true; + } + + log.error("Skipping target entries update. Destination server schema doesn't has next attributes: '{}', target OC: '{}', target OC attributes: '{}'"+ + targetAttributesSet, targetObjectClasses.toArray(new String[0]), objectClassesAttributesSet); + + return false; + } + + public boolean updateTargetEntryViaCopy(GluuSimplePerson sourcePerson, String targetInum, + String[] targetCustomObjectClasses, Map targetServerAttributesMapping) { + String targetPersonDn = personService.getDnForPerson(targetInum); + GluuCustomPerson targetPerson = null; + boolean updatePerson; + if (personService.contains(targetPersonDn)) { + try { + targetPerson = personService.findPersonByDn(targetPersonDn); + log.info("Found person by inum '{}'"+ targetInum); + } catch (EntryPersistenceException ex) { + log.error("Failed to find person '{}'"+ targetInum, ex); + return false; + } + updatePerson = true; + } else { + targetPerson = new GluuCustomPerson(); + targetPerson.setDn(targetPersonDn); + targetPerson.setInum(targetInum); + targetPerson.setStatus(GluuStatus.ACTIVE.getValue()); + updatePerson = false; + } + + if (PersistenceEntryManager.PERSITENCE_TYPES.ldap.name().equals(getLdapEntryManager().getPersistenceType())) { + targetPerson.setCustomObjectClasses(targetCustomObjectClasses); + } + + //#################################### + targetPerson.setSourceServerName(sourcePerson.getSourceServerName()); //add keycloak + //#################################### + targetPerson.setSourceServerUserDn(sourcePerson.getDn()); + + cacheRefreshService.setTargetEntryAttributes(sourcePerson, targetServerAttributesMapping, targetPerson); + + // Execute interceptor script + boolean executionResult = externalCacheRefreshService.executeExternalUpdateUserMethods(targetPerson); + if (!executionResult) { + log.error("Failed to execute Cache Refresh scripts for person '{}'"+ targetInum); + return false; + } + + try { + if (updatePerson) { + personService.updatePersonWithoutCheck(targetPerson); + log.info("Updated person '{}'"+ targetInum); + } else { + personService.addPersonWithoutCheck(targetPerson); + log.info("Added new person '{}'"+ targetInum); + } + } catch (Exception ex) { + ex.printStackTrace(); + String test = updatePerson ? "update" : "add"; + log.error("Failed to '{}' person '{}'" + test + targetInum + ex); + return false; + } + + return true; + } + + /*public HashMap getInumCacheCompoundKeyMap( + HashMap primaryKeyAttrValueInumMap) { + HashMap result = new HashMap(); + + for (Map.Entry primaryKeyAttrValueInumMapEntry : primaryKeyAttrValueInumMap + .entrySet()) { + result.put(primaryKeyAttrValueInumMapEntry.getValue().getInum(), primaryKeyAttrValueInumMapEntry.getKey()); + } + + return result; + }*/ + + /*public List loadInumServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration, + LdapServerConnection inumDbServerConnection) { + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + String inumbaseDn = inumDbServerConnection.getBaseDns()[0]; + + Filter filterObjectClass = Filter.createEqualityFilter(OxConstants.OBJECT_CLASS, + JansConstants.objectClassInumMap); + Filter filterStatus = Filter.createNOTFilter( + Filter.createEqualityFilter(JansConstants.jansStatus, GluuStatus.INACTIVE.getValue())); + Filter filter = Filter.createANDFilter(filterObjectClass, filterStatus); + + return inumDbPersistenceEntryManager.findEntries(inumbaseDn, JansInumMap.class, filter, SearchScope.SUB, null, + null, 0, 0, cacheRefreshConfiguration.getLdapSearchSizeLimit()); + }*/ + + public List loadSourceServerEntriesWithoutLimits( + CacheRefreshConfiguration cacheRefreshConfiguration, LdapServerConnection[] sourceServerConnections) + throws SearchException { + Filter customFilter = cacheRefreshService.createFilter(cacheRefreshConfiguration.getCustomLdapFilter()); + String[] keyAttributes = getCompoundKeyAttributes(cacheRefreshConfiguration); + String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); + String[] keyObjectClasses = getCompoundKeyObjectClasses(cacheRefreshConfiguration); + String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration); + + String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes); + + Set addedDns = new HashSet(); + + List sourcePersons = new ArrayList(); + for (LdapServerConnection sourceServerConnection : sourceServerConnections) { + String sourceServerName = sourceServerConnection.getSourceServerName(); + + PersistenceEntryManager sourcePersistenceEntryManager = sourceServerConnection.getPersistenceEntryManager(); + String[] baseDns = sourceServerConnection.getBaseDns(); + Filter filter = cacheRefreshService.createFilter(keyAttributes, keyObjectClasses, "", customFilter); + if (log.isTraceEnabled()) { + log.trace("Using next filter to load entris from source server: {}"+ filter); + } + + for (String baseDn : baseDns) { + List currentSourcePersons = sourcePersistenceEntryManager.findEntries(baseDn, + GluuSimplePerson.class, filter, SearchScope.SUB, returnAttributes, null, 0, 0, + cacheRefreshConfiguration.getLdapSearchSizeLimit()); + + // Add to result and ignore root entry if needed + for (GluuSimplePerson currentSourcePerson : currentSourcePersons) { + currentSourcePerson.setSourceServerName(sourceServerName); + // if (!StringHelper.equalsIgnoreCase(baseDn, + // currentSourcePerson.getDn())) { + String currentSourcePersonDn = currentSourcePerson.getDn().toLowerCase(); + if (!addedDns.contains(currentSourcePersonDn)) { + sourcePersons.add(currentSourcePerson); + addedDns.add(currentSourcePersonDn); + } + // } + } + } + } + + return sourcePersons; + } + + public List loadSourceServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration, + LdapServerConnection[] sourceServerConnections) throws SearchException { + Filter customFilter = cacheRefreshService.createFilter(cacheRefreshConfiguration.getCustomLdapFilter()); + String[] keyAttributes = getCompoundKeyAttributes(cacheRefreshConfiguration); + String[] keyAttributesWithoutValues = getCompoundKeyAttributesWithoutValues(cacheRefreshConfiguration); + String[] keyObjectClasses = getCompoundKeyObjectClasses(cacheRefreshConfiguration); + String[] sourceAttributes = getSourceAttributes(cacheRefreshConfiguration); + + String[] twoLettersArray = createTwoLettersArray(); + String[] returnAttributes = ArrayHelper.arrayMerge(keyAttributesWithoutValues, sourceAttributes); + + Set addedDns = new HashSet(); + + List sourcePersons = new ArrayList(); + for (LdapServerConnection sourceServerConnection : sourceServerConnections) { + String sourceServerName = sourceServerConnection.getSourceServerName(); + + PersistenceEntryManager sourcePersistenceEntryManager = sourceServerConnection.getPersistenceEntryManager(); + String[] baseDns = sourceServerConnection.getBaseDns(); + for (String keyAttributeStart : twoLettersArray) { + Filter filter = cacheRefreshService.createFilter(keyAttributes, keyObjectClasses, keyAttributeStart, + customFilter); + if (log.isDebugEnabled()) { + log.trace("Using next filter to load entris from source server: {}"+ filter); + } + + for (String baseDn : baseDns) { + List currentSourcePersons = sourcePersistenceEntryManager.findEntries(baseDn, + GluuSimplePerson.class, filter, SearchScope.SUB, returnAttributes, null, 0, 0, + cacheRefreshConfiguration.getLdapSearchSizeLimit()); + + // Add to result and ignore root entry if needed + for (GluuSimplePerson currentSourcePerson : currentSourcePersons) { + currentSourcePerson.setSourceServerName(sourceServerName); + // if (!StringHelper.equalsIgnoreCase(baseDn, + // currentSourcePerson.getDn())) { + String currentSourcePersonDn = currentSourcePerson.getDn().toLowerCase(); + if (!addedDns.contains(currentSourcePersonDn)) { + sourcePersons.add(currentSourcePerson); + addedDns.add(currentSourcePersonDn); + } + // } + } + } + } + } + + return sourcePersons; + } + + public List loadTargetServerEntries(CacheRefreshConfiguration cacheRefreshConfiguration, + PersistenceEntryManager targetPersistenceEntryManager) { + Filter filter = Filter.createEqualityFilter(OxConstants.OBJECT_CLASS, JansConstants.objectClassPerson); + + return targetPersistenceEntryManager.findEntries(personService.getDnForPerson(null), TypedGluuSimplePerson.class, + filter, SearchScope.SUB, TARGET_PERSON_RETURN_ATTRIBUTES, null, 0, 0, + cacheRefreshConfiguration.getLdapSearchSizeLimit()); + } + + public JansInumMap addGluuInumMap(String inumbBaseDn, PersistenceEntryManager inumDbPersistenceEntryManager, + String[] primaryKeyAttrName, String[][] primaryKeyValues) { + String inum = cacheRefreshService.generateInumForNewInumMap(inumbBaseDn, inumDbPersistenceEntryManager); + String inumDn = cacheRefreshService.getDnForInum(inumbBaseDn, inum); + + JansInumMap inumMap = new JansInumMap(); + inumMap.setDn(inumDn); + inumMap.setInum(inum); + inumMap.setPrimaryKeyAttrName(primaryKeyAttrName[0]); + inumMap.setPrimaryKeyValues(primaryKeyValues[0]); + if (primaryKeyAttrName.length > 1) { + inumMap.setSecondaryKeyAttrName(primaryKeyAttrName[1]); + inumMap.setSecondaryKeyValues(primaryKeyValues[1]); + } + if (primaryKeyAttrName.length > 2) { + inumMap.setTertiaryKeyAttrName(primaryKeyAttrName[2]); + inumMap.setTertiaryKeyValues(primaryKeyValues[2]); + } + inumMap.setStatus(GluuStatus.ACTIVE); + cacheRefreshService.addInumMap(inumDbPersistenceEntryManager, inumMap); + + return inumMap; + } + + /*public HashMap getAllInumServerEntries( + HashMap primaryKeyAttrValueInumMap, + HashMap addedPrimaryKeyAttrValueInumMap) { + HashMap result = new HashMap(); + + result.putAll(primaryKeyAttrValueInumMap); + result.putAll(addedPrimaryKeyAttrValueInumMap); + + return result; + }*/ + + /*public HashMap getSourcePersonsHashCodesMap(LdapServerConnection inumDbServerConnection, + Map sourcePersonCacheCompoundKeyMap, + HashMap primaryKeyAttrValueInumMap) { + PersistenceEntryManager inumDbPersistenceEntryManager = inumDbServerConnection.getPersistenceEntryManager(); + + HashMap result = new HashMap(); + + for (Map.Entry sourcePersonCacheCompoundKeyEntry : sourcePersonCacheCompoundKeyMap + .entrySet()) { + CacheCompoundKey cacheCompoundKey = sourcePersonCacheCompoundKeyEntry.getKey(); + GluuSimplePerson sourcePerson = sourcePersonCacheCompoundKeyEntry.getValue(); + + JansInumMap currentInumMap = primaryKeyAttrValueInumMap.get(cacheCompoundKey); + + result.put(currentInumMap.getInum(), inumDbPersistenceEntryManager.getHashCode(sourcePerson)); + } + + return result; + }*/ + + public List processTargetPersons(List targetPersons, + HashMap currInumWithEntryHashCodeMap) { + List result = new ArrayList(); + + for (GluuSimplePerson targetPerson : targetPersons) { + String personInum = targetPerson.getStringAttribute(JansConstants.inum); + if (!currInumWithEntryHashCodeMap.containsKey(personInum)) { + log.info("Person with such DN: '{}' isn't present on source server"+ targetPerson.getDn()); + result.add(targetPerson); + } + } + + return result; + } + + /*public HashMap getPrimaryKeyAttrValueInumMap(List inumMaps) { + HashMap result = new HashMap(); + + for (JansInumMap inumMap : inumMaps) { + result.put(new CacheCompoundKey(inumMap.getPrimaryKeyValues(), inumMap.getSecondaryKeyValues(), + inumMap.getTertiaryKeyValues()), inumMap); + } + + return result; + }*/ + + /*public HashMap getInumInumMap(List inumMaps) { + HashMap result = new HashMap(); + + for (JansInumMap inumMap : inumMaps) { + result.put(inumMap.getInum(), inumMap); + } + + return result; + }*/ + + public void closeLdapServerConnection(LdapServerConnection... ldapServerConnections) { + for (LdapServerConnection ldapServerConnection : ldapServerConnections) { + if ((ldapServerConnection != null) && (ldapServerConnection.getPersistenceEntryManager() != null)) { + ldapServerConnection.getPersistenceEntryManager().destroy(); + } + } + } + + private String[] createTwoLettersArray() { + char[] characters = LETTERS_FOR_SEARCH.toCharArray(); + int lettersCount = characters.length; + + String[] result = new String[lettersCount * lettersCount]; + for (int i = 0; i < lettersCount; i++) { + for (int j = 0; j < lettersCount; j++) { + result[i * lettersCount + j] = "" + characters[i] + characters[j]; + } + } + + return result; + } + + public String getInumCachePath(CacheRefreshConfiguration cacheRefreshConfiguration) { + return FilenameUtils.concat(cacheRefreshConfiguration.getSnapshotFolder(), "inum_cache.dat"); + } + + public PersistenceEntryManager getLdapEntryManager() { + return ldapEntryManager; + } + + public void setLdapEntryManager(PersistenceEntryManager ldapEntryManager) { + this.ldapEntryManager = ldapEntryManager; + } + + public class LdapServerConnection { + private String sourceServerName; + private PersistenceEntryManager ldapEntryManager; + private String[] baseDns; + + public LdapServerConnection(String sourceServerName, PersistenceEntryManager ldapEntryManager, + String[] baseDns) { + this.sourceServerName = sourceServerName; + this.ldapEntryManager = ldapEntryManager; + this.baseDns = baseDns; + } + + public final String getSourceServerName() { + return sourceServerName; + } + + public final PersistenceEntryManager getPersistenceEntryManager() { + return ldapEntryManager; + } + + public final String[] getBaseDns() { + return baseDns; + } + } + + public CacheRefreshUpdateMethod getUpdateMethod(CacheRefreshConfiguration cacheRefreshConfiguration) { + String updateMethod = cacheRefreshConfiguration.getUpdateMethod(); + if (StringHelper.isEmpty(updateMethod)) { + return CacheRefreshUpdateMethod.COPY; + } + + return CacheRefreshUpdateMethod.getByValue(cacheRefreshConfiguration.getUpdateMethod()); + } + + public String[] getSourceAttributes(CacheRefreshConfiguration cacheRefreshConfiguration) { + return cacheRefreshConfiguration.getSourceAttributes().toArray(new String[0]); + } + + public String[] getCompoundKeyObjectClasses(CacheRefreshConfiguration cacheRefreshConfiguration) { + return cacheRefreshConfiguration.getKeyObjectClasses().toArray(new String[0]); + } + + public String[] getCompoundKeyAttributes(CacheRefreshConfiguration cacheRefreshConfiguration) { + return cacheRefreshConfiguration.getKeyAttributes().toArray(new String[0]); + } + + public String[] getCompoundKeyAttributesWithoutValues(CacheRefreshConfiguration cacheRefreshConfiguration) { + String[] result = cacheRefreshConfiguration.getKeyAttributes().toArray(new String[0]); + for (int i = 0; i < result.length; i++) { + int index = result[i].indexOf('='); + if (index != -1) { + result[i] = result[i].substring(0, index); + } + } + + return result; + } + + public Map getTargetServerAttributesMapping(CacheRefreshConfiguration cacheRefreshConfiguration) { + Map result = new HashMap(); + for (CacheRefreshAttributeMapping attributeMapping : cacheRefreshConfiguration.getAttributeMapping()) { + result.put(attributeMapping.getDestination(), attributeMapping.getSource()); + } + + return result; + } + + public Properties toLdapProperties(PersistenceEntryManagerFactory ldapEntryManagerFactory, + GluuLdapConfiguration ldapConfiguration) { + String persistenceType = ldapEntryManagerFactory.getPersistenceType(); + Properties ldapProperties = new Properties(); + ldapProperties.put(persistenceType + "#servers", + PropertyUtil.simplePropertiesToCommaSeparatedList(ldapConfiguration.getServers())); + ldapProperties.put(persistenceType + "#maxconnections", + Integer.toString(ldapConfiguration.getMaxConnections())); + ldapProperties.put(persistenceType + "#useSSL", Boolean.toString(ldapConfiguration.isUseSSL())); + ldapProperties.put(persistenceType + "#bindDN", ldapConfiguration.getBindDN()); + ldapProperties.put(persistenceType + "#bindPassword", ldapConfiguration.getBindPassword()); + + // Copy binary attributes list from main LDAP connection + PersistenceOperationService persistenceOperationService = getLdapEntryManager().getOperationService(); + if (persistenceOperationService instanceof LdapOperationService) { + ldapProperties.put(persistenceType + "#binaryAttributes", + PropertyUtil.stringsToCommaSeparatedList(((LdapOperationService) persistenceOperationService) + .getConnectionProvider().getBinaryAttributes())); + } + + return ldapProperties; + } + + public String[] getBaseDNs(GluuLdapConfiguration ldapConfiguration) { + return ldapConfiguration.getBaseDNsStringsList().toArray(new String[0]); + } + + @ObjectClass(value = "gluuPerson") + public class TypedGluuSimplePerson extends GluuSimplePerson { + public TypedGluuSimplePerson() { + super(); + } + } + + public AtomicBoolean getIsActive() { + return isActive; + } + + public void setIsActive(AtomicBoolean isActive) { + this.isActive = isActive; + } + +} diff --git a/jans-link/service/src/main/java/io/jans/link/service/CacheRefreshSnapshotFileService.java b/jans-link/service/src/main/java/io/jans/link/service/CacheRefreshSnapshotFileService.java index 4717704e438..95d9975b982 100644 --- a/jans-link/service/src/main/java/io/jans/link/service/CacheRefreshSnapshotFileService.java +++ b/jans-link/service/src/main/java/io/jans/link/service/CacheRefreshSnapshotFileService.java @@ -23,7 +23,7 @@ import java.util.Map.Entry; import java.util.Set; -import io.jans.link.model.config.CacheRefreshConfiguration; +import io.jans.link.model.config.shared.CacheRefreshConfiguration; import io.jans.util.ArrayHelper; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; diff --git a/jans-link/service/src/main/java/io/jans/link/service/OrganizationService.java b/jans-link/service/src/main/java/io/jans/link/service/OrganizationService.java index eadd1931675..a96574d356b 100644 --- a/jans-link/service/src/main/java/io/jans/link/service/OrganizationService.java +++ b/jans-link/service/src/main/java/io/jans/link/service/OrganizationService.java @@ -6,36 +6,22 @@ package io.jans.link.service; -import io.jans.link.model.config.AppConfiguration; import io.jans.model.ApplicationType; -import jakarta.annotation.Priority; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; /** * Provides operations with organization * * @author Yuriy Movchan Date: 11.02.2010 */ -@ApplicationScoped -@Priority(value = 5) -public class OrganizationService extends io.jans.service.OrganizationService { +public abstract class OrganizationService extends io.jans.service.OrganizationService { private static final long serialVersionUID = -1959146007518514678L; - @Inject - private AppConfiguration appConfiguration; - public String getDnForOrganization() { - return getDnForOrganization(appConfiguration.getBaseDN()); + return getDnForOrganization(getAppConfigurationBaseDn()); } - public String getDnForOrganization(String baseDn) { - if (baseDn == null) { - baseDn = "o=jans"; - } - return baseDn; - } + public abstract String getAppConfigurationBaseDn(); /** * Build DN string for organization @@ -43,7 +29,7 @@ public String getDnForOrganization(String baseDn) { * @return DN string for organization */ public String getBaseDn() { - return appConfiguration.getBaseDN(); + return getAppConfigurationBaseDn(); } @Override diff --git a/jans-link/service/src/main/java/io/jans/link/service/PairwiseIdService.java b/jans-link/service/src/main/java/io/jans/link/service/PairwiseIdService.java index 3cf597000af..aa243518712 100644 --- a/jans-link/service/src/main/java/io/jans/link/service/PairwiseIdService.java +++ b/jans-link/service/src/main/java/io/jans/link/service/PairwiseIdService.java @@ -18,9 +18,6 @@ @ApplicationScoped public class PairwiseIdService implements IPairwiseIdService, Serializable { - /** - * - */ private static final long serialVersionUID = -758342433526960035L; @Inject diff --git a/jans-link/service/src/main/java/io/jans/link/service/PersonService.java b/jans-link/service/src/main/java/io/jans/link/service/PersonService.java index 7b4126b5192..13598169e4b 100644 --- a/jans-link/service/src/main/java/io/jans/link/service/PersonService.java +++ b/jans-link/service/src/main/java/io/jans/link/service/PersonService.java @@ -37,7 +37,6 @@ public class PersonService implements Serializable, IPersonService { @Inject private OrganizationService organizationService; - public void addPersonWithoutCheck(GluuCustomPerson person) { person.setCreationDate(new Date()); persistenceEntryManager.persist(person); diff --git a/jans-link/service/src/main/java/io/jans/link/service/custom/CustomScriptService.java b/jans-link/service/src/main/java/io/jans/link/service/custom/CustomScriptService.java index 524d74cca45..3b6337e57e0 100644 --- a/jans-link/service/src/main/java/io/jans/link/service/custom/CustomScriptService.java +++ b/jans-link/service/src/main/java/io/jans/link/service/custom/CustomScriptService.java @@ -5,31 +5,17 @@ */ package io.jans.link.service.custom; -import io.jans.link.model.config.StaticConfiguration; import io.jans.service.custom.script.AbstractCustomScriptService; -import jakarta.annotation.Priority; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Alternative; -import jakarta.inject.Inject; -import jakarta.interceptor.Interceptor; /** * Operations with custom scripts * * @author Yuriy Movchan Date: 12/03/2014 */ -@ApplicationScoped -@Alternative -@Priority(Interceptor.Priority.APPLICATION + 1) -public class CustomScriptService extends AbstractCustomScriptService { - - @Inject - private StaticConfiguration staticConfiguration; +public abstract class CustomScriptService extends AbstractCustomScriptService { private static final long serialVersionUID = -5283102477313448031L; - public String baseDn() { - return staticConfiguration.getBaseDn().getScripts(); - } + public abstract String baseDn(); } diff --git a/jans-link/service/src/main/java/io/jans/link/service/status/cdi/event/StatusCheckerTimerEvent.java b/jans-link/service/src/main/java/io/jans/link/service/status/cdi/event/StatusCheckerTimerEvent.java index 2f1555ec9e8..4fc6f6ce640 100644 --- a/jans-link/service/src/main/java/io/jans/link/service/status/cdi/event/StatusCheckerTimerEvent.java +++ b/jans-link/service/src/main/java/io/jans/link/service/status/cdi/event/StatusCheckerTimerEvent.java @@ -9,6 +9,5 @@ public class StatusCheckerTimerEvent { public StatusCheckerTimerEvent() {} - } diff --git a/jans-link/service/src/main/resources/META-INF/beans.xml b/jans-link/service/src/main/resources/META-INF/beans.xml index ddc64918ba0..b7930c568e8 100644 --- a/jans-link/service/src/main/resources/META-INF/beans.xml +++ b/jans-link/service/src/main/resources/META-INF/beans.xml @@ -2,5 +2,5 @@ + bean-discovery-mode="annotated" version="3.0">