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 2017/03/02 10:58:40 UTC
[7/8] syncope git commit: [SYNCOPE-1035] Using JWT as authentication
mean, obtained via initial call
[SYNCOPE-1035] Using JWT as authentication mean, obtained via initial call
Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/32af0320
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/32af0320
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/32af0320
Branch: refs/heads/master
Commit: 32af0320d9d426bb34fc5a69287b5e19a0630ad3
Parents: b494537
Author: Francesco Chicchiricc� <il...@apache.org>
Authored: Tue Feb 28 17:58:39 2017 +0100
Committer: Francesco Chicchiricc� <il...@apache.org>
Committed: Thu Mar 2 11:57:57 2017 +0100
----------------------------------------------------------------------
.../syncope/client/cli/SyncopeServices.java | 2 +-
.../client/console/SyncopeConsoleSession.java | 45 ++--
.../client/console/rest/BaseRestClient.java | 11 +-
.../console/rest/ConnectorRestClient.java | 4 +-
.../client/console/rest/ResourceRestClient.java | 4 +-
.../client/enduser/SyncopeEnduserSession.java | 48 ++--
.../lib/AnonymousAuthenticationHandler.java | 30 +++
.../client/lib/AuthenticationHandler.java | 27 +++
.../client/lib/BasicAuthenticationHandler.java | 43 ++++
.../client/lib/JWTAuthenticationHandler.java | 36 +++
.../client/lib/NoAuthenticationHandler.java | 26 ++
.../client/lib/RestClientFactoryBean.java | 91 -------
.../syncope/client/lib/SyncopeClient.java | 192 ++++++++++-----
.../client/lib/SyncopeClientFactoryBean.java | 45 +++-
.../syncope/client/lib/ConcurrencyTest.java | 13 +-
.../syncope/common/lib/SyncopeConstants.java | 2 +
.../syncope/common/lib/to/AccessTokenTO.java | 88 +++++++
.../common/lib/types/StandardEntitlement.java | 4 +
.../syncope/common/rest/api/RESTHeaders.java | 2 +
.../common/rest/api/beans/AccessTokenQuery.java | 33 +++
.../rest/api/service/AccessTokenService.java | 82 +++++++
core/logic/pom.xml | 10 +
.../syncope/core/logic/AccessTokenLogic.java | 187 +++++++++++++++
.../apache/syncope/core/logic/ReportLogic.java | 3 +-
.../persistence/api/dao/AccessTokenDAO.java | 42 ++++
.../persistence/api/entity/AccessToken.java | 36 +++
.../persistence/jpa/dao/JPAAccessTokenDAO.java | 143 +++++++++++
.../core/persistence/jpa/dao/JPAUserDAO.java | 10 +
.../persistence/jpa/entity/JPAAccessToken.java | 83 +++++++
.../jpa/entity/JPAEntityFactory.java | 3 +
.../main/resources/domains/MasterContent.xml | 13 +
.../persistence/jpa/inner/MultitenancyTest.java | 2 +-
.../persistence/jpa/inner/PlainSchemaTest.java | 2 +-
.../core/persistence/jpa/inner/TaskTest.java | 2 +-
.../persistence/jpa/outer/AccessTokenTest.java | 64 +++++
.../test/resources/domains/MasterContent.xml | 12 +
.../src/test/resources/domains/TwoContent.xml | 18 ++
.../api/data/AccessTokenDataBinder.java | 33 +++
.../api/serialization/POJOHelper.java | 13 +
.../java/data/AccessTokenDataBinderImpl.java | 70 ++++++
.../java/data/UserDataBinderImpl.java | 11 +
.../java/job/ExpiredAccessTokenCleanup.java | 42 ++++
.../java/job/IdentityRecertification.java | 21 +-
.../cxf/service/AccessTokenServiceImpl.java | 73 ++++++
core/spring/pom.xml | 5 +
.../core/spring/security/AuthDataAccessor.java | 106 +++++++--
.../core/spring/security/JWTAuthentication.java | 88 +++++++
.../security/JWTAuthenticationFilter.java | 108 +++++++++
.../security/JWTAuthenticationProvider.java | 91 +++++++
.../security/SyncopeAuthenticationDetails.java | 34 +--
.../security/SyncopeAuthenticationProvider.java | 235 -------------------
.../SyncopeDigestAuthenticationEntryPoint.java | 43 ++++
.../security/SyncopeGrantedAuthority.java | 8 +-
.../UsernamePasswordAuthenticationProvider.java | 210 +++++++++++++++++
.../src/main/resources/security.properties | 4 +
.../src/main/resources/securityContext.xml | 90 ++++---
.../org/apache/syncope/fit/AbstractITCase.java | 6 +-
.../syncope/fit/core/AuthenticationITCase.java | 70 ++++--
.../apache/syncope/fit/core/GroupITCase.java | 5 +-
.../syncope/fit/core/MultitenancyITCase.java | 2 +-
.../org/apache/syncope/fit/core/RESTITCase.java | 12 +-
.../apache/syncope/fit/core/ResourceITCase.java | 4 +-
.../org/apache/syncope/fit/core/UserITCase.java | 9 +-
.../syncope/fit/core/VirSchemaITCase.java | 5 +-
pom.xml | 21 +-
.../concepts/typemanagement.adoc | 2 +-
66 files changed, 2296 insertions(+), 583 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java
----------------------------------------------------------------------
diff --git a/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java b/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java
index d534930..beb5feb 100644
--- a/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java
+++ b/client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java
@@ -53,7 +53,7 @@ public final class SyncopeServices {
setUseCompression(BooleanUtils.toBoolean(useGZIPCompression)).
create(properties.getProperty("syncope.admin.user"), syncopeAdminPassword);
- LOG.debug("Creting service for {}", clazz.getName());
+ LOG.debug("Creating service for {}", clazz.getName());
return syncopeClient.getService(clazz);
}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
index 658e52b..1ce3b71 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
@@ -37,8 +37,8 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
import org.apache.syncope.client.lib.SyncopeClient;
-import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.EntityTOUtils;
import org.apache.syncope.common.lib.to.DomainTO;
@@ -87,10 +87,6 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
private SyncopeClient client;
- private String username;
-
- private String password;
-
private UserTO selfTO;
private Map<String, Set<String>> auth;
@@ -106,9 +102,10 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
public SyncopeConsoleSession(final Request request) {
super(request);
- SyncopeClient anonymousClient = SyncopeConsoleApplication.get().getClientFactory().create(
- SyncopeConsoleApplication.get().getAnonymousUser(),
- SyncopeConsoleApplication.get().getAnonymousKey());
+ SyncopeClient anonymousClient = SyncopeConsoleApplication.get().getClientFactory().
+ create(new AnonymousAuthenticationHandler(
+ SyncopeConsoleApplication.get().getAnonymousUser(),
+ SyncopeConsoleApplication.get().getAnonymousKey()));
platformInfo = anonymousClient.getService(SyncopeService.class).platform();
systemInfo = anonymousClient.getService(SyncopeService.class).system();
@@ -134,8 +131,16 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
@Override
public void invalidate() {
+ client.logout();
+ executorService.shutdown();
super.invalidate();
+ }
+
+ @Override
+ public void invalidateNow() {
+ client.logout();
executorService.shutdownNow();
+ super.invalidateNow();
}
public PlatformInfo getPlatformInfo() {
@@ -158,6 +163,10 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
return StringUtils.isBlank(domain) ? SyncopeConstants.MASTER_DOMAIN : domain;
}
+ public String getJWT() {
+ return client.getJWT();
+ }
+
@Override
public boolean authenticate(final String username, final String password) {
boolean authenticated = false;
@@ -170,8 +179,6 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
auth = self.getKey();
selfTO = self.getValue();
- this.username = username;
- this.password = password;
authenticated = true;
} catch (Exception e) {
LOG.error("Authentication failed", e);
@@ -199,7 +206,7 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
}
public void refreshAuth() {
- authenticate(username, password);
+ client.refresh();
roles = null;
}
@@ -213,8 +220,7 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
services.put(serviceClass, service);
}
- WebClient.client(service).
- type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
+ WebClient.client(service).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
return service;
}
@@ -231,18 +237,9 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
}
public <T> T getService(final MediaType mediaType, final Class<T> serviceClass) {
- T service;
+ T service = client.getService(serviceClass);
- synchronized (SyncopeConsoleApplication.get().getClientFactory()) {
- SyncopeClientFactoryBean.ContentType preType = SyncopeConsoleApplication.get().getClientFactory().
- getContentType();
-
- SyncopeConsoleApplication.get().getClientFactory().
- setContentType(SyncopeClientFactoryBean.ContentType.fromString(mediaType.toString()));
- service = SyncopeConsoleApplication.get().getClientFactory().
- create(username, password).getService(serviceClass);
- SyncopeConsoleApplication.get().getClientFactory().setContentType(preType);
- }
+ WebClient.client(service).type(mediaType).accept(mediaType);
return service;
}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java
index 9eaa65d..d2c73f1 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java
@@ -24,6 +24,7 @@ import org.apache.syncope.client.console.SyncopeConsoleApplication;
import org.apache.syncope.client.console.SyncopeConsoleSession;
import org.apache.syncope.client.lib.SyncopeClient;
import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
+import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.service.JAXRSService;
import org.apache.syncope.common.rest.api.service.SyncopeService;
import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -69,10 +70,14 @@ public abstract class BaseRestClient implements RestClient {
return builder.build();
}
- protected static <E extends JAXRSService, T> T getObject(final E service, final URI location,
- final Class<T> resultClass) {
+ protected static <E extends JAXRSService, T> T getObject(
+ final E service, final URI location, final Class<T> resultClass) {
+
WebClient webClient = WebClient.fromClient(WebClient.client(service));
webClient.accept(SyncopeConsoleApplication.get().getMediaType()).to(location.toASCIIString(), false);
- return webClient.get(resultClass);
+ return webClient.
+ header(RESTHeaders.DOMAIN, SyncopeConsoleSession.get().getDomain()).
+ header(RESTHeaders.TOKEN, SyncopeConsoleSession.get().getJWT()).
+ get(resultClass);
}
}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
index e506a77..7ac3fcc 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
@@ -58,8 +58,8 @@ public class ConnectorRestClient extends BaseRestClient {
connectorTO.getConf().clear();
connectorTO.getConf().addAll(filteredConf);
- final ConnectorService service = getService(ConnectorService.class);
- final Response response = service.create(connectorTO);
+ ConnectorService service = getService(ConnectorService.class);
+ Response response = service.create(connectorTO);
return getObject(service, response.getLocation(), ConnInstanceTO.class);
}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
index 46e7d3e..7ba375d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
@@ -104,8 +104,8 @@ public class ResourceRestClient extends BaseRestClient {
}
public ResourceTO create(final ResourceTO resourceTO) {
- final ResourceService service = getService(ResourceService.class);
- final Response response = service.create(resourceTO);
+ ResourceService service = getService(ResourceService.class);
+ Response response = service.create(resourceTO);
return getObject(service, response.getLocation(), ResourceTO.class);
}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
index cd2fe24..7c1af3b 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
@@ -18,9 +18,7 @@
*/
package org.apache.syncope.client.enduser;
-import java.text.DateFormat;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.EntityTag;
@@ -29,6 +27,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
import org.apache.syncope.client.lib.SyncopeClient;
import org.apache.syncope.common.lib.info.PlatformInfo;
import org.apache.syncope.common.lib.to.PlainSchemaTO;
@@ -58,10 +57,6 @@ public class SyncopeEnduserSession extends WebSession {
private SyncopeClient client;
- private String username;
-
- private String password;
-
private final PlatformInfo platformInfo;
private final List<PlainSchemaTO> datePlainSchemas;
@@ -81,9 +76,10 @@ public class SyncopeEnduserSession extends WebSession {
// define cookie utility to manage application cookies
cookieUtils = new CookieUtils();
- anonymousClient = SyncopeEnduserApplication.get().getClientFactory().create(
- SyncopeEnduserApplication.get().getAnonymousUser(),
- SyncopeEnduserApplication.get().getAnonymousKey());
+ anonymousClient = SyncopeEnduserApplication.get().getClientFactory().
+ create(new AnonymousAuthenticationHandler(
+ SyncopeEnduserApplication.get().getAnonymousUser(),
+ SyncopeEnduserApplication.get().getAnonymousKey()));
platformInfo = anonymousClient.getService(SyncopeService.class).platform();
datePlainSchemas = anonymousClient.getService(SchemaService.class).
@@ -108,10 +104,8 @@ public class SyncopeEnduserSession extends WebSession {
Pair<Map<String, Set<String>>, UserTO> self = client.self();
selfTO = self.getValue();
- this.username = username;
- this.password = password;
- // bind explicitly this session to have a stateful behavior during http requests, unless session will expire
- // for every request
+ // bind explicitly this session to have a stateful behavior during http requests, unless session will
+ // expire for every request
this.bind();
authenticated = true;
} catch (Exception e) {
@@ -135,14 +129,6 @@ public class SyncopeEnduserSession extends WebSession {
return serviceInstance;
}
- public String getUsername() {
- return username;
- }
-
- public String getPassword() {
- return password;
- }
-
public PlatformInfo getPlatformInfo() {
return platformInfo;
}
@@ -156,13 +142,7 @@ public class SyncopeEnduserSession extends WebSession {
}
public boolean isAuthenticated() {
- return getUsername() != null;
- }
-
- public DateFormat getDateFormat() {
- final Locale locale = getLocale() == null ? Locale.ENGLISH : getLocale();
-
- return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);
+ return client.getJWT() != null;
}
public CookieUtils getCookieUtils() {
@@ -177,4 +157,16 @@ public class SyncopeEnduserSession extends WebSession {
this.xsrfTokenGenerated = xsrfTokenGenerated;
}
+ @Override
+ public void invalidate() {
+ client.logout();
+ super.invalidate();
+ }
+
+ @Override
+ public void invalidateNow() {
+ client.logout();
+ super.invalidateNow();
+ }
+
}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
new file mode 100644
index 0000000..b34be66
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
@@ -0,0 +1,30 @@
+/*
+ * 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.client.lib;
+
+/**
+ * Implementation providing Basic Authentication capability for the special {@code anonymous} user.
+ */
+public class AnonymousAuthenticationHandler extends BasicAuthenticationHandler implements AuthenticationHandler {
+
+ public AnonymousAuthenticationHandler(final String username, final String password) {
+ super(username, password);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/lib/src/main/java/org/apache/syncope/client/lib/AuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/AuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/AuthenticationHandler.java
new file mode 100644
index 0000000..d30de3d
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/AuthenticationHandler.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.client.lib;
+
+/**
+ * Marker interface for usage with
+ * {@link SyncopeClientFactoryBean#create(org.apache.syncope.client.lib.AuthenticationHandler)}.
+ */
+public interface AuthenticationHandler {
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
new file mode 100644
index 0000000..ff1452e
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.client.lib;
+
+/**
+ * Implementation providing Basic Authentication capability.
+ */
+public class BasicAuthenticationHandler implements AuthenticationHandler {
+
+ private final String username;
+
+ private final String password;
+
+ public BasicAuthenticationHandler(final String username, final String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/lib/src/main/java/org/apache/syncope/client/lib/JWTAuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/JWTAuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/JWTAuthenticationHandler.java
new file mode 100644
index 0000000..b13ec2e
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/JWTAuthenticationHandler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.client.lib;
+
+/**
+ * Implementation providing JSON Web Token authentication capability.
+ */
+public class JWTAuthenticationHandler implements AuthenticationHandler {
+
+ private final String jwt;
+
+ public JWTAuthenticationHandler(final String jwtToken) {
+ this.jwt = jwtToken;
+ }
+
+ public String getJwt() {
+ return jwt;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/lib/src/main/java/org/apache/syncope/client/lib/NoAuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/NoAuthenticationHandler.java b/client/lib/src/main/java/org/apache/syncope/client/lib/NoAuthenticationHandler.java
new file mode 100644
index 0000000..6ad2b1f
--- /dev/null
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/NoAuthenticationHandler.java
@@ -0,0 +1,26 @@
+/*
+ * 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.client.lib;
+
+/**
+ * Empty implementation not providing any real authentication capability.
+ */
+public class NoAuthenticationHandler implements AuthenticationHandler {
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java b/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java
deleted file mode 100644
index c126deb..0000000
--- a/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.client.lib;
-
-import javax.ws.rs.core.MediaType;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.cxf.jaxrs.client.Client;
-import org.apache.cxf.jaxrs.client.ClientConfiguration;
-import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
-import org.apache.cxf.jaxrs.client.WebClient;
-import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
-import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
-import org.apache.cxf.transport.http.URLConnectionHTTPConduit;
-
-/**
- * Provides shortcuts for creating JAX-RS service instances via CXF's {@link JAXRSClientFactoryBean}.
- */
-public class RestClientFactoryBean extends JAXRSClientFactoryBean {
-
- public static final String HEADER_SPLIT_PROPERTY = "org.apache.cxf.http.header.split";
-
- /**
- * Creates an anonymous instance of the given service class, for the given content type.
- *
- * @param <T> any service class
- * @param serviceClass service class reference
- * @param mediaType XML or JSON are supported
- * @return anonymous service instance of the given reference class
- */
- public <T> T createServiceInstance(final Class<T> serviceClass, final MediaType mediaType) {
- return createServiceInstance(serviceClass, mediaType, null, null, false);
- }
-
- /**
- * Creates an authenticated instance of the given service class, for the given content type.
- *
- * @param <T> any service class
- * @param serviceClass service class reference
- * @param mediaType XML or JSON are supported
- * @param username username for REST authentication
- * @param password password for REST authentication
- * @param useCompression whether transparent gzip <tt>Content-Encoding</tt> handling is to be enabled
- * @return anonymous service instance of the given reference class
- */
- public <T> T createServiceInstance(
- final Class<T> serviceClass,
- final MediaType mediaType,
- final String username,
- final String password,
- final boolean useCompression) {
-
- if (StringUtils.isNotBlank(username)) {
- setUsername(username);
- }
- if (StringUtils.isNotBlank(password)) {
- setPassword(password);
- }
-
- setServiceClass(serviceClass);
- T serviceInstance = create(serviceClass);
-
- Client client = WebClient.client(serviceInstance);
- client.type(mediaType).accept(mediaType);
-
- ClientConfiguration config = WebClient.getConfig(client);
- config.getRequestContext().put(HEADER_SPLIT_PROPERTY, true);
- config.getRequestContext().put(URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION, true);
- if (useCompression) {
- config.getInInterceptors().add(new GZIPInInterceptor());
- config.getOutInterceptors().add(new GZIPOutInterceptor());
- }
-
- return serviceInstance;
- }
-}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
index 7c61b10..a566419 100644
--- a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
@@ -21,7 +21,9 @@ package org.apache.syncope.client.lib;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.EntityTag;
@@ -29,7 +31,14 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cxf.jaxrs.client.Client;
+import org.apache.cxf.jaxrs.client.ClientConfiguration;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
+import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
+import org.apache.cxf.transport.http.URLConnectionHTTPConduit;
+import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.search.AnyObjectFiqlSearchConditionBuilder;
import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder;
@@ -37,6 +46,7 @@ import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.rest.api.Preference;
import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.AccessTokenService;
import org.apache.syncope.common.rest.api.service.UserSelfService;
/**
@@ -45,34 +55,99 @@ import org.apache.syncope.common.rest.api.service.UserSelfService;
*/
public class SyncopeClient {
+ private static final String HEADER_SPLIT_PROPERTY = "org.apache.cxf.http.header.split";
+
private final MediaType mediaType;
- private final RestClientFactoryBean restClientFactory;
+ private final JAXRSClientFactoryBean restClientFactory;
private final RestClientExceptionMapper exceptionMapper;
- private final String username;
-
- private final String password;
-
private final boolean useCompression;
public SyncopeClient(
final MediaType mediaType,
- final RestClientFactoryBean restClientFactory,
+ final JAXRSClientFactoryBean restClientFactory,
final RestClientExceptionMapper exceptionMapper,
- final String username, final String password,
+ final AuthenticationHandler handler,
final boolean useCompression) {
this.mediaType = mediaType;
this.restClientFactory = restClientFactory;
+ if (this.restClientFactory.getHeaders() == null) {
+ this.restClientFactory.setHeaders(new HashMap<String, String>());
+ }
this.exceptionMapper = exceptionMapper;
- this.username = username;
- this.password = password;
+ init(handler);
this.useCompression = useCompression;
}
/**
+ * Initializes the provided {@code restClientFactory} with the authentication capabilities of the provided
+ * {@code handler}.
+ *
+ * Currently supports:
+ * <ul>
+ * <li>{@link JWTAuthenticationHandler}</li>
+ * <li>{@link AnonymousAuthenticationHandler}</li>
+ * <li>{@link BasicAuthenticationHandler}</li>
+ * </ul>
+ * More can be supported by subclasses.
+ *
+ * @param handler authentication handler
+ */
+ protected void init(final AuthenticationHandler handler) {
+ cleanup();
+
+ if (handler instanceof AnonymousAuthenticationHandler) {
+ restClientFactory.setUsername(((AnonymousAuthenticationHandler) handler).getUsername());
+ restClientFactory.setPassword(((AnonymousAuthenticationHandler) handler).getPassword());
+ } else if (handler instanceof BasicAuthenticationHandler) {
+ restClientFactory.setUsername(((BasicAuthenticationHandler) handler).getUsername());
+ restClientFactory.setPassword(((BasicAuthenticationHandler) handler).getPassword());
+
+ String jwt = getService(AccessTokenService.class).login().getHeaderString(RESTHeaders.TOKEN);
+ restClientFactory.getHeaders().put(RESTHeaders.TOKEN, Collections.singletonList(jwt));
+
+ restClientFactory.setUsername(null);
+ restClientFactory.setPassword(null);
+ } else if (handler instanceof JWTAuthenticationHandler) {
+ restClientFactory.getHeaders().put(
+ RESTHeaders.TOKEN, Collections.singletonList(((JWTAuthenticationHandler) handler).getJwt()));
+ }
+ }
+
+ protected void cleanup() {
+ restClientFactory.getHeaders().remove(RESTHeaders.TOKEN);
+ restClientFactory.setUsername(null);
+ restClientFactory.setPassword(null);
+ }
+
+ /**
+ * Attempts to extend the lifespan of the JWT currently in use.
+ */
+ public void refresh() {
+ getService(AccessTokenService.class).refresh();
+ }
+
+ /**
+ * Invalidates the JWT currently in use.
+ */
+ public void logout() {
+ getService(AccessTokenService.class).logout();
+ cleanup();
+ }
+
+ /**
+ * (Re)initializes the current instance with the authentication capabilities of the provided {@code handler}.
+ *
+ * @param handler authentication handler
+ */
+ public void login(final AuthenticationHandler handler) {
+ init(handler);
+ }
+
+ /**
* Returns a new instance of {@link UserFiqlSearchConditionBuilder}, for assisted building of FIQL queries.
*
* @return default instance of {@link UserFiqlSearchConditionBuilder}
@@ -110,6 +185,31 @@ public class SyncopeClient {
}
/**
+ * Returns the JWT in used by this instance, passed with the {@link RESTHeaders#TOKEN} header in all requests.
+ * It can be null (in case {@link NoAuthenticationHandler} or {@link AnonymousAuthenticationHandler} were used).
+ *
+ * @return the JWT in used by this instance
+ */
+ public String getJWT() {
+ List<String> headerValues = restClientFactory.getHeaders().get(RESTHeaders.TOKEN);
+ return headerValues == null || headerValues.isEmpty()
+ ? null
+ : headerValues.get(0);
+ }
+
+ /**
+ * Returns the domain configured for this instance, or {@link SyncopeConstants#MASTER_DOMAIN} if not set.
+ *
+ * @return the domain configured for this instance
+ */
+ public String getDomain() {
+ List<String> headerValues = restClientFactory.getHeaders().get(RESTHeaders.DOMAIN);
+ return headerValues == null || headerValues.isEmpty()
+ ? SyncopeConstants.MASTER_DOMAIN
+ : headerValues.get(0);
+ }
+
+ /**
* Creates an instance of the given service class, with configured content type and authentication.
*
* @param <T> any service class
@@ -118,7 +218,21 @@ public class SyncopeClient {
*/
public <T> T getService(final Class<T> serviceClass) {
synchronized (restClientFactory) {
- return restClientFactory.createServiceInstance(serviceClass, mediaType, username, password, useCompression);
+ restClientFactory.setServiceClass(serviceClass);
+ T serviceInstance = restClientFactory.create(serviceClass);
+
+ Client client = WebClient.client(serviceInstance);
+ client.type(mediaType).accept(mediaType);
+
+ ClientConfiguration config = WebClient.getConfig(client);
+ config.getRequestContext().put(HEADER_SPLIT_PROPERTY, true);
+ config.getRequestContext().put(URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION, true);
+ if (useCompression) {
+ config.getInInterceptors().add(new GZIPInInterceptor());
+ config.getOutInterceptors().add(new GZIPOutInterceptor());
+ }
+
+ return serviceInstance;
}
}
@@ -126,8 +240,7 @@ public class SyncopeClient {
public Pair<Map<String, Set<String>>, UserTO> self() {
// Explicitly disable header value split because it interferes with JSON deserialization below
UserSelfService service = getService(UserSelfService.class);
- WebClient.getConfig(WebClient.client(service)).
- getRequestContext().put(RestClientFactoryBean.HEADER_SPLIT_PROPERTY, false);
+ WebClient.getConfig(WebClient.client(service)).getRequestContext().put(HEADER_SPLIT_PROPERTY, false);
Response response = service.read();
if (response.getStatusInfo().getStatusCode() != Response.Status.OK.getStatusCode()) {
@@ -164,19 +277,6 @@ public class SyncopeClient {
}
/**
- * Creates an instance of the given service class and sets the given header.
- *
- * @param <T> any service class
- * @param serviceClass service class reference
- * @param key HTTP header key
- * @param values HTTP header values
- * @return service instance of the given reference class, with given header set
- */
- public <T> T header(final Class<T> serviceClass, final String key, final Object... values) {
- return header(getService(serviceClass), key, values);
- }
-
- /**
* Sets the {@code Prefer} header on the give service instance.
*
* @param <T> any service class
@@ -189,28 +289,16 @@ public class SyncopeClient {
}
/**
- * Creates an instance of the given service class, with {@code Prefer} header set.
- *
- * @param <T> any service class
- * @param serviceClass service class reference
- * @param preference preference to be set via {@code Prefer} header
- * @return service instance of the given reference class, with {@code Prefer} header set
- */
- public <T> T prefer(final Class<T> serviceClass, final Preference preference) {
- return header(serviceClass, RESTHeaders.PREFER, preference.toString());
- }
-
- /**
* Asks for asynchronous propagation towards external resources with null priority.
*
* @param <T> any service class
- * @param serviceClass service class reference
+ * @param service service class instance
* @param nullPriorityAsync whether asynchronous propagation towards external resources with null priority is
* requested
* @return service instance of the given reference class, with related header set
*/
- public <T> T nullPriorityAsync(final Class<T> serviceClass, final boolean nullPriorityAsync) {
- return header(serviceClass, RESTHeaders.NULL_PRIORITY_ASYNC, nullPriorityAsync);
+ public <T> T nullPriorityAsync(final T service, final boolean nullPriorityAsync) {
+ return header(service, RESTHeaders.NULL_PRIORITY_ASYNC, nullPriorityAsync);
}
/**
@@ -240,18 +328,6 @@ public class SyncopeClient {
}
/**
- * Creates an instance of the given service class, with {@code If-Match} header set.
- *
- * @param <T> any service class
- * @param serviceClass service class reference
- * @param etag ETag value
- * @return given service instance, with {@code If-Match} set
- */
- public <T> T ifMatch(final Class<T> serviceClass, final EntityTag etag) {
- return match(getService(serviceClass), etag, false);
- }
-
- /**
* Sets the {@code If-None-Match} header on the given service instance.
*
* @param <T> any service class
@@ -264,18 +340,6 @@ public class SyncopeClient {
}
/**
- * Creates an instance of the given service class, with {@code If-None-Match} header set.
- *
- * @param <T> any service class
- * @param serviceClass service class reference
- * @param etag ETag value
- * @return given service instance, with {@code If-None-Match} set
- */
- public <T> T ifNoneMatch(final Class<T> serviceClass, final EntityTag etag) {
- return match(getService(serviceClass), etag, true);
- }
-
- /**
* Fetches {@code ETag} header value from latest service run (if available).
*
* @param <T> any service class
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
----------------------------------------------------------------------
diff --git a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
index 1a30d54..45e6ffa 100644
--- a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
+++ b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
@@ -31,6 +31,7 @@ import javax.xml.bind.Marshaller;
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.ext.logging.LoggingFeature;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
import org.apache.cxf.jaxrs.provider.JAXBElementProvider;
import org.apache.cxf.staxutils.DocumentDepthProperties;
import org.apache.syncope.common.lib.policy.AbstractPolicyTO;
@@ -79,7 +80,7 @@ public class SyncopeClientFactoryBean {
private boolean useCompression;
- private RestClientFactoryBean restClientFactoryBean;
+ private JAXRSClientFactoryBean restClientFactoryBean;
protected JacksonJaxbJsonProvider defaultJsonProvider() {
ObjectMapper objectMapper = new ObjectMapper();
@@ -111,8 +112,9 @@ public class SyncopeClientFactoryBean {
return new RestClientExceptionMapper();
}
- protected RestClientFactoryBean defaultRestClientFactoryBean() {
- RestClientFactoryBean defaultRestClientFactoryBean = new RestClientFactoryBean();
+ protected JAXRSClientFactoryBean defaultRestClientFactoryBean() {
+ JAXRSClientFactoryBean defaultRestClientFactoryBean = new JAXRSClientFactoryBean();
+ defaultRestClientFactoryBean.setHeaders(new HashMap<String, String>());
if (StringUtils.isBlank(address)) {
throw new IllegalArgumentException("Property 'address' is missing");
@@ -120,7 +122,7 @@ public class SyncopeClientFactoryBean {
defaultRestClientFactoryBean.setAddress(address);
if (StringUtils.isNotBlank(domain)) {
- defaultRestClientFactoryBean.setHeaders(Collections.singletonMap(RESTHeaders.DOMAIN, domain));
+ defaultRestClientFactoryBean.getHeaders().put(RESTHeaders.DOMAIN, Collections.singletonList(domain));
}
defaultRestClientFactoryBean.setThreadSafe(true);
@@ -221,41 +223,62 @@ public class SyncopeClientFactoryBean {
return useCompression;
}
- public RestClientFactoryBean getRestClientFactoryBean() {
+ public JAXRSClientFactoryBean getRestClientFactoryBean() {
return restClientFactoryBean == null
? defaultRestClientFactoryBean()
: restClientFactoryBean;
}
- public SyncopeClientFactoryBean setRestClientFactoryBean(final RestClientFactoryBean restClientFactoryBean) {
+ public SyncopeClientFactoryBean setRestClientFactoryBean(final JAXRSClientFactoryBean restClientFactoryBean) {
this.restClientFactoryBean = restClientFactoryBean;
return this;
}
/**
- * Builds client instance with no authentication, for user self-registration and related queries (schema,
- * resources, ...).
+ * Builds client instance with no authentication, for user self-registration and password reset.
*
* @return client instance with no authentication
*/
public SyncopeClient create() {
- return create(null, null);
+ return create(new NoAuthenticationHandler());
}
/**
* Builds client instance with the given credentials.
+ * Such credentials will be used only to obtain a valid JWT in the {@link RESTHeaders#TOKEN} header;
*
* @param username username
* @param password password
* @return client instance with the given credentials
*/
public SyncopeClient create(final String username, final String password) {
+ return create(new BasicAuthenticationHandler(username, password));
+ }
+
+ /**
+ * Builds client instance which will be passing the provided value in the {@link RESTHeaders#TOKEN}
+ * request header.
+ *
+ * @param jwt value received after login, in the {@link RESTHeaders#TOKEN} response header
+ * @return client instance which will be passing the provided value in the {{@link RESTHeaders#TOKEN}
+ * request header
+ */
+ public SyncopeClient create(final String jwt) {
+ return create(new JWTAuthenticationHandler(jwt));
+ }
+
+ /**
+ * Builds client instance with the given authentication handler.
+ *
+ * @param handler authentication handler
+ * @return client instance with the given authentication handler
+ */
+ public SyncopeClient create(final AuthenticationHandler handler) {
return new SyncopeClient(
getContentType().getMediaType(),
getRestClientFactoryBean(),
getExceptionMapper(),
- username,
- password,
+ handler,
useCompression);
}
}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java
----------------------------------------------------------------------
diff --git a/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java b/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java
index a20cb59..583109f 100644
--- a/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java
+++ b/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java
@@ -32,8 +32,7 @@ public class ConcurrencyTest {
private static final int THREAD_NUMBER = 1000;
- private static final SyncopeClient client =
- new SyncopeClientFactoryBean().setAddress("http://url").create("username", "password");
+ private static final SyncopeClient client = new SyncopeClientFactoryBean().setAddress("http://url").create();
@Test
public void multiThreadTest()
@@ -54,11 +53,11 @@ public class ConcurrencyTest {
}
}
};
- try {
- execution.start();
- } catch(OutOfMemoryError e) {
- // ignore
- }
+ try {
+ execution.start();
+ } catch (OutOfMemoryError e) {
+ // ignore
+ }
}
Thread.sleep(THREAD_NUMBER);
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java b/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
index 8d80f6d..5ab80d2 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/SyncopeConstants.java
@@ -29,6 +29,8 @@ public final class SyncopeConstants {
public static final String NS = "http://syncope.apache.org/2.0";
+ public static final String JWT_CLAIM_REMOTE_HOST = "remoteHost";
+
public static final String MASTER_DOMAIN = "Master";
public static final String ROOT_REALM = "/";
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
new file mode 100644
index 0000000..c172e88
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
@@ -0,0 +1,88 @@
+/*
+ * 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.common.lib.to;
+
+import java.util.Date;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "accessToken")
+@XmlType
+public class AccessTokenTO extends AbstractBaseBean implements EntityTO {
+
+ private static final long serialVersionUID = 6577639976115661357L;
+
+ private String key;
+
+ private String body;
+
+ private Date expiryTime;
+
+ private String owner;
+
+ private String authorities;
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public void setKey(final String key) {
+ this.key = key;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public void setBody(final String body) {
+ this.body = body;
+ }
+
+ public Date getExpiryTime() {
+ return expiryTime == null
+ ? null
+ : new Date(expiryTime.getTime());
+ }
+
+ public void setExpiryTime(final Date expiryTime) {
+ this.expiryTime = expiryTime == null
+ ? null
+ : new Date(expiryTime.getTime());
+ }
+
+ public String getOwner() {
+ return owner;
+ }
+
+ public void setOwner(final String owner) {
+ this.owner = owner;
+ }
+
+ public String getAuthorities() {
+ return authorities;
+ }
+
+ public void setAuthorities(final String authorities) {
+ this.authorities = authorities;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
index 74c59b7..8f70ba1 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
@@ -240,6 +240,10 @@ public final class StandardEntitlement {
public static final String SECURITY_QUESTION_DELETE = "SECURITY_QUESTION_DELETE";
+ public static final String ACCESS_TOKEN_LIST = "TASK_LIST";
+
+ public static final String ACCESS_TOKEN_DELETE = "TASK_DELETE";
+
private static final Set<String> VALUES;
static {
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
index 9312543..0c54116 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
@@ -25,6 +25,8 @@ public final class RESTHeaders {
public static final String DOMAIN = "X-Syncope-Domain";
+ public static final String TOKEN = "X-Syncope-Token";
+
public static final String OWNED_ENTITLEMENTS = "X-Syncope-Entitlements";
public static final String RESOURCE_KEY = "X-Syncope-Key";
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AccessTokenQuery.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AccessTokenQuery.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AccessTokenQuery.java
new file mode 100644
index 0000000..abdea3f
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AccessTokenQuery.java
@@ -0,0 +1,33 @@
+/*
+ * 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.common.rest.api.beans;
+
+public class AccessTokenQuery extends AbstractQuery {
+
+ private static final long serialVersionUID = -8792519310029596796L;
+
+ public static class Builder extends AbstractQuery.Builder<AccessTokenQuery, Builder> {
+
+ @Override
+ protected AccessTokenQuery newInstance() {
+ return new AccessTokenQuery();
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AccessTokenService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AccessTokenService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AccessTokenService.java
new file mode 100644
index 0000000..e9f5ff3
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AccessTokenService.java
@@ -0,0 +1,82 @@
+/*
+ * 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.common.rest.api.service;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.AccessTokenTO;
+import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.rest.api.beans.AccessTokenQuery;
+
+/**
+ * REST operations for access tokens.
+ */
+@Path("accessTokens")
+public interface AccessTokenService extends JAXRSService {
+
+ /**
+ * Returns an empty response bearing the X-Syncope-Token header value, in case of successful authentication.
+ * The provided value is a signed JSON Web Token.
+ *
+ * @return empty response bearing the X-Syncope-Token header value, in case of successful authentication
+ */
+ @POST
+ @Path("login")
+ Response login();
+
+ /**
+ * Returns an empty response bearing the X-Syncope-Token header value, with extended lifetime.
+ * The provided value is a signed JSON Web Token.
+ *
+ * @return an empty response bearing the X-Syncope-Token header value, with extended lifetime
+ */
+ @POST
+ @Path("refresh")
+ Response refresh();
+
+ /**
+ * Invalidates the access token of the requesting user.
+ */
+ @POST
+ @Path("logout")
+ void logout();
+
+ /**
+ * Returns a paged list of existing access tokens matching the given query.
+ *
+ * @param query query conditions
+ * @return paged list of existing access tokens matching the given query
+ */
+ @GET
+ PagedResult<AccessTokenTO> list(@BeanParam AccessTokenQuery query);
+
+ /**
+ * Invalidates the access token matching the provided key.
+ *
+ * @param key access token key
+ */
+ @DELETE
+ @Path("{key}")
+ void delete(@PathParam("key") String key);
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/logic/pom.xml
----------------------------------------------------------------------
diff --git a/core/logic/pom.xml b/core/logic/pom.xml
index 58d1f81..8442b9c 100644
--- a/core/logic/pom.xml
+++ b/core/logic/pom.xml
@@ -39,6 +39,16 @@ under the License.
<dependencies>
<dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-security-jose</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.uuid</groupId>
+ <artifactId>java-uuid-generator</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
new file mode 100644
index 0000000..6ca1b87
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
@@ -0,0 +1,187 @@
+/*
+ * 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.uuid.Generators;
+import com.fasterxml.uuid.impl.RandomBasedGenerator;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.Resource;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+import org.apache.cxf.rs.security.jose.common.JoseType;
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
+import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
+import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactProducer;
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.cxf.rs.security.jose.jwt.JwtToken;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.AccessTokenTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.dao.ConfDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.provisioning.api.data.AccessTokenDataBinder;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AccessTokenLogic extends AbstractTransactionalLogic<AccessTokenTO> {
+
+ private static final RandomBasedGenerator UUID_GENERATOR = Generators.randomBasedGenerator();
+
+ private static final JwsHeaders JWS_HEADERS = new JwsHeaders(JoseType.JWT, SignatureAlgorithm.HS512);
+
+ @Resource(name = "jwtIssuer")
+ private String jwtIssuer;
+
+ @Resource(name = "anonymousUser")
+ private String anonymousUser;
+
+ @Autowired
+ private JwsSignatureProvider jwsSignatureProvider;
+
+ @Autowired
+ private AccessTokenDataBinder binder;
+
+ @Autowired
+ private AccessTokenDAO accessTokenDAO;
+
+ @Autowired
+ private ConfDAO confDAO;
+
+ @PreAuthorize("isAuthenticated()")
+ public String login(final String remoteHost) {
+ if (anonymousUser.equals(AuthContextUtils.getUsername())) {
+ throw new IllegalArgumentException(anonymousUser + " cannot be granted for an access token");
+ }
+
+ String body = null;
+
+ AccessToken accessToken = accessTokenDAO.findByOwner(AuthContextUtils.getUsername());
+ if (accessToken != null) {
+ body = accessToken.getBody();
+ }
+
+ if (body == null) {
+ Date now = new Date();
+ Calendar expiry = Calendar.getInstance();
+ expiry.setTime(now);
+ expiry.add(Calendar.MINUTE,
+ confDAO.find("jwt.lifetime.minutes", "120").getValues().get(0).getLongValue().intValue());
+
+ JwtClaims claims = new JwtClaims();
+ claims.setTokenId(UUID_GENERATOR.generate().toString());
+ claims.setSubject(AuthContextUtils.getUsername());
+ claims.setIssuedAt(now.getTime());
+ claims.setIssuer(jwtIssuer);
+ claims.setExpiryTime(expiry.getTime().getTime());
+ claims.setNotBefore(now.getTime());
+ claims.setClaim(SyncopeConstants.JWT_CLAIM_REMOTE_HOST, remoteHost);
+
+ JwtToken token = new JwtToken(JWS_HEADERS, claims);
+ JwsJwtCompactProducer producer = new JwsJwtCompactProducer(token);
+
+ body = producer.signWith(jwsSignatureProvider);
+
+ binder.create(claims.getTokenId(), body, expiry.getTime());
+ }
+
+ return body;
+ }
+
+ @PreAuthorize("isAuthenticated()")
+ public String refresh() {
+ AccessToken accessToken = accessTokenDAO.findByOwner(AuthContextUtils.getUsername());
+ if (accessToken == null) {
+ throw new NotFoundException("AccessToken for " + AuthContextUtils.getUsername());
+ }
+
+ JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(accessToken.getBody());
+
+ Date now = new Date();
+ Calendar expiry = Calendar.getInstance();
+ expiry.setTime(now);
+ expiry.add(Calendar.MINUTE,
+ confDAO.find("jwt.lifetime.minutes", "120").getValues().get(0).getLongValue().intValue());
+ consumer.getJwtClaims().setExpiryTime(expiry.getTime().getTime());
+
+ JwtToken token = new JwtToken(JWS_HEADERS, consumer.getJwtClaims());
+ JwsJwtCompactProducer producer = new JwsJwtCompactProducer(token);
+
+ String body = producer.signWith(jwsSignatureProvider);
+
+ binder.update(accessToken, body, expiry.getTime());
+
+ return body;
+ }
+
+ @PreAuthorize("isAuthenticated()")
+ public void logout() {
+ AccessToken accessToken = accessTokenDAO.findByOwner(AuthContextUtils.getUsername());
+ if (accessToken == null) {
+ throw new NotFoundException("AccessToken for " + AuthContextUtils.getUsername());
+ }
+
+ delete(accessToken.getKey());
+ }
+
+ @PreAuthorize("hasRole('" + StandardEntitlement.ACCESS_TOKEN_LIST + "')")
+ public int count() {
+ return accessTokenDAO.count();
+ }
+
+ @PreAuthorize("hasRole('" + StandardEntitlement.ACCESS_TOKEN_LIST + "')")
+ public List<AccessTokenTO> list(
+ final int page,
+ final int size,
+ final List<OrderByClause> orderByClauses) {
+
+ return CollectionUtils.collect(accessTokenDAO.findAll(page, size, orderByClauses),
+ new Transformer<AccessToken, AccessTokenTO>() {
+
+ @Override
+ public AccessTokenTO transform(final AccessToken input) {
+ return binder.getAccessTokenTO(input);
+ }
+ }, new ArrayList<AccessTokenTO>());
+ }
+
+ @PreAuthorize("hasRole('" + StandardEntitlement.ACCESS_TOKEN_DELETE + "')")
+ public void delete(final String key) {
+ accessTokenDAO.delete(key);
+ }
+
+ @Override
+ protected AccessTokenTO resolveReference(final Method method, final Object... args)
+ throws UnresolvedReferenceException {
+
+ throw new UnresolvedReferenceException();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
index 033dfa3..af8a29f 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
@@ -137,8 +137,7 @@ public class ReportLogic extends AbstractExecutableLogic<ReportTO> {
@PreAuthorize("hasRole('" + StandardEntitlement.REPORT_LIST + "')")
public List<ReportTO> list() {
- return CollectionUtils.collect(reportDAO.findAll(),
- new Transformer<Report, ReportTO>() {
+ return CollectionUtils.collect(reportDAO.findAll(), new Transformer<Report, ReportTO>() {
@Override
public ReportTO transform(final Report input) {
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccessTokenDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccessTokenDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccessTokenDAO.java
new file mode 100644
index 0000000..ef07ee6
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccessTokenDAO.java
@@ -0,0 +1,42 @@
+/*
+ * 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.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+
+public interface AccessTokenDAO extends DAO<AccessToken> {
+
+ AccessToken find(String key);
+
+ AccessToken findByOwner(String username);
+
+ int count();
+
+ List<AccessToken> findAll(int page, int itemsPerPage, List<OrderByClause> orderByClauses);
+
+ AccessToken save(AccessToken accessToken);
+
+ void delete(String key);
+
+ void delete(AccessToken accessToken);
+
+ int deleteExpired();
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AccessToken.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AccessToken.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AccessToken.java
new file mode 100644
index 0000000..e08e9e3
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AccessToken.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Date;
+
+public interface AccessToken extends ProvidedKeyEntity {
+
+ String getBody();
+
+ void setBody(String body);
+
+ Date getExpiryTime();
+
+ void setExpiryTime(Date expiryTime);
+
+ String getOwner();
+
+ void setOwner(String owner);
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAccessTokenDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAccessTokenDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAccessTokenDAO.java
new file mode 100644
index 0000000..01c53fd
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAccessTokenDAO.java
@@ -0,0 +1,143 @@
+/*
+ * 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.Date;
+import java.util.List;
+import javax.persistence.NoResultException;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.persistence.jpa.entity.JPAAccessToken;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.ReflectionUtils;
+
+@Repository
+public class JPAAccessTokenDAO extends AbstractDAO<AccessToken> implements AccessTokenDAO {
+
+ @Transactional(readOnly = true)
+ @Override
+ public AccessToken find(final String key) {
+ return entityManager().find(JPAAccessToken.class, key);
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public AccessToken findByOwner(final String username) {
+ TypedQuery<AccessToken> query = entityManager().createQuery(
+ "SELECT e FROM " + JPAAccessToken.class.getSimpleName() + " e "
+ + "WHERE e.owner=:username", AccessToken.class);
+ query.setParameter("username", username);
+
+ AccessToken result = null;
+ try {
+ result = query.getSingleResult();
+ } catch (NoResultException e) {
+ LOG.debug("No token for user {} could be found", username, e);
+ }
+
+ return result;
+ }
+
+ private StringBuilder buildFindAllQuery() {
+ return new StringBuilder("SELECT e FROM ").
+ append(JPAAccessToken.class.getSimpleName()).
+ append(" e WHERE 1=1");
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public int count() {
+ StringBuilder queryString = buildFindAllQuery();
+
+ Query query = entityManager().createQuery(StringUtils.replaceOnce(
+ queryString.toString(), "SELECT e", "SELECT COUNT(e)"));
+ return ((Number) query.getSingleResult()).intValue();
+ }
+
+ private String toOrderByStatement(final List<OrderByClause> orderByClauses) {
+ StringBuilder statement = new StringBuilder();
+
+ for (OrderByClause clause : orderByClauses) {
+ String field = clause.getField().trim();
+ if (ReflectionUtils.findField(JPAAccessToken.class, field) != null) {
+ statement.append("e.").append(field).append(' ').append(clause.getDirection().name());
+ }
+ }
+
+ if (statement.length() == 0) {
+ statement.append("ORDER BY e.expiryTime DESC");
+ } else {
+ statement.insert(0, "ORDER BY ");
+ }
+ return statement.toString();
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List<AccessToken> findAll(final int page, final int itemsPerPage, final List<OrderByClause> orderByClauses) {
+ StringBuilder queryString = buildFindAllQuery().append(toOrderByStatement(orderByClauses));
+
+ TypedQuery<AccessToken> query = entityManager().createQuery(queryString.toString(), AccessToken.class);
+
+ query.setFirstResult(itemsPerPage * (page <= 0
+ ? 0
+ : page - 1));
+
+ if (itemsPerPage > 0) {
+ query.setMaxResults(itemsPerPage);
+ }
+
+ return query.getResultList();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Throwable.class)
+ public AccessToken save(final AccessToken accessToken) {
+ return entityManager().merge(accessToken);
+ }
+
+ @Override
+ public void delete(final String key) {
+ AccessToken accessToken = find(key);
+ if (accessToken == null) {
+ return;
+ }
+
+ delete(accessToken);
+ }
+
+ @Override
+ public void delete(final AccessToken accessToken) {
+ entityManager().remove(accessToken);
+ }
+
+ @Override
+ public int deleteExpired() {
+ Query query = entityManager().createQuery(
+ "DELETE FROM " + JPAAccessToken.class.getSimpleName() + " e "
+ + "WHERE e.expiryTime < :now");
+ query.setParameter("now", new Date());
+ return query.executeUpdate();
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index 039c279..3580136 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -50,6 +50,7 @@ import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.apache.syncope.core.persistence.api.ImplementationLookup;
import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
import org.apache.syncope.core.persistence.api.dao.AccountRule;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
@@ -57,6 +58,7 @@ import org.apache.syncope.core.persistence.api.dao.PasswordRule;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.RoleDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.Role;
@@ -96,6 +98,9 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
private RoleDAO roleDAO;
@Autowired
+ private AccessTokenDAO accessTokenDAO;
+
+ @Autowired
private ImplementationLookup implementationLookup;
@Resource(name = "adminUser")
@@ -424,6 +429,11 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
group.getUDynMembership().getMembers().remove(user);
}
+ AccessToken accessToken = accessTokenDAO.findByOwner(user.getUsername());
+ if (accessToken != null) {
+ accessTokenDAO.delete(accessToken);
+ }
+
entityManager().remove(user);
}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAccessToken.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAccessToken.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAccessToken.java
new file mode 100644
index 0000000..05464d6
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAccessToken.java
@@ -0,0 +1,83 @@
+/*
+ * 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.Date;
+import javax.persistence.Cacheable;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
+
+@Entity
+@Table(name = JPAAccessToken.TABLE)
+@Cacheable
+public class JPAAccessToken extends AbstractProvidedKeyEntity implements AccessToken {
+
+ public static final String TABLE = "AccessToken";
+
+ private static final long serialVersionUID = -8734194815582467949L;
+
+ @Lob
+ private String body;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date expiryTime;
+
+ @Column(nullable = true)
+ private String owner;
+
+ @Override
+ public String getBody() {
+ return body;
+ }
+
+ @Override
+ public void setBody(final String body) {
+ this.body = body;
+ }
+
+ @Override
+ public Date getExpiryTime() {
+ return expiryTime == null
+ ? null
+ : new Date(expiryTime.getTime());
+ }
+
+ @Override
+ public void setExpiryTime(final Date expiryTime) {
+ this.expiryTime = expiryTime == null
+ ? null
+ : new Date(expiryTime.getTime());
+ }
+
+ @Override
+ public String getOwner() {
+ return owner;
+ }
+
+ @Override
+ public void setOwner(final String owner) {
+ this.owner = owner;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/32af0320/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index 30483fc..7d9660c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.core.persistence.jpa.entity;
+import org.apache.syncope.core.persistence.api.entity.AccessToken;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
@@ -252,6 +253,8 @@ public class JPAEntityFactory implements EntityFactory {
result = (E) new JPAADynGroupMembership();
} else if (reference.equals(UDynGroupMembership.class)) {
result = (E) new JPAUDynGroupMembership();
+ } else if (reference.equals(AccessToken.class)) {
+ result = (E) new JPAAccessToken();
} else {
throw new IllegalArgumentException("Could not find a JPA implementation of " + reference.getName());
}