You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2018/05/03 14:58:13 UTC
[6/8] syncope git commit: [SYNCOPE-1270] implementation for OpenID
Connect for Admin Console and Enduser - This closes #74
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/logic/pom.xml
----------------------------------------------------------------------
diff --git a/ext/oidcclient/logic/pom.xml b/ext/oidcclient/logic/pom.xml
new file mode 100644
index 0000000..6701bc3
--- /dev/null
+++ b/ext/oidcclient/logic/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.syncope.ext</groupId>
+ <artifactId>syncope-ext-oidcclient</artifactId>
+ <version>2.1.0-SNAPSHOT</version>
+ </parent>
+
+ <name>Apache Syncope Ext: OIDC Client Logic</name>
+ <description>Apache Syncope Ext: OIDC Client Logic</description>
+ <groupId>org.apache.syncope.ext.oidcclient</groupId>
+ <artifactId>syncope-ext-oidcclient-logic</artifactId>
+ <packaging>jar</packaging>
+
+ <properties>
+ <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.syncope.core</groupId>
+ <artifactId>syncope-core-logic</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.syncope.ext.oidcclient</groupId>
+ <artifactId>syncope-ext-oidcclient-provisioning-java</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-extension-providers</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-security-sso-oidc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-json-provider</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/OIDCClientLogic.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/OIDCClientLogic.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/OIDCClientLogic.java
new file mode 100644
index 0000000..638ae5e
--- /dev/null
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/OIDCClientLogic.java
@@ -0,0 +1,406 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.uuid.Generators;
+import com.fasterxml.uuid.impl.RandomBasedGenerator;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.provider.json.JsonMapObjectProvider;
+import org.apache.cxf.rs.security.jose.jaxrs.JsonWebKeysProvider;
+import org.apache.cxf.rs.security.oauth2.client.Consumer;
+import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken;
+import org.apache.cxf.rs.security.oidc.common.IdToken;
+import org.apache.cxf.rs.security.oidc.common.UserInfo;
+import org.apache.cxf.rs.security.oidc.rp.IdTokenReader;
+import org.apache.cxf.rs.security.oidc.rp.UserInfoClient;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.OIDCConstants;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.OIDCLoginRequestTO;
+import org.apache.syncope.common.lib.to.OIDCLoginResponseTO;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.logic.model.TokenEndpointResponse;
+import org.apache.syncope.core.logic.oidc.OIDCUserManager;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.OIDCProviderDAO;
+import org.apache.syncope.core.persistence.api.entity.OIDCProvider;
+import org.apache.syncope.core.persistence.api.entity.OIDCProviderItem;
+import org.apache.syncope.core.provisioning.api.data.AccessTokenDataBinder;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.core.spring.security.AuthDataAccessor;
+import org.apache.syncope.core.spring.security.Encryptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class OIDCClientLogic extends AbstractTransactionalLogic<AbstractBaseBean> {
+
+ private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private static final RandomBasedGenerator UUID_GENERATOR = Generators.randomBasedGenerator();
+
+ private static final String JWT_CLAIM_OP_ENTITYID = "OP_ENTITYID";
+
+ private static final String JWT_CLAIM_USERID = "USERID";
+
+ @Autowired
+ private AuthDataAccessor authDataAccessor;
+
+ @Autowired
+ private AccessTokenDataBinder accessTokenDataBinder;
+
+ @Autowired
+ private OIDCProviderDAO opDAO;
+
+ @Autowired
+ private OIDCUserManager userManager;
+
+ private OIDCProvider getOIDCProvider(final String opName) {
+ OIDCProvider op = null;
+ if (StringUtils.isBlank(opName)) {
+ List<OIDCProvider> ops = opDAO.findAll();
+ if (!ops.isEmpty()) {
+ op = ops.get(0);
+ }
+ } else {
+ op = opDAO.findByName(opName);
+ }
+ if (op == null) {
+ throw new NotFoundException(StringUtils.isBlank(opName)
+ ? "Any OIDC Provider"
+ : "OIDC Provider '" + opName + "'");
+ }
+ return op;
+ }
+
+ @PreAuthorize("hasRole('" + StandardEntitlement.ANONYMOUS + "')")
+ public OIDCLoginRequestTO createLoginRequest(final String redirectURI, final String opName) {
+ // 1. look for Provider
+ OIDCProvider op = getOIDCProvider(opName);
+
+ // 2. create AuthnRequest
+ OIDCLoginRequestTO requestTO = new OIDCLoginRequestTO();
+ requestTO.setProviderAddress(op.getAuthorizationEndpoint());
+ requestTO.setClientId(op.getClientID());
+ requestTO.setScope("openid email profile");
+ requestTO.setResponseType("code");
+ requestTO.setRedirectURI(redirectURI);
+ requestTO.setState(UUID_GENERATOR.generate().toString());
+ return requestTO;
+ }
+
+ @PreAuthorize("hasRole('" + StandardEntitlement.ANONYMOUS + "')")
+ public OIDCLoginResponseTO login(final String redirectURI, final String authorizationCode, final String opName) {
+ OIDCProvider op = getOIDCProvider(opName);
+
+ // 1. get OpenID Connect tokens
+ String body = OIDCConstants.CODE + "=" + authorizationCode
+ + "&" + OIDCConstants.CLIENT_ID + "=" + op.getClientID()
+ + "&" + OIDCConstants.CLIENT_SECRET + "=" + op.getClientSecret()
+ + "&" + OIDCConstants.REDIRECT_URI + "=" + redirectURI
+ + "&" + OIDCConstants.GRANT_TYPE + "=authorization_code";
+ TokenEndpointResponse tokenEndpointResponse = getOIDCTokens(op.getTokenEndpoint(), body);
+
+ // 1. get OpenID Connect tokens
+ Consumer consumer = new Consumer(op.getClientID(), op.getClientSecret());
+
+ // 2. validate token
+ IdToken idToken = getValidatedIdToken(op, consumer, tokenEndpointResponse.getIdToken());
+
+ // 3. extract user information
+ UserInfo userInfo = getUserInfo(op, tokenEndpointResponse.getAccessToken(), idToken, consumer);
+
+ // 4. prepare the result: find matching user (if any) and return the received attributes
+ final OIDCLoginResponseTO responseTO = new OIDCLoginResponseTO();
+ responseTO.setEmail(userInfo.getEmail());
+ responseTO.setFamilyName(userInfo.getFamilyName());
+ responseTO.setGivenName(userInfo.getGivenName());
+ responseTO.setName(userInfo.getName());
+ responseTO.setSubject(userInfo.getSubject());
+
+ String keyValue = null;
+ for (OIDCProviderItem item : op.getItems()) {
+ AttrTO attrTO = new AttrTO();
+ attrTO.setSchema(item.getExtAttrName());
+ switch (item.getExtAttrName()) {
+ case UserInfo.PREFERRED_USERNAME_CLAIM:
+ attrTO.getValues().add(userInfo.getPreferredUserName());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getPreferredUserName();
+ }
+ break;
+
+ case UserInfo.PROFILE_CLAIM:
+ attrTO.getValues().add(userInfo.getProfile());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getProfile();
+ }
+ break;
+
+ case UserInfo.EMAIL_CLAIM:
+ attrTO.getValues().add(userInfo.getEmail());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getEmail();
+ }
+ break;
+
+ case UserInfo.NAME_CLAIM:
+ attrTO.getValues().add(userInfo.getName());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getName();
+ }
+ break;
+
+ case UserInfo.FAMILY_NAME_CLAIM:
+ attrTO.getValues().add(userInfo.getFamilyName());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getFamilyName();
+ }
+ break;
+
+ case UserInfo.MIDDLE_NAME_CLAIM:
+ attrTO.getValues().add(userInfo.getMiddleName());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getMiddleName();
+ }
+ break;
+
+ case UserInfo.GIVEN_NAME_CLAIM:
+ attrTO.getValues().add(userInfo.getGivenName());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getGivenName();
+ }
+ break;
+
+ case UserInfo.NICKNAME_CLAIM:
+ attrTO.getValues().add(userInfo.getNickName());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getNickName();
+ }
+ break;
+
+ case UserInfo.GENDER_CLAIM:
+ attrTO.getValues().add(userInfo.getGender());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getGender();
+ }
+ break;
+
+ case UserInfo.LOCALE_CLAIM:
+ attrTO.getValues().add(userInfo.getLocale());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getLocale();
+ }
+ break;
+
+ case UserInfo.ZONEINFO_CLAIM:
+ attrTO.getValues().add(userInfo.getZoneInfo());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getZoneInfo();
+ }
+ break;
+
+ case UserInfo.BIRTHDATE_CLAIM:
+ attrTO.getValues().add(userInfo.getBirthDate());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getBirthDate();
+ }
+ break;
+
+ case UserInfo.PHONE_CLAIM:
+ attrTO.getValues().add(userInfo.getPhoneNumber());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getPhoneNumber();
+ }
+ break;
+
+ case UserInfo.ADDRESS_CLAIM:
+ attrTO.getValues().add(userInfo.getUserAddress().getFormatted());
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = userInfo.getUserAddress().getFormatted();
+ }
+ break;
+
+ case UserInfo.UPDATED_AT_CLAIM:
+ attrTO.getValues().add(Long.toString(userInfo.getUpdatedAt()));
+ responseTO.getAttrs().add(attrTO);
+ if (item.isConnObjectKey()) {
+ keyValue = Long.toString(userInfo.getUpdatedAt());
+ }
+ break;
+
+ default:
+ LOG.warn("Unsupported: {} ", item.getExtAttrName());
+ }
+ }
+
+ final List<String> matchingUsers = keyValue == null
+ ? Collections.<String>emptyList()
+ : userManager.findMatchingUser(keyValue, op.getConnObjectKeyItem().get());
+ LOG.debug("Found {} matching users for {}", matchingUsers.size(), keyValue);
+
+ String username;
+ if (matchingUsers.isEmpty()) {
+ if (op.isCreateUnmatching()) {
+ LOG.debug("No user matching {}, about to create", keyValue);
+
+ final String emailValue = userInfo.getEmail();
+ username = AuthContextUtils.execWithAuthContext(AuthContextUtils.getDomain(),
+ () -> userManager.create(op, responseTO, emailValue));
+ } else {
+ throw new NotFoundException("User matching the provided value " + keyValue);
+ }
+ } else if (matchingUsers.size() > 1) {
+ throw new IllegalArgumentException("Several users match the provided value " + keyValue);
+ } else {
+ if (op.isUpdateMatching()) {
+ LOG.debug("About to update {} for {}", matchingUsers.get(0), keyValue);
+
+ username = AuthContextUtils.execWithAuthContext(AuthContextUtils.getDomain(),
+ () -> userManager.update(matchingUsers.get(0), op, responseTO));
+ } else {
+ username = matchingUsers.get(0);
+ }
+
+ }
+
+ responseTO.setUsername(username);
+
+ // 5. generate JWT for further access
+ Map<String, Object> claims = new HashMap<>();
+ claims.put(JWT_CLAIM_OP_ENTITYID, idToken.getIssuer());
+ claims.put(JWT_CLAIM_USERID, idToken.getSubject());
+
+ byte[] authorities = null;
+ try {
+ authorities = ENCRYPTOR.encode(POJOHelper.serialize(
+ authDataAccessor.getAuthorities(responseTO.getUsername())), CipherAlgorithm.AES).
+ getBytes();
+ } catch (Exception e) {
+ LOG.error("Could not fetch authorities", e);
+ }
+
+ Pair<String, Date> accessTokenInfo =
+ accessTokenDataBinder.create(responseTO.getUsername(), claims, authorities, true);
+ responseTO.setAccessToken(accessTokenInfo.getLeft());
+ responseTO.setAccessTokenExpiryTime(accessTokenInfo.getRight());
+
+ return responseTO;
+ }
+
+ private TokenEndpointResponse getOIDCTokens(final String url, final String body) {
+ String oidcTokens = WebClient.create(url).
+ type(MediaType.APPLICATION_FORM_URLENCODED).accept(MediaType.APPLICATION_JSON).
+ post(body).
+ readEntity(String.class);
+ TokenEndpointResponse endpointResponse = null;
+ try {
+ endpointResponse = MAPPER.readValue(oidcTokens, TokenEndpointResponse.class);
+ } catch (Exception e) {
+ LOG.error("While getting the Tokens from the OP", e);
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
+ sce.getElements().add(e.getMessage());
+ throw sce;
+ }
+ return endpointResponse;
+ }
+
+ private IdToken getValidatedIdToken(final OIDCProvider op, final Consumer consumer, final String jwtIdToken) {
+ IdTokenReader idTokenReader = new IdTokenReader();
+ idTokenReader.setClockOffset(10);
+ idTokenReader.setIssuerId(op.getIssuer());
+ WebClient jwkSetClient = WebClient.create(
+ op.getJwksUri(), Arrays.asList(new JsonWebKeysProvider())).
+ accept(MediaType.APPLICATION_JSON);
+ idTokenReader.setJwkSetClient(jwkSetClient);
+ IdToken idToken = null;
+ try {
+ idToken = idTokenReader.getIdToken(jwtIdToken, consumer);
+ } catch (Exception e) {
+ LOG.error("While validating the id_token", e);
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
+ sce.getElements().add(e.getMessage());
+ throw sce;
+ }
+ return idToken;
+ }
+
+ private UserInfo getUserInfo(
+ final OIDCProvider op,
+ final String accessToken,
+ final IdToken idToken,
+ final Consumer consumer) {
+
+ WebClient userInfoServiceClient = WebClient.create(
+ op.getUserinfoEndpoint(), Arrays.asList(new JsonMapObjectProvider())).
+ accept(MediaType.APPLICATION_JSON);
+ ClientAccessToken clientAccessToken = new ClientAccessToken("Bearer", accessToken);
+ UserInfoClient userInfoClient = new UserInfoClient();
+ userInfoClient.setUserInfoServiceClient(userInfoServiceClient);
+ UserInfo userInfo = null;
+ try {
+ userInfo = userInfoClient.getUserInfo(clientAccessToken, idToken, consumer);
+ } catch (Exception e) {
+ LOG.error("While getting the userInfo", e);
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
+ sce.getElements().add(e.getMessage());
+ throw sce;
+ }
+ return userInfo;
+ }
+
+ @Override
+ protected AbstractBaseBean resolveReference(
+ final Method method, final Object... args) throws UnresolvedReferenceException {
+
+ throw new UnresolvedReferenceException();
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/OIDCProviderLogic.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/OIDCProviderLogic.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/OIDCProviderLogic.java
new file mode 100644
index 0000000..af8e579
--- /dev/null
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/OIDCProviderLogic.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.ItemTO;
+import org.apache.syncope.common.lib.to.OIDCProviderTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.OIDCClientEntitlement;
+import org.apache.syncope.core.logic.init.OIDCClientClassPathScanImplementationLookup;
+import org.apache.syncope.core.logic.model.OIDCProviderDiscoveryDocument;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.OIDCProviderDAO;
+import org.apache.syncope.core.persistence.api.entity.OIDCProvider;
+import org.apache.syncope.core.provisioning.api.data.OIDCProviderDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class OIDCProviderLogic extends AbstractTransactionalLogic<OIDCProviderTO> {
+
+ @Autowired
+ private OIDCProviderDAO opDAO;
+
+ @Autowired
+ private OIDCProviderDataBinder binder;
+
+ @Autowired
+ private OIDCClientClassPathScanImplementationLookup implLookup;
+
+ @PreAuthorize("isAuthenticated()")
+ public Set<String> getActionsClasses() {
+ return implLookup.getActionsClasses();
+ }
+
+ private OIDCProviderDiscoveryDocument getDiscoveryDocument(final String issuer) {
+ WebClient client = WebClient.create(
+ issuer + "/.well-known/openid-configuration", Arrays.asList(new JacksonJsonProvider())).
+ accept(MediaType.APPLICATION_JSON);
+ return client.get(OIDCProviderDiscoveryDocument.class);
+ }
+
+ @PreAuthorize("hasRole('" + OIDCClientEntitlement.OP_CREATE + "')")
+ public String createFromDiscovery(final OIDCProviderTO opTO) {
+ OIDCProviderDiscoveryDocument discoveryDocument = getDiscoveryDocument(opTO.getIssuer());
+
+ opTO.setAuthorizationEndpoint(discoveryDocument.getAuthorizationEndpoint());
+ opTO.setIssuer(discoveryDocument.getIssuer());
+ opTO.setJwksUri(discoveryDocument.getJwksUri());
+ opTO.setTokenEndpoint(discoveryDocument.getTokenEndpoint());
+ opTO.setUserinfoEndpoint(discoveryDocument.getUserinfoEndpoint());
+
+ return create(opTO);
+ }
+
+ @PreAuthorize("hasRole('" + OIDCClientEntitlement.OP_CREATE + "')")
+ public String create(final OIDCProviderTO opTO) {
+ if (opTO.getConnObjectKeyItem() == null) {
+ ItemTO connObjectKeyItem = new ItemTO();
+ connObjectKeyItem.setIntAttrName("username");
+ connObjectKeyItem.setExtAttrName("email");
+ opTO.setConnObjectKeyItem(connObjectKeyItem);
+ }
+
+ OIDCProvider provider = opDAO.save(binder.create(opTO));
+
+ return provider.getKey();
+ }
+
+ @PreAuthorize("isAuthenticated()")
+ @Transactional(readOnly = true)
+ public List<OIDCProviderTO> list() {
+ return opDAO.findAll().stream().map(binder::getOIDCProviderTO).collect(Collectors.toList());
+ }
+
+ @PreAuthorize("hasRole('" + OIDCClientEntitlement.OP_READ + "')")
+ @Transactional(readOnly = true)
+ public OIDCProviderTO read(final String key) {
+ OIDCProvider op = opDAO.find(key);
+ if (op == null) {
+ throw new NotFoundException("OIDC Provider '" + key + "'");
+ }
+ return binder.getOIDCProviderTO(op);
+ }
+
+ @PreAuthorize("hasRole('" + OIDCClientEntitlement.OP_UPDATE + "')")
+ public void update(final OIDCProviderTO oidcProviderTO) {
+ OIDCProvider oidcProvider = opDAO.find(oidcProviderTO.getKey());
+ if (oidcProvider == null) {
+ throw new NotFoundException("OIDC Provider '" + oidcProviderTO.getKey() + "'");
+ }
+
+ if (!oidcProvider.getIssuer().equals(oidcProviderTO.getIssuer())) {
+ LOG.error("Issuers do not match: expected {}, found {}",
+ oidcProvider.getIssuer(), oidcProviderTO.getIssuer());
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidEntity);
+ sce.getElements().add("Issuers do not match");
+ throw sce;
+ }
+
+ opDAO.save(binder.update(oidcProvider, oidcProviderTO));
+ }
+
+ @PreAuthorize("hasRole('" + OIDCClientEntitlement.OP_DELETE + "')")
+ public void delete(final String key) {
+ OIDCProvider op = opDAO.find(key);
+ if (op == null) {
+ throw new NotFoundException("OIDC Provider '" + key + "'");
+ }
+ opDAO.delete(key);
+ }
+
+ @Override
+ protected OIDCProviderTO resolveReference(
+ final Method method, final Object... args) throws UnresolvedReferenceException {
+
+ String key = null;
+
+ if (ArrayUtils.isNotEmpty(args)) {
+ for (int i = 0; key == null && i < args.length; i++) {
+ if (args[i] instanceof String) {
+ key = (String) args[i];
+ } else if (args[i] instanceof OIDCProviderTO) {
+ key = ((OIDCProviderTO) args[i]).getKey();
+ }
+ }
+ }
+
+ if (key != null) {
+ try {
+ return binder.getOIDCProviderTO(opDAO.find(key));
+ } catch (Throwable ignore) {
+ LOG.debug("Unresolved reference", ignore);
+ throw new UnresolvedReferenceException(ignore);
+ }
+ }
+
+ throw new UnresolvedReferenceException();
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientClassPathScanImplementationLookup.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientClassPathScanImplementationLookup.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientClassPathScanImplementationLookup.java
new file mode 100644
index 0000000..82d6d8a
--- /dev/null
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientClassPathScanImplementationLookup.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic.init;
+
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.api.SyncopeLoader;
+import org.apache.syncope.core.provisioning.api.OIDCProviderActions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.type.filter.AssignableTypeFilter;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ClassUtils;
+
+@Component
+public class OIDCClientClassPathScanImplementationLookup implements SyncopeLoader {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ImplementationLookup.class);
+
+ private static final String DEFAULT_BASE_PACKAGE = "org.apache.syncope.core";
+
+ private Set<String> actionsClasses;
+
+ @Override
+ public Integer getPriority() {
+ return Integer.MIN_VALUE;
+ }
+
+ @Override
+ public void load() {
+ actionsClasses = new HashSet<>();
+
+ ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
+ scanner.addIncludeFilter(new AssignableTypeFilter(OIDCProviderActions.class));
+
+ for (BeanDefinition bd : scanner.findCandidateComponents(DEFAULT_BASE_PACKAGE)) {
+ try {
+ Class<?> clazz = ClassUtils.resolveClassName(
+ bd.getBeanClassName(), ClassUtils.getDefaultClassLoader());
+ boolean isAbstractClazz = Modifier.isAbstract(clazz.getModifiers());
+
+ if (OIDCProviderActions.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+ actionsClasses.add(clazz.getName());
+ }
+ } catch (Throwable t) {
+ LOG.warn("Could not inspect class {}", bd.getBeanClassName(), t);
+ }
+ }
+
+ actionsClasses = Collections.unmodifiableSet(actionsClasses);
+ }
+
+ public Set<String> getActionsClasses() {
+ return actionsClasses;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientLoader.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientLoader.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientLoader.java
new file mode 100644
index 0000000..f24eb79
--- /dev/null
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCClientLoader.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic.init;
+
+import org.apache.syncope.common.lib.types.OIDCClientEntitlement;
+import org.apache.syncope.core.persistence.api.DomainsHolder;
+import org.apache.syncope.core.persistence.api.SyncopeLoader;
+import org.apache.syncope.core.provisioning.api.EntitlementsHolder;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class OIDCClientLoader implements SyncopeLoader {
+
+ @Autowired
+ private DomainsHolder domainsHolder;
+
+ @Override
+ public Integer getPriority() {
+ return 1000;
+ }
+
+ @Override
+ public void load() {
+ EntitlementsHolder.getInstance().init(OIDCClientEntitlement.values());
+
+ for (String domain : domainsHolder.getDomains().keySet()) {
+ AuthContextUtils.execWithAuthContext(domain, new AuthContextUtils.Executable<Void>() {
+
+ @Override
+ public Void exec() {
+ return null;
+ }
+ });
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/model/OIDCProviderDiscoveryDocument.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/model/OIDCProviderDiscoveryDocument.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/model/OIDCProviderDiscoveryDocument.java
new file mode 100644
index 0000000..aae3f0c
--- /dev/null
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/model/OIDCProviderDiscoveryDocument.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({
+ "issuer",
+ "authorization_endpoint",
+ "token_endpoint",
+ "userinfo_endpoint",
+ "end_session_endpoint",
+ "jwks_uri",
+ "registration_endpoint"
+})
+public class OIDCProviderDiscoveryDocument {
+
+ @JsonProperty("issuer")
+ private String issuer;
+
+ @JsonProperty("authorization_endpoint")
+ private String authorizationEndpoint;
+
+ @JsonProperty("token_endpoint")
+ private String tokenEndpoint;
+
+ @JsonProperty("userinfo_endpoint")
+ private String userinfoEndpoint;
+
+ @JsonProperty("end_session_endpoint")
+ private String endSessionEndpoint;
+
+ @JsonProperty("jwks_uri")
+ private String jwksUri;
+
+ @JsonProperty("registration_endpoint")
+ private String registrationEndpoint;
+
+ @JsonProperty("issuer")
+ public String getIssuer() {
+ return issuer;
+ }
+
+ @JsonProperty("issuer")
+ public void setIssuer(final String issuer) {
+ this.issuer = issuer;
+ }
+
+ @JsonProperty("authorization_endpoint")
+ public String getAuthorizationEndpoint() {
+ return authorizationEndpoint;
+ }
+
+ @JsonProperty("authorization_endpoint")
+ public void setAuthorizationEndpoint(final String authorizationEndpoint) {
+ this.authorizationEndpoint = authorizationEndpoint;
+ }
+
+ @JsonProperty("token_endpoint")
+ public String getTokenEndpoint() {
+ return tokenEndpoint;
+ }
+
+ @JsonProperty("token_endpoint")
+ public void setTokenEndpoint(final String tokenEndpoint) {
+ this.tokenEndpoint = tokenEndpoint;
+ }
+
+ @JsonProperty("userinfo_endpoint")
+ public String getUserinfoEndpoint() {
+ return userinfoEndpoint;
+ }
+
+ @JsonProperty("userinfo_endpoint")
+ public void setUserinfoEndpoint(final String userinfoEndpoint) {
+ this.userinfoEndpoint = userinfoEndpoint;
+ }
+
+ @JsonProperty("end_session_endpoint")
+ public String getEndSessionEndpoint() {
+ return endSessionEndpoint;
+ }
+
+ @JsonProperty("end_session_endpoint")
+ public void setEndSessionEndpoint(final String endSessionEndpoint) {
+ this.endSessionEndpoint = endSessionEndpoint;
+ }
+
+ @JsonProperty("jwks_uri")
+ public String getJwksUri() {
+ return jwksUri;
+ }
+
+ @JsonProperty("jwks_uri")
+ public void setJwksUri(final String jwksUri) {
+ this.jwksUri = jwksUri;
+ }
+
+ @JsonProperty("registration_endpoint")
+ public String getRegistrationEndpoint() {
+ return registrationEndpoint;
+ }
+
+ @JsonProperty("registration_endpoint")
+ public void setRegistrationEndpoint(final String registrationEndpoint) {
+ this.registrationEndpoint = registrationEndpoint;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/model/TokenEndpointResponse.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/model/TokenEndpointResponse.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/model/TokenEndpointResponse.java
new file mode 100644
index 0000000..7908b88
--- /dev/null
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/model/TokenEndpointResponse.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({
+ "access_token",
+ "id_token",
+ "expires_in",
+ "token_type"
+})
+public class TokenEndpointResponse {
+
+ @JsonProperty("access_token")
+ private String accessToken;
+
+ @JsonProperty("id_token")
+ private String idToken;
+
+ @JsonProperty("expires_in")
+ private int expiresIn;
+
+ @JsonProperty("token_type")
+ private String tokenType;
+
+ @JsonProperty("access_token")
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ @JsonProperty("access_token")
+ public void setAccessToken(final String accessToken) {
+ this.accessToken = accessToken;
+ }
+
+ @JsonProperty("id_token")
+ public String getIdToken() {
+ return idToken;
+ }
+
+ @JsonProperty("id_token")
+ public void setIdToken(final String idToken) {
+ this.idToken = idToken;
+ }
+
+ @JsonProperty("expires_in")
+ public int getExpiresIn() {
+ return expiresIn;
+ }
+
+ @JsonProperty("expires_in")
+ public void setExpiresIn(final int expiresIn) {
+ this.expiresIn = expiresIn;
+ }
+
+ @JsonProperty("token_type")
+ public String getTokenType() {
+ return tokenType;
+ }
+
+ @JsonProperty("token_type")
+ public void setTokenType(final String tokenType) {
+ this.tokenType = tokenType;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
new file mode 100644
index 0000000..25cb784
--- /dev/null
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic.oidc;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.AnyOperations;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.OIDCLoginResponseTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.OIDCProvider;
+import org.apache.syncope.core.persistence.api.entity.OIDCProviderItem;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.IntAttrName;
+import org.apache.syncope.core.provisioning.api.OIDCProviderActions;
+import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
+import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class OIDCUserManager {
+
+ private static final Logger LOG = LoggerFactory.getLogger(OIDCUserManager.class);
+
+ @Autowired
+ private UserDAO userDAO;
+
+ @Autowired
+ private PlainSchemaDAO plainSchemaDAO;
+
+ @Autowired
+ private IntAttrNameParser intAttrNameParser;
+
+ @Autowired
+ private EntityFactory entityFactory;
+
+ @Autowired
+ private TemplateUtils templateUtils;
+
+ @Autowired
+ private UserProvisioningManager provisioningManager;
+
+ @Autowired
+ private UserDataBinder binder;
+
+ @Transactional(readOnly = true)
+ public List<String> findMatchingUser(final String keyValue, final OIDCProviderItem connObjectKeyItem) {
+ List<String> result = new ArrayList<>();
+
+ String transformed = keyValue;
+ for (ItemTransformer transformer : MappingUtils.getItemTransformers(connObjectKeyItem)) {
+ List<Object> output = transformer.beforePull(
+ null,
+ null,
+ Collections.<Object>singletonList(transformed));
+ if (output != null && !output.isEmpty()) {
+ transformed = output.get(0).toString();
+ }
+ }
+
+ IntAttrName intAttrName;
+ try {
+ intAttrName = intAttrNameParser.parse(connObjectKeyItem.getIntAttrName(), AnyTypeKind.USER);
+ } catch (ParseException e) {
+ LOG.error("Invalid intAttrName '{}' specified, ignoring", connObjectKeyItem.getIntAttrName(), e);
+ return result;
+ }
+
+ if (intAttrName.getField() != null) {
+ switch (intAttrName.getField()) {
+ case "key":
+ User byKey = userDAO.find(transformed);
+ if (byKey != null) {
+ result.add(byKey.getUsername());
+ }
+ break;
+
+ case "username":
+ User byUsername = userDAO.findByUsername(transformed);
+ if (byUsername != null) {
+ result.add(byUsername.getUsername());
+ }
+ break;
+
+ default:
+ }
+ } else if (intAttrName.getSchemaType() != null) {
+ switch (intAttrName.getSchemaType()) {
+ case PLAIN:
+ PlainAttrValue value = entityFactory.newEntity(UPlainAttrValue.class);
+
+ PlainSchema schema = plainSchemaDAO.find(intAttrName.getSchemaName());
+ if (schema == null) {
+ value.setStringValue(transformed);
+ } else {
+ try {
+ value.parseValue(schema, transformed);
+ } catch (ParsingValidationException e) {
+ LOG.error("While parsing provided key value {}", transformed, e);
+ value.setStringValue(transformed);
+ }
+ }
+
+ result.addAll(userDAO.findByPlainAttrValue(intAttrName.getSchemaName(), value, false).stream().
+ map(User::getUsername).collect(Collectors.toList()));
+ break;
+
+ case DERIVED:
+ result.addAll(userDAO.findByDerAttrValue(intAttrName.getSchemaName(), transformed, false).stream().
+ map(User::getUsername).collect(Collectors.toList()));
+ break;
+
+ default:
+ }
+ }
+
+ return result;
+ }
+
+ private List<OIDCProviderActions> getActions(final OIDCProvider op) {
+ List<OIDCProviderActions> actions = new ArrayList<>();
+
+ op.getActionsClassNames().forEach(className -> {
+ try {
+ Class<?> actionsClass = Class.forName(className);
+ OIDCProviderActions opActions = (OIDCProviderActions) ApplicationContextProvider.getBeanFactory().
+ createBean(actionsClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
+
+ actions.add(opActions);
+ } catch (Exception e) {
+ LOG.warn("Class '{}' not found", className, e);
+ }
+ });
+
+ return actions;
+ }
+
+ public void fill(final OIDCProvider op, final OIDCLoginResponseTO responseTO, final UserTO userTO) {
+ op.getItems().forEach(item -> {
+ List<String> values = Collections.emptyList();
+ Optional<AttrTO> oidcAttr = responseTO.getAttr(item.getExtAttrName());
+ if (oidcAttr.isPresent() && !oidcAttr.get().getValues().isEmpty()) {
+ values = oidcAttr.get().getValues();
+
+ List<Object> transformed = new ArrayList<>(values);
+ for (ItemTransformer transformer : MappingUtils.getItemTransformers(item)) {
+ transformed = transformer.beforePull(null, userTO, transformed);
+ }
+ values.clear();
+ for (Object value : transformed) {
+ values.add(value.toString());
+ }
+ }
+
+ IntAttrName intAttrName = null;
+ try {
+ intAttrName = intAttrNameParser.parse(item.getIntAttrName(), AnyTypeKind.USER);
+ } catch (ParseException e) {
+ LOG.error("Invalid intAttrName '{}' specified, ignoring", item.getIntAttrName(), e);
+ }
+
+ if (intAttrName != null && intAttrName.getField() != null) {
+ switch (intAttrName.getField()) {
+ case "username":
+ if (!values.isEmpty()) {
+ userTO.setUsername(values.get(0));
+ }
+ break;
+
+ default:
+ LOG.warn("Unsupported: {}", intAttrName.getField());
+ }
+ } else if (intAttrName != null && intAttrName.getSchemaType() != null) {
+ switch (intAttrName.getSchemaType()) {
+ case PLAIN:
+ Optional<AttrTO> attr = userTO.getPlainAttr(intAttrName.getSchemaName());
+ if (attr.isPresent()) {
+ attr.get().getValues().clear();
+ } else {
+ attr = Optional.of(new AttrTO.Builder().schema(intAttrName.getSchemaName()).build());
+ userTO.getPlainAttrs().add(attr.get());
+ }
+ attr.get().getValues().addAll(values);
+ break;
+
+ default:
+ LOG.warn("Unsupported: {} {}", intAttrName.getSchemaType(), intAttrName.getSchemaName());
+ }
+ }
+ });
+ }
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public String create(final OIDCProvider op, final OIDCLoginResponseTO responseTO, final String email) {
+ UserTO userTO = new UserTO();
+
+ if (op.getUserTemplate() != null && op.getUserTemplate().get() != null) {
+ templateUtils.apply(userTO, op.getUserTemplate().get());
+ }
+
+ List<OIDCProviderActions> actions = getActions(op);
+ for (OIDCProviderActions action : actions) {
+ userTO = action.beforeCreate(userTO, responseTO);
+ }
+
+ fill(op, responseTO, userTO);
+
+ if (userTO.getRealm() == null) {
+ userTO.setRealm(SyncopeConstants.ROOT_REALM);
+ }
+ if (userTO.getUsername() == null) {
+ userTO.setUsername(email);
+ }
+
+ Pair<String, List<PropagationStatus>> created = provisioningManager.create(userTO, false, false);
+ userTO = binder.getUserTO(created.getKey());
+
+ for (OIDCProviderActions action : actions) {
+ userTO = action.afterCreate(userTO, responseTO);
+ }
+
+ return userTO.getUsername();
+ }
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public String update(final String username, final OIDCProvider op, final OIDCLoginResponseTO responseTO) {
+ UserTO userTO = binder.getUserTO(userDAO.findKey(username));
+ UserTO original = SerializationUtils.clone(userTO);
+
+ fill(op, responseTO, userTO);
+
+ UserPatch userPatch = AnyOperations.diff(userTO, original, true);
+
+ List<OIDCProviderActions> actions = getActions(op);
+ for (OIDCProviderActions action : actions) {
+ userPatch = action.beforeUpdate(userPatch, responseTO);
+ }
+
+ Pair<UserPatch, List<PropagationStatus>> updated = provisioningManager.update(userPatch, false);
+ userTO = binder.getUserTO(updated.getLeft().getKey());
+
+ for (OIDCProviderActions action : actions) {
+ userTO = action.afterUpdate(userTO, responseTO);
+ }
+
+ return userTO.getUsername();
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-api/pom.xml
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-api/pom.xml b/ext/oidcclient/persistence-api/pom.xml
new file mode 100644
index 0000000..321602b
--- /dev/null
+++ b/ext/oidcclient/persistence-api/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.syncope.ext</groupId>
+ <artifactId>syncope-ext-oidcclient</artifactId>
+ <version>2.1.0-SNAPSHOT</version>
+ </parent>
+
+ <name>Apache Syncope Ext: OIDC Client Persistence API</name>
+ <description>Apache Syncope Ext: OIDC Client Persistence API</description>
+ <groupId>org.apache.syncope.ext.oidcclient</groupId>
+ <artifactId>syncope-ext-oidcclient-persistence-api</artifactId>
+ <packaging>jar</packaging>
+
+ <properties>
+ <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.syncope.core</groupId>
+ <artifactId>syncope-core-persistence-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.syncope.ext.oidcclient</groupId>
+ <artifactId>syncope-ext-oidcclient-common-lib</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCProviderDAO.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCProviderDAO.java b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCProviderDAO.java
new file mode 100644
index 0000000..793e461
--- /dev/null
+++ b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCProviderDAO.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.dao;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.OIDCProvider;
+
+public interface OIDCProviderDAO {
+
+ OIDCProvider find(String key);
+
+ OIDCProvider findByName(String name);
+
+ List<OIDCProvider> findAll();
+
+ OIDCProvider save(final OIDCProvider op);
+
+ void delete(String key);
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCEntityFactory.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCEntityFactory.java b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCEntityFactory.java
new file mode 100644
index 0000000..489ce86
--- /dev/null
+++ b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCEntityFactory.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity;
+
+public interface OIDCEntityFactory {
+
+ <E extends Entity> E newEntity(Class<E> reference);
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCProvider.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCProvider.java b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCProvider.java
new file mode 100644
index 0000000..b0c7dee
--- /dev/null
+++ b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCProvider.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+public interface OIDCProvider extends Entity {
+
+ String getName();
+
+ void setName(String entityID);
+
+ String getClientID();
+
+ void setClientID(String clientId);
+
+ String getClientSecret();
+
+ void setClientSecret(String clientSecret);
+
+ String getAuthorizationEndpoint();
+
+ void setAuthorizationEndpoint(String authorizationEndpoint);
+
+ String getTokenEndpoint();
+
+ void setTokenEndpoint(String tokenEndpoint);
+
+ String getJwksUri();
+
+ void setJwksUri(String jwsUri);
+
+ String getIssuer();
+
+ void setIssuer(String issuer);
+
+ String getUserinfoEndpoint();
+
+ void setUserinfoEndpoint(String userinfoEndpoint);
+
+ boolean getHasDiscovery();
+
+ void setHasDiscovery(boolean hasDiscovery);
+
+ boolean isCreateUnmatching();
+
+ void setCreateUnmatching(boolean createUnmatching);
+
+ boolean isUpdateMatching();
+
+ void setUpdateMatching(boolean updateMatching);
+
+ OIDCUserTemplate getUserTemplate();
+
+ void setUserTemplate(OIDCUserTemplate userTemplate);
+
+ List<? extends OIDCProviderItem> getItems();
+
+ Optional<? extends OIDCProviderItem> getConnObjectKeyItem();
+
+ void setConnObjectKeyItem(OIDCProviderItem item);
+
+ boolean add(OIDCProviderItem item);
+
+ Set<String> getActionsClassNames();
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCProviderItem.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCProviderItem.java b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCProviderItem.java
new file mode 100644
index 0000000..3047c88
--- /dev/null
+++ b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCProviderItem.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity;
+
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+
+public interface OIDCProviderItem extends Item {
+
+ OIDCProvider getOP();
+
+ void setOP(OIDCProvider op);
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCUserTemplate.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCUserTemplate.java b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCUserTemplate.java
new file mode 100644
index 0000000..339f6ec
--- /dev/null
+++ b/ext/oidcclient/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCUserTemplate.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity;
+
+public interface OIDCUserTemplate extends AnyTemplate {
+
+ OIDCProvider getOP();
+
+ void setOP(OIDCProvider op);
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-jpa/pom.xml
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-jpa/pom.xml b/ext/oidcclient/persistence-jpa/pom.xml
new file mode 100644
index 0000000..f642ef6
--- /dev/null
+++ b/ext/oidcclient/persistence-jpa/pom.xml
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.syncope.ext</groupId>
+ <artifactId>syncope-ext-oidcclient</artifactId>
+ <version>2.1.0-SNAPSHOT</version>
+ </parent>
+
+ <name>Apache Syncope Ext: OIDC Client Persistence JPA</name>
+ <description>Apache Syncope Ext: OIDC Client Persistence JPA</description>
+ <groupId>org.apache.syncope.ext.oidcclient</groupId>
+ <artifactId>syncope-ext-oidcclient-persistence-jpa</artifactId>
+ <packaging>jar</packaging>
+
+ <properties>
+ <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.syncope.core</groupId>
+ <artifactId>syncope-core-persistence-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.syncope.ext.oidcclient</groupId>
+ <artifactId>syncope-ext-oidcclient-persistence-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.openjpa</groupId>
+ <artifactId>openjpa-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ <dependencies>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <version>${h2.version}</version>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <persistenceXmlFile>${rootpom.basedir}/core/persistence-jpa/src/main/resources/persistence-enhance.xml</persistenceXmlFile>
+ <includes>org/apache/syncope/core/persistence/jpa/entity/**/*.class</includes>
+ <connectionDriverName>org.springframework.jdbc.datasource.DriverManagerDataSource</connectionDriverName>
+ <connectionProperties>
+ driverClassName=org.h2.Driver,
+ url=jdbc:h2:mem:syncopedb
+ username=sa,
+ password=
+ </connectionProperties>
+ </configuration>
+ <executions>
+ <execution>
+ <id>enhancer</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>enhance</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ </plugin>
+ </plugins>
+
+ <testResources>
+ <testResource>
+ <directory>${rootpom.basedir}/core/persistence-jpa/src/main/resources</directory>
+ <filtering>true</filtering>
+ </testResource>
+ </testResources>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>sqlgen</id>
+
+ <properties>
+ <skipTests>true</skipTests>
+ </properties>
+
+ <build>
+ <defaultGoal>clean verify</defaultGoal>
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.openjpa</groupId>
+ <artifactId>openjpa-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ <executions>
+ <execution>
+ <id>sqlgenr</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>sql</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ </profile>
+ </profiles>
+
+</project>
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCProviderDAO.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCProviderDAO.java b/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCProviderDAO.java
new file mode 100644
index 0000000..91e3af0
--- /dev/null
+++ b/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCProviderDAO.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.dao;
+
+import java.util.List;
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.core.persistence.api.dao.OIDCProviderDAO;
+import org.apache.syncope.core.persistence.api.entity.OIDCProvider;
+import org.apache.syncope.core.persistence.jpa.entity.JPAOIDCProvider;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+public class JPAOIDCProviderDAO extends AbstractDAO<OIDCProvider> implements OIDCProviderDAO {
+
+ @Transactional(readOnly = true)
+ @Override
+ public OIDCProvider find(final String key) {
+ return entityManager().find(JPAOIDCProvider.class, key);
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public OIDCProvider findByName(final String name) {
+ TypedQuery<OIDCProvider> query = entityManager().createQuery(
+ "SELECT e FROM " + JPAOIDCProvider.class.getSimpleName()
+ + " e WHERE e.name = :name", OIDCProvider.class);
+ query.setParameter("name", name);
+
+ OIDCProvider result = null;
+ try {
+ result = query.getSingleResult();
+ } catch (NoResultException e) {
+ LOG.debug("No OIDC Provider found with name {}", name, e);
+ }
+
+ return result;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List<OIDCProvider> findAll() {
+ TypedQuery<OIDCProvider> query = entityManager().createQuery(
+ "SELECT e FROM " + JPAOIDCProvider.class.getSimpleName() + " e", OIDCProvider.class);
+ return query.getResultList();
+ }
+
+ @Override
+ public OIDCProvider save(final OIDCProvider op) {
+ return entityManager().merge(op);
+ }
+
+ @Override
+ public void delete(final String key) {
+ OIDCProvider op = find(key);
+ if (op != null) {
+ entityManager().remove(op);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCEntityFactory.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCEntityFactory.java b/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCEntityFactory.java
new file mode 100644
index 0000000..e5870bf
--- /dev/null
+++ b/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCEntityFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.entity;
+
+import org.apache.syncope.core.persistence.api.entity.Entity;
+import org.apache.syncope.core.persistence.api.entity.OIDCEntityFactory;
+import org.apache.syncope.core.persistence.api.entity.OIDCProvider;
+import org.apache.syncope.core.persistence.api.entity.OIDCProviderItem;
+import org.apache.syncope.core.persistence.api.entity.OIDCUserTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JPAOIDCEntityFactory implements OIDCEntityFactory {
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <E extends Entity> E newEntity(final Class<E> reference) {
+ E result;
+
+ if (reference.equals(OIDCProvider.class)) {
+ result = (E) new JPAOIDCProvider();
+ } else if (reference.equals(OIDCProviderItem.class)) {
+ result = (E) new JPAOIDCProviderItem();
+ } else if (reference.equals(OIDCUserTemplate.class)) {
+ result = (E) new JPAOIDCUserTemplate();
+ } else {
+ throw new IllegalArgumentException("Could not find a JPA implementation of " + reference.getName());
+ }
+
+ return result;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/797fd1cb/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCProvider.java
----------------------------------------------------------------------
diff --git a/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCProvider.java b/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCProvider.java
new file mode 100644
index 0000000..c32f91e
--- /dev/null
+++ b/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCProvider.java
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.entity;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import javax.persistence.Cacheable;
+import javax.persistence.CascadeType;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import org.apache.syncope.core.persistence.api.entity.OIDCProvider;
+import org.apache.syncope.core.persistence.api.entity.OIDCProviderItem;
+import org.apache.syncope.core.persistence.api.entity.OIDCUserTemplate;
+
+@Entity
+@Table(name = JPAOIDCProvider.TABLE)
+@Cacheable
+public class JPAOIDCProvider extends AbstractGeneratedKeyEntity implements OIDCProvider {
+
+ public static final String TABLE = "OIDCProvider";
+
+ private static final long serialVersionUID = 1423093003585826403L;
+
+ @Column(unique = true, nullable = false)
+ private String name;
+
+ @Column(unique = true, nullable = false)
+ private String clientID;
+
+ @Column(unique = true, nullable = false)
+ private String clientSecret;
+
+ @Column(nullable = false)
+ private String authorizationEndpoint;
+
+ @Column(nullable = false)
+ private String tokenEndpoint;
+
+ @Column(nullable = false)
+ private String jwksUri;
+
+ @Column(nullable = false)
+ private String issuer;
+
+ @Column(nullable = false)
+ private String userinfoEndpoint;
+
+ @Column(nullable = false)
+ private boolean hasDiscovery;
+
+ @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "op")
+ private JPAOIDCUserTemplate userTemplate;
+
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "op")
+ private List<JPAOIDCProviderItem> items = new ArrayList<>();
+
+ @Min(0)
+ @Max(1)
+ @Column(nullable = false)
+ private Integer createUnmatching;
+
+ @Min(0)
+ @Max(1)
+ @Column(nullable = false)
+ private Integer updateMatching;
+
+ @ElementCollection(fetch = FetchType.EAGER)
+ @Column(name = "actionClassName")
+ @CollectionTable(name = TABLE + "_actionsClassNames",
+ joinColumns =
+ @JoinColumn(name = "oidcOP_id", referencedColumnName = "id"))
+ private Set<String> actionsClassNames = new HashSet<>();
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getClientID() {
+ return clientID;
+ }
+
+ @Override
+ public void setClientID(final String clientID) {
+ this.clientID = clientID;
+ }
+
+ @Override
+ public String getClientSecret() {
+ return clientSecret;
+ }
+
+ @Override
+ public void setClientSecret(final String clientSecret) {
+ this.clientSecret = clientSecret;
+ }
+
+ @Override
+ public String getAuthorizationEndpoint() {
+ return authorizationEndpoint;
+ }
+
+ @Override
+ public void setAuthorizationEndpoint(final String authorizationEndpoint) {
+ this.authorizationEndpoint = authorizationEndpoint;
+ }
+
+ @Override
+ public String getTokenEndpoint() {
+ return tokenEndpoint;
+ }
+
+ @Override
+ public void setTokenEndpoint(final String tokenEndpoint) {
+ this.tokenEndpoint = tokenEndpoint;
+ }
+
+ @Override
+ public String getJwksUri() {
+ return jwksUri;
+ }
+
+ @Override
+ public void setJwksUri(final String jwksUri) {
+ this.jwksUri = jwksUri;
+ }
+
+ @Override
+ public String getIssuer() {
+ return issuer;
+ }
+
+ @Override
+ public void setIssuer(final String issuer) {
+ this.issuer = issuer;
+ }
+
+ @Override
+ public String getUserinfoEndpoint() {
+ return userinfoEndpoint;
+ }
+
+ @Override
+ public void setUserinfoEndpoint(final String userinfoEndpoint) {
+ this.userinfoEndpoint = userinfoEndpoint;
+ }
+
+ @Override
+ public boolean getHasDiscovery() {
+ return hasDiscovery;
+ }
+
+ @Override
+ public void setHasDiscovery(final boolean hasDiscovery) {
+ this.hasDiscovery = hasDiscovery;
+ }
+
+ @Override
+ public boolean isCreateUnmatching() {
+ return isBooleanAsInteger(createUnmatching);
+ }
+
+ @Override
+ public void setCreateUnmatching(final boolean createUnmatching) {
+ this.createUnmatching = getBooleanAsInteger(createUnmatching);
+ }
+
+ @Override
+ public boolean isUpdateMatching() {
+ return isBooleanAsInteger(updateMatching);
+ }
+
+ @Override
+ public void setUpdateMatching(final boolean updateMatching) {
+ this.updateMatching = getBooleanAsInteger(updateMatching);
+ }
+
+ @Override
+ public OIDCUserTemplate getUserTemplate() {
+ return userTemplate;
+ }
+
+ @Override
+ public void setUserTemplate(final OIDCUserTemplate userTemplate) {
+ checkType(userTemplate, JPAOIDCUserTemplate.class);
+ this.userTemplate = (JPAOIDCUserTemplate) userTemplate;
+ }
+
+ @Override
+ public boolean add(final OIDCProviderItem item) {
+ checkType(item, JPAOIDCProviderItem.class);
+ return items.contains((JPAOIDCProviderItem) item) || items.add((JPAOIDCProviderItem) item);
+ }
+
+ @Override
+ public List<? extends OIDCProviderItem> getItems() {
+ return items;
+ }
+
+ @Override
+ public Optional<? extends OIDCProviderItem> getConnObjectKeyItem() {
+ return getItems().stream().filter(item -> item.isConnObjectKey()).findFirst();
+ }
+
+ @Override
+ public void setConnObjectKeyItem(final OIDCProviderItem item) {
+ item.setConnObjectKey(true);
+ this.add(item);
+ }
+
+ @Override
+ public Set<String> getActionsClassNames() {
+ return actionsClassNames;
+ }
+
+}