http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/main/resources/domains/MasterContent.xml ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml index 6aca424..33d8a70 100644 --- a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml +++ b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml @@ -142,6 +142,15 @@ under the License. owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" schema_id="identity.recertification.day.interval"/> <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f77b" attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb" longValue="-1"/> + + <!-- JWT lifetime in minutes --> + <SyncopeSchema id="jwt.lifetime.minutes"/> + <PlainSchema id="jwt.lifetime.minutes" type="Long" + mandatoryCondition="true" multivalue="0" uniqueConstraint="0" readonly="0"/> + <CPlainAttr id="cfec3140-562d-459c-ac6a-e3e10758661d" + owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" schema_id="jwt.lifetime.minutes"/> + <CPlainAttrValue id="447e2456-3ff5-41bc-8ff1-cbb0567546cb" + attribute_id="cfec3140-562d-459c-ac6a-e3e10758661d" longValue="120"/> <AnyType id="USER" kind="USER"/> <AnyTypeClass id="BaseUser"/> @@ -160,6 +169,10 @@ under the License. <Task DTYPE="SchedTask" id="e95555d2-1b09-42c8-b25b-f4c4ec598989" name="Identity Recertification Task" active="0" jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.IdentityRecertification"/> + <Task DTYPE="SchedTask" id="89de5014-e3f5-4462-84d8-d97575740baf" name="Access Token Cleanup Task" active="1" + jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.ExpiredAccessTokenCleanup" + cronExpression="0 0/5 * * * ?"/> + <!-- Password reset notifications --> <MailTemplate id="requestPasswordReset" textTemplate="Hi,
http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java index e82efa6..78dd527 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java @@ -83,7 +83,7 @@ public class MultitenancyTest extends AbstractTest { @Test public void readPlainSchemas() { - assertEquals(11, plainSchemaDAO.findAll().size()); + assertEquals(13, plainSchemaDAO.findAll().size()); } @Test http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java index b20bc4b..f1d0f95 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainSchemaTest.java @@ -47,7 +47,7 @@ public class PlainSchemaTest extends AbstractTest { @Test public void findAll() { List<PlainSchema> schemas = plainSchemaDAO.findAll(); - assertEquals(40, schemas.size()); + assertEquals(41, schemas.size()); } @Test http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java index c77b312..e7f1165 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java @@ -97,7 +97,7 @@ public class TaskTest extends AbstractTest { public void findAll() { assertEquals(5, taskDAO.findAll(TaskType.PROPAGATION).size()); assertEquals(1, taskDAO.findAll(TaskType.NOTIFICATION).size()); - assertEquals(2, taskDAO.findAll(TaskType.SCHEDULED).size()); + assertEquals(3, taskDAO.findAll(TaskType.SCHEDULED).size()); assertEquals(10, taskDAO.findAll(TaskType.PULL).size()); assertEquals(11, taskDAO.findAll(TaskType.PUSH).size()); } http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AccessTokenTest.java ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AccessTokenTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AccessTokenTest.java new file mode 100644 index 0000000..5f87771 --- /dev/null +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AccessTokenTest.java @@ -0,0 +1,64 @@ +/* + * 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.outer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.Date; +import java.util.UUID; +import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO; +import org.apache.syncope.core.persistence.api.entity.AccessToken; +import org.apache.syncope.core.persistence.jpa.AbstractTest; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional("Master") +public class AccessTokenTest extends AbstractTest { + + @Autowired + private AccessTokenDAO accessTokenDAO; + + @Test + public void crud() { + AccessToken accessToken = entityFactory.newEntity(AccessToken.class); + accessToken.setKey(UUID.randomUUID().toString()); + accessToken.setBody("pointless body"); + accessToken.setExpiryTime(new Date()); + accessToken.setOwner("bellini"); + + accessToken = accessTokenDAO.save(accessToken); + assertNotNull(accessToken); + + accessTokenDAO.flush(); + + accessToken = accessTokenDAO.findByOwner("bellini"); + assertNotNull(accessToken); + assertEquals("bellini", accessToken.getOwner()); + + accessTokenDAO.deleteExpired(); + + accessTokenDAO.flush(); + + accessToken = accessTokenDAO.findByOwner("bellini"); + assertNull(accessToken); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/test/resources/domains/MasterContent.xml ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml index 9b70be9..e626c64 100644 --- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml +++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml @@ -143,6 +143,15 @@ under the License. <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f77b" attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb" longValue="365"/> + <!-- JWT lifetime in minutes --> + <SyncopeSchema id="jwt.lifetime.minutes"/> + <PlainSchema id="jwt.lifetime.minutes" type="Long" + mandatoryCondition="true" multivalue="0" uniqueConstraint="0" readonly="0"/> + <CPlainAttr id="cfec3140-562d-459c-ac6a-e3e10758661d" + owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" schema_id="jwt.lifetime.minutes"/> + <CPlainAttrValue id="447e2456-3ff5-41bc-8ff1-cbb0567546cb" + attribute_id="cfec3140-562d-459c-ac6a-e3e10758661d" longValue="120"/> + <!-- sample policies --> <PullPolicy id="66691e96-285f-4464-bc19-e68384ea4c85" description="a pull policy" specification='{"conflictResolutionAction":"IGNORE"}'/> @@ -1110,6 +1119,9 @@ under the License. jobDelegateClassName="org.apache.syncope.fit.core.reference.TestSampleJobDelegate" cronExpression="0 0 0 1 * ?"/> <Task DTYPE="SchedTask" id="e95555d2-1b09-42c8-b25b-f4c4ec598989" name="Identity Recertification Task" active="1" jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.IdentityRecertification"/> + <Task DTYPE="SchedTask" id="89de5014-e3f5-4462-84d8-d97575740baf" name="Access Token Cleanup Task" active="1" + jobDelegateClassName="org.apache.syncope.core.provisioning.java.job.ExpiredAccessTokenCleanup" + cronExpression="0 0/5 * * * ?"/> <Task DTYPE="PropagationTask" id="d6c2d6d3-6329-44c1-9187-f1469ead1cfa" operation="UPDATE" objectClassName="__ACCOUNT__" resource_id="ws-target-resource-nopropagation" anyTypeKind="USER" entityKey="1417acbe-cbf6-4277-9372-e75e04f97000" attributes='[{"name":"__PASSWORD__","value":[{"readOnly":false,"disposed":false,"encryptedBytes":"m9nh2US0Sa6m+cXccCq0Xw==","base64SHA1Hash":"GFJ69qfjxEOdrmt+9q+0Cw2uz60="}]},{"name":"__NAME__","value":["userId"],"nameValue":"userId"},{"name":"fullname","value":["fullname"]},{"name":"type","value":["type"]}]'/> http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/persistence-jpa/src/test/resources/domains/TwoContent.xml ---------------------------------------------------------------------- diff --git a/core/persistence-jpa/src/test/resources/domains/TwoContent.xml b/core/persistence-jpa/src/test/resources/domains/TwoContent.xml index 437debc..d4949d8 100644 --- a/core/persistence-jpa/src/test/resources/domains/TwoContent.xml +++ b/core/persistence-jpa/src/test/resources/domains/TwoContent.xml @@ -105,6 +105,24 @@ under the License. <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f779" attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f6493a" booleanValue="0"/> + <!-- Identity Recertification interval in days --> + <SyncopeSchema id="identity.recertification.day.interval"/> + <PlainSchema id="identity.recertification.day.interval" type="Long" + mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"/> + <CPlainAttr id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb" + owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" schema_id="identity.recertification.day.interval"/> + <CPlainAttrValue id="e5fa94db-b524-4309-908d-8198d0b3f77b" + attribute_id="bcfd7efc-0605-4b5e-b4bb-85c1d5f64bbb" longValue="-1"/> + + <!-- JWT lifetime in minutes --> + <SyncopeSchema id="jwt.lifetime.minutes"/> + <PlainSchema id="jwt.lifetime.minutes" type="Long" + mandatoryCondition="true" multivalue="0" uniqueConstraint="0" readonly="0"/> + <CPlainAttr id="cfec3140-562d-459c-ac6a-e3e10758661d" + owner_id="cd64d66f-6fff-4008-b966-a06b1cc1436d" schema_id="jwt.lifetime.minutes"/> + <CPlainAttrValue id="447e2456-3ff5-41bc-8ff1-cbb0567546cb" + attribute_id="cfec3140-562d-459c-ac6a-e3e10758661d" longValue="120"/> + <AnyType id="USER" kind="USER"/> <AnyTypeClass id="BaseUser"/> <AnyType_AnyTypeClass anyType_id="USER" anyTypeClass_id="BaseUser"/> http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java ---------------------------------------------------------------------- diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java new file mode 100644 index 0000000..b06b4c2 --- /dev/null +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.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.core.provisioning.api.data; + +import java.util.Date; +import org.apache.syncope.common.lib.to.AccessTokenTO; +import org.apache.syncope.core.persistence.api.entity.AccessToken; + +public interface AccessTokenDataBinder { + + void create(String key, String body, Date expiryTime); + + void update(AccessToken accessToken, String body, Date expiryTime); + + AccessTokenTO getAccessTokenTO(AccessToken accessToken); + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java ---------------------------------------------------------------------- diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java index 25e4316..af99906 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java @@ -19,6 +19,7 @@ package org.apache.syncope.core.provisioning.api.serialization; import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.module.afterburner.AfterburnerModule; @@ -75,6 +76,18 @@ public final class POJOHelper { return result; } + public static <T extends Object> T deserialize(final String serialized, final TypeReference<T> reference) { + T result = null; + + try { + result = MAPPER.readValue(serialized, reference); + } catch (Exception e) { + LOG.error("During deserialization", e); + } + + return result; + } + private POJOHelper() { } } http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java new file mode 100644 index 0000000..d15664c --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java @@ -0,0 +1,70 @@ +/* + * 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.provisioning.java.data; + +import java.util.Date; +import org.apache.syncope.common.lib.to.AccessTokenTO; +import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO; +import org.apache.syncope.core.persistence.api.entity.AccessToken; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.provisioning.api.data.AccessTokenDataBinder; +import org.apache.syncope.core.spring.BeanUtils; +import org.apache.syncope.core.spring.security.AuthContextUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class AccessTokenDataBinderImpl implements AccessTokenDataBinder { + + private static final String[] IGNORE_PROPERTIES = { "owner" }; + + @Autowired + private AccessTokenDAO accessTokenDAO; + + @Autowired + private EntityFactory entityFactory; + + @Override + public void create(final String key, final String body, final Date expiryTime) { + AccessToken accessToken = entityFactory.newEntity(AccessToken.class); + accessToken.setKey(key); + accessToken.setBody(body); + accessToken.setExpiryTime(expiryTime); + accessToken.setOwner(AuthContextUtils.getUsername()); + + accessTokenDAO.save(accessToken); + } + + @Override + public void update(final AccessToken accessToken, final String body, final Date expiryTime) { + accessToken.setBody(body); + accessToken.setExpiryTime(expiryTime); + + accessTokenDAO.save(accessToken); + } + + @Override + public AccessTokenTO getAccessTokenTO(final AccessToken accessToken) { + AccessTokenTO accessTokenTO = new AccessTokenTO(); + BeanUtils.copyProperties(accessToken, accessTokenTO, IGNORE_PROPERTIES); + accessTokenTO.setOwner(accessToken.getOwner()); + + return accessTokenTO; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java index 4437713..fe21b51 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java @@ -48,6 +48,7 @@ import org.apache.syncope.common.lib.types.CipherAlgorithm; import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.lib.types.PatchOperation; import org.apache.syncope.common.lib.types.ResourceOperation; +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.SecurityQuestionDAO; import org.apache.syncope.core.persistence.api.entity.group.Group; @@ -61,6 +62,7 @@ import org.apache.syncope.core.spring.BeanUtils; import org.apache.syncope.core.provisioning.api.utils.EntityUtils; import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; import org.apache.syncope.core.persistence.api.dao.RoleDAO; +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.PlainSchema; import org.apache.syncope.core.persistence.api.entity.Realm; @@ -99,6 +101,9 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat @Autowired private AnyTypeDAO anyTypeDAO; + @Autowired + private AccessTokenDAO accessTokenDAO; + @Resource(name = "adminUser") private String adminUser; @@ -338,6 +343,12 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat AuthContextUtils.updateUsername(userPatch.getUsername().getValue()); } + AccessToken accessToken = accessTokenDAO.findByOwner(oldUsername); + if (accessToken != null) { + accessToken.setOwner(userPatch.getUsername().getValue()); + accessTokenDAO.save(accessToken); + } + propByRes.addAll(ResourceOperation.UPDATE, currentResources); } http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredAccessTokenCleanup.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredAccessTokenCleanup.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredAccessTokenCleanup.java new file mode 100644 index 0000000..1e4ac03 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ExpiredAccessTokenCleanup.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.provisioning.java.job; + +import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; + +public class ExpiredAccessTokenCleanup extends AbstractSchedTaskJobDelegate { + + @Autowired + private AccessTokenDAO accessTokenDAO; + + @Override + protected String doExecute(final boolean dryRun) throws JobExecutionException { + if (!dryRun) { + int deleted = accessTokenDAO.deleteExpired(); + LOG.debug("Successfully deleted {} expired access tokens", deleted); + } + + return (dryRun + ? "DRY " + : "") + "RUNNING"; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java index 65584db..36e59a2 100755 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/IdentityRecertification.java @@ -18,10 +18,13 @@ */ package org.apache.syncope.core.provisioning.java.job; +import java.util.Collections; import java.util.Date; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.core.persistence.api.dao.ConfDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttr; import org.apache.syncope.core.persistence.api.entity.task.TaskExec; import org.apache.syncope.core.persistence.api.entity.user.User; @@ -33,6 +36,8 @@ public class IdentityRecertification extends AbstractSchedTaskJobDelegate { private static final String RECERTIFICATION_TIME = "identity.recertification.day.interval"; + private static final int PAGE_SIZE = 10; + @Autowired private ConfDAO confDAO; @@ -88,13 +93,17 @@ public class IdentityRecertification extends AbstractSchedTaskJobDelegate { return ("IDENTITY RECERTIFICATION DISABLED"); } - for (User user : userDAO.findAll()) { - LOG.debug("Processing user: {}", user.getUsername()); + for (int page = 1; page <= (userDAO.count() / PAGE_SIZE) + 1; page++) { + for (User user : userDAO.findAll( + SyncopeConstants.FULL_ADMIN_REALMS, page, PAGE_SIZE, Collections.<OrderByClause>emptyList())) { - if (StringUtils.isNotBlank(user.getWorkflowId()) && isToBeRecertified(user) && !dryRun) { - uwfAdapter.requestCertify(user); - } else { - LOG.warn("Workflow for {} is null or empty", user); + LOG.debug("Processing user: {}", user.getUsername()); + + if (StringUtils.isNotBlank(user.getWorkflowId()) && isToBeRecertified(user) && !dryRun) { + uwfAdapter.requestCertify(user); + } else { + LOG.warn("Workflow for {} is null or empty", user); + } } } http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AccessTokenServiceImpl.java ---------------------------------------------------------------------- diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AccessTokenServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AccessTokenServiceImpl.java new file mode 100644 index 0000000..2b19add --- /dev/null +++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AccessTokenServiceImpl.java @@ -0,0 +1,73 @@ +/* + * 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.rest.cxf.service; + +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.RESTHeaders; +import org.apache.syncope.common.rest.api.beans.AccessTokenQuery; +import org.springframework.stereotype.Service; +import org.apache.syncope.common.rest.api.service.AccessTokenService; +import org.apache.syncope.core.logic.AccessTokenLogic; +import org.springframework.beans.factory.annotation.Autowired; + +@Service +public class AccessTokenServiceImpl extends AbstractServiceImpl implements AccessTokenService { + + @Autowired + private AccessTokenLogic logic; + + @Override + public Response login() { + return Response.noContent(). + header(RESTHeaders.TOKEN, logic.login(messageContext.getHttpServletRequest().getRemoteHost())). + build(); + } + + @Override + public Response refresh() { + return Response.noContent(). + header(RESTHeaders.TOKEN, logic.refresh()). + build(); + } + + @Override + public void logout() { + logic.logout(); + } + + @Override + public PagedResult<AccessTokenTO> list(final AccessTokenQuery query) { + return buildPagedResult( + logic.list( + query.getPage(), + query.getSize(), + getOrderByClauses(query.getOrderBy())), + query.getPage(), + query.getSize(), + logic.count()); + } + + @Override + public void delete(final String key) { + logic.delete(key); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/pom.xml ---------------------------------------------------------------------- diff --git a/core/spring/pom.xml b/core/spring/pom.xml index 6d2e0b6..f07ba6d 100644 --- a/core/spring/pom.xml +++ b/core/spring/pom.xml @@ -55,6 +55,11 @@ under the License. </dependency> <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-rs-security-jose</artifactId> + </dependency> + + <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> </dependency> http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java ---------------------------------------------------------------------- diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java index e8a919d..af85985 100644 --- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java +++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java @@ -35,9 +35,11 @@ import org.apache.commons.collections4.SetUtils; import org.apache.commons.collections4.Transformer; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.AuditElements; 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.AnySearchDAO; import org.apache.syncope.core.provisioning.api.utils.RealmUtils; import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; @@ -48,6 +50,7 @@ import org.apache.syncope.core.persistence.api.dao.RealmDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.dao.search.AttributeCond; import org.apache.syncope.core.persistence.api.dao.search.SearchCond; +import org.apache.syncope.core.persistence.api.entity.AccessToken; import org.apache.syncope.core.persistence.api.entity.Domain; import org.apache.syncope.core.persistence.api.entity.Realm; import org.apache.syncope.core.persistence.api.entity.Role; @@ -57,14 +60,17 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.provisioning.api.AuditManager; import org.apache.syncope.core.provisioning.api.ConnectorFactory; +import org.apache.syncope.core.provisioning.api.EntitlementsHolder; import org.apache.syncope.core.provisioning.api.MappingManager; import org.identityconnectors.framework.common.objects.Uid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.transaction.annotation.Transactional; /** @@ -79,6 +85,9 @@ public class AuthDataAccessor { protected static final Encryptor ENCRYPTOR = Encryptor.getInstance(); + protected static final Set<SyncopeGrantedAuthority> ANONYMOUS_AUTHORITIES = + Collections.singleton(new SyncopeGrantedAuthority(StandardEntitlement.ANONYMOUS)); + @Resource(name = "adminUser") protected String adminUser; @@ -107,6 +116,9 @@ public class AuthDataAccessor { protected AnySearchDAO searchDAO; @Autowired + protected AccessTokenDAO accessTokenDAO; + + @Autowired protected ConnectorFactory connFactory; @Autowired @@ -170,7 +182,7 @@ public class AuthDataAccessor { } boolean userModified = false; - authenticated = authenticate(user, authentication.getCredentials().toString()); + authenticated = AuthDataAccessor.this.authenticate(user, authentication.getCredentials().toString()); if (authenticated) { if (confDAO.find("log.lastlogindate", Boolean.toString(true)).getValues().get(0).getBooleanValue()) { user.setLastLoginDate(new Date()); @@ -249,23 +261,20 @@ public class AuthDataAccessor { return SetUtils.emptyIfNull(result); } - @Transactional(readOnly = true) - public void audit( - final AuditElements.EventCategoryType type, - final String category, - final String subcategory, - final String event, - final AuditElements.Result result, - final Object before, - final Object output, - final Object... input) { + protected Set<SyncopeGrantedAuthority> getAdminAuthorities() { + return CollectionUtils.collect(EntitlementsHolder.getInstance().getValues(), + new Transformer<String, SyncopeGrantedAuthority>() { - auditManager.audit(type, category, subcategory, event, result, before, output, input); + @Override + public SyncopeGrantedAuthority transform(final String entitlement) { + return new SyncopeGrantedAuthority(entitlement, SyncopeConstants.ROOT_REALM); + } + }, new HashSet<SyncopeGrantedAuthority>()); } - @Transactional - public Set<SyncopeGrantedAuthority> getAuthorities(final User user) { + protected Set<SyncopeGrantedAuthority> getUserAuthorities(final User user) { Set<SyncopeGrantedAuthority> authorities = new HashSet<>(); + if (user.isMustChangePassword()) { authorities.add(new SyncopeGrantedAuthority(StandardEntitlement.MUST_CHANGE_PASSWORD)); } else { @@ -322,4 +331,73 @@ public class AuthDataAccessor { return authorities; } + + @Transactional + public Set<SyncopeGrantedAuthority> getAuthorities(final String username) { + Set<SyncopeGrantedAuthority> authorities; + + if (anonymousUser.equals(username)) { + authorities = ANONYMOUS_AUTHORITIES; + } else if (adminUser.equals(username)) { + authorities = getAdminAuthorities(); + } else { + User user = userDAO.findByUsername(username); + if (user == null) { + throw new UsernameNotFoundException("Could not find any user with id " + username); + } + + authorities = getUserAuthorities(user); + } + + return authorities; + } + + @Transactional + public Set<SyncopeGrantedAuthority> authenticate(final JWTAuthentication authentication) { + AccessToken accessToken = accessTokenDAO.find(authentication.getClaims().getTokenId()); + if (accessToken == null) { + throw new AuthenticationCredentialsNotFoundException( + "Could not find JWT " + authentication.getClaims().getTokenId()); + } + + Set<SyncopeGrantedAuthority> authorities; + if (adminUser.equals(accessToken.getOwner())) { + authorities = getAdminAuthorities(); + } else { + User user = userDAO.findByUsername(accessToken.getOwner()); + if (user == null) { + throw new AuthenticationCredentialsNotFoundException( + "Could not find user " + accessToken.getOwner() + + " for JWT " + authentication.getClaims().getTokenId()); + } + + if (user.isSuspended() != null && user.isSuspended()) { + throw new DisabledException("User " + user.getUsername() + " is suspended"); + } + + CPlainAttr authStatuses = confDAO.find("authentication.statuses"); + if (authStatuses != null && !authStatuses.getValuesAsStrings().contains(user.getStatus())) { + throw new DisabledException("User " + user.getUsername() + " not allowed to authenticate"); + } + + authorities = getUserAuthorities(user); + } + + return authorities; + } + + @Transactional(readOnly = true) + public void audit( + final AuditElements.EventCategoryType type, + final String category, + final String subcategory, + final String event, + final AuditElements.Result result, + final Object before, + final Object output, + final Object... input) { + + auditManager.audit(type, category, subcategory, event, result, before, output, input); + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java ---------------------------------------------------------------------- diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.java new file mode 100644 index 0000000..7348938 --- /dev/null +++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthentication.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.core.spring.security; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.rs.security.jose.jwt.JwtClaims; +import org.springframework.security.core.Authentication; + +/** + * Represents the token for an authentication request or for an authenticated principal as JSON Web Token, + * once the request has been processed by the + * {@link org.springframework.security.authentication.AuthenticationManager#authenticate(Authentication)} method. + */ +public class JWTAuthentication implements Authentication { + + private static final long serialVersionUID = -2013733709281305394L; + + private final JwtClaims claims; + + private final SyncopeAuthenticationDetails details; + + private final Set<SyncopeGrantedAuthority> authorities = new HashSet<>(); + + private boolean authenticated = false; + + public JWTAuthentication(final JwtClaims claims, final SyncopeAuthenticationDetails details) { + this.claims = claims; + this.details = details; + } + + public JwtClaims getClaims() { + return claims; + } + + @Override + public Collection<SyncopeGrantedAuthority> getAuthorities() { + return authorities; + } + + @Override + public Object getCredentials() { + return StringUtils.EMPTY; + } + + @Override + public SyncopeAuthenticationDetails getDetails() { + return details; + } + + @Override + public Object getPrincipal() { + return claims.getSubject(); + } + + @Override + public boolean isAuthenticated() { + return authenticated; + } + + @Override + public void setAuthenticated(final boolean authenticated) throws IllegalArgumentException { + this.authenticated = authenticated; + } + + @Override + public String getName() { + return claims.getSubject(); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java new file mode 100644 index 0000000..c1520fa --- /dev/null +++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationFilter.java @@ -0,0 +1,108 @@ +/* + * 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.spring.security; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer; +import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.util.Assert; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * Processes the JSON Web Token provided as {@link RESTHeaders#TOKEN} HTTP header, putting the result into the + * {@link SecurityContextHolder}. + */ +public class JWTAuthenticationFilter extends OncePerRequestFilter { + + private static final Logger LOG = LoggerFactory.getLogger(JWTAuthenticationFilter.class); + + private AuthenticationEntryPoint authenticationEntryPoint; + + private AuthenticationManager authenticationManager; + + private SyncopeAuthenticationDetailsSource authenticationDetailsSource; + + @Autowired + private JwsSignatureVerifier jwsSignatureCerifier; + + public void setAuthenticationEntryPoint(final AuthenticationEntryPoint authenticationEntryPoint) { + this.authenticationEntryPoint = authenticationEntryPoint; + } + + public void setAuthenticationManager(final AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public void setAuthenticationDetailsSource(final SyncopeAuthenticationDetailsSource authenticationDetailsSource) { + this.authenticationDetailsSource = authenticationDetailsSource; + } + + @Override + public void afterPropertiesSet() { + Assert.notNull(this.authenticationEntryPoint, "An AuthenticationEntryPoint is required"); + Assert.notNull(this.authenticationManager, "An AuthenticationManager is required"); + Assert.notNull(this.authenticationDetailsSource, "AuthenticationDetailsSource required"); + } + + @Override + protected void doFilterInternal( + final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain chain) + throws ServletException, IOException { + + String stringToken = request.getHeader(RESTHeaders.TOKEN); + if (stringToken == null) { + chain.doFilter(request, response); + return; + } + + LOG.debug("JWT receveid: {}", stringToken); + + JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(stringToken); + try { + if (!consumer.verifySignatureWith(jwsSignatureCerifier)) { + throw new BadCredentialsException("Invalid signature found in JWT"); + } + + Authentication authentication = authenticationManager.authenticate( + new JWTAuthentication(consumer.getJwtClaims(), authenticationDetailsSource.buildDetails(request))); + SecurityContextHolder.getContext().setAuthentication(authentication); + + chain.doFilter(request, response); + } catch (AuthenticationException e) { + SecurityContextHolder.clearContext(); + this.authenticationEntryPoint.commence(request, response, e); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java new file mode 100644 index 0000000..9686fd7 --- /dev/null +++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/JWTAuthenticationProvider.java @@ -0,0 +1,91 @@ +/* + * 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.spring.security; + +import java.util.Date; +import javax.annotation.Resource; +import org.apache.cxf.rs.security.jose.jwt.JwtClaims; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * Attempts to authenticate the passed {@link JWTAuthentication} object, returning a fully populated + * {@link Authentication} object (including granted authorities) if successful. + */ +public class JWTAuthenticationProvider implements AuthenticationProvider { + + @Resource(name = "jwtIssuer") + private String jwtIssuer; + + @Autowired + private AuthDataAccessor dataAccessor; + + @Override + public Authentication authenticate(final Authentication authentication) throws AuthenticationException { + final JWTAuthentication jwtAuthentication = (JWTAuthentication) authentication; + + AuthContextUtils.execWithAuthContext( + jwtAuthentication.getDetails().getDomain(), + new AuthContextUtils.Executable<Void>() { + + @Override + public Void exec() { + jwtAuthentication.getAuthorities().addAll(dataAccessor.authenticate(jwtAuthentication)); + return null; + } + }); + + JwtClaims claims = jwtAuthentication.getClaims(); + Long referenceTime = new Date().getTime(); + + Long expiryTime = claims.getExpiryTime(); + if (expiryTime == null || expiryTime < referenceTime) { + throw new CredentialsExpiredException("JWT is expired"); + } + + Long notBefore = claims.getNotBefore(); + if (notBefore == null || notBefore > referenceTime) { + throw new CredentialsExpiredException("JWT not valid yet"); + } + + if (!jwtIssuer.equals(claims.getIssuer())) { + throw new BadCredentialsException("Invalid JWT issuer"); + } + + if (!claims.containsProperty(SyncopeConstants.JWT_CLAIM_REMOTE_HOST) + || !claims.getClaim(SyncopeConstants.JWT_CLAIM_REMOTE_HOST). + equals(jwtAuthentication.getDetails().getRemoteHost())) { + + throw new BadCredentialsException("Unexpected property found in JWT"); + } + + jwtAuthentication.setAuthenticated(true); + return jwtAuthentication; + } + + @Override + public boolean supports(final Class<?> authentication) { + return JWTAuthentication.class.isAssignableFrom(authentication); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java ---------------------------------------------------------------------- diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java index c427c48..d2080aa 100644 --- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java +++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java @@ -20,52 +20,40 @@ package org.apache.syncope.core.spring.security; import java.io.Serializable; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.rest.api.RESTHeaders; public class SyncopeAuthenticationDetails implements Serializable { private static final long serialVersionUID = -5899959397393502897L; - private final String remoteAddress; + private final String remoteHost; - private final String sessionId; - - private String domain; + private final String domain; public SyncopeAuthenticationDetails(final HttpServletRequest request) { - this.remoteAddress = request.getRemoteAddr(); - - HttpSession session = request.getSession(false); - this.sessionId = session == null ? null : session.getId(); - this.domain = request.getHeader(RESTHeaders.DOMAIN); + this.remoteHost = request.getRemoteHost(); } public SyncopeAuthenticationDetails(final String domain) { - this.remoteAddress = null; - this.sessionId = null; this.domain = domain; - } - - public String getRemoteAddress() { - return remoteAddress; - } - - public String getSessionId() { - return sessionId; + this.remoteHost = null; } public String getDomain() { - return domain; + return StringUtils.isBlank(domain) + ? SyncopeConstants.MASTER_DOMAIN + : domain; } - public void setDomain(final String domain) { - this.domain = domain; + public String getRemoteHost() { + return remoteHost; } @Override http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java deleted file mode 100644 index 9f6a3ed..0000000 --- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java +++ /dev/null @@ -1,235 +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.core.spring.security; - -import java.util.HashSet; -import java.util.Set; -import javax.annotation.Resource; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.Transformer; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.syncope.common.lib.SyncopeConstants; -import org.apache.syncope.common.lib.types.AuditElements; -import org.apache.syncope.common.lib.types.AuditElements.Result; -import org.apache.syncope.common.lib.types.CipherAlgorithm; -import org.apache.syncope.common.lib.types.StandardEntitlement; -import org.apache.syncope.core.spring.security.AuthContextUtils.Executable; -import org.apache.syncope.core.persistence.api.entity.Domain; -import org.apache.syncope.core.persistence.api.entity.user.User; -import org.apache.syncope.core.provisioning.api.EntitlementsHolder; -import org.apache.syncope.core.provisioning.api.UserProvisioningManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Configurable; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; - -@Configurable -public class SyncopeAuthenticationProvider implements AuthenticationProvider { - - protected static final Logger LOG = LoggerFactory.getLogger(SyncopeAuthenticationProvider.class); - - @Autowired - protected AuthDataAccessor dataAccessor; - - @Autowired - protected UserProvisioningManager provisioningManager; - - @Resource(name = "adminUser") - protected String adminUser; - - @Resource(name = "anonymousUser") - protected String anonymousUser; - - protected String adminPassword; - - protected String adminPasswordAlgorithm; - - protected String anonymousKey; - - protected final Encryptor encryptor = Encryptor.getInstance(); - - /** - * @param adminPassword the adminPassword to set - */ - public void setAdminPassword(final String adminPassword) { - this.adminPassword = adminPassword; - } - - /** - * @param adminPasswordAlgorithm the adminPasswordAlgorithm to set - */ - public void setAdminPasswordAlgorithm(final String adminPasswordAlgorithm) { - this.adminPasswordAlgorithm = adminPasswordAlgorithm; - } - - /** - * @param anonymousKey the anonymousKey to set - */ - public void setAnonymousKey(final String anonymousKey) { - this.anonymousKey = anonymousKey; - } - - @Override - public Authentication authenticate(final Authentication authentication) { - String domainKey = SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain(); - if (StringUtils.isBlank(domainKey)) { - domainKey = SyncopeConstants.MASTER_DOMAIN; - } - SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).setDomain(domainKey); - - final String[] username = new String[1]; - Boolean authenticated; - final Set<SyncopeGrantedAuthority> authorities = new HashSet<>(); - - if (anonymousUser.equals(authentication.getName())) { - username[0] = anonymousUser; - authenticated = authentication.getCredentials().toString().equals(anonymousKey); - if (authenticated) { - authorities.add(new SyncopeGrantedAuthority(StandardEntitlement.ANONYMOUS)); - } - } else if (adminUser.equals(authentication.getName())) { - username[0] = adminUser; - if (SyncopeConstants.MASTER_DOMAIN.equals(domainKey)) { - authenticated = encryptor.verify( - authentication.getCredentials().toString(), - CipherAlgorithm.valueOf(adminPasswordAlgorithm), - adminPassword); - } else { - final String domainToFind = domainKey; - authenticated = AuthContextUtils.execWithAuthContext( - SyncopeConstants.MASTER_DOMAIN, new Executable<Boolean>() { - - @Override - public Boolean exec() { - Domain domain = dataAccessor.findDomain(domainToFind); - - return encryptor.verify( - authentication.getCredentials().toString(), - domain.getAdminCipherAlgorithm(), - domain.getAdminPwd()); - } - }); - } - if (authenticated) { - CollectionUtils.collect( - EntitlementsHolder.getInstance().getValues(), - new Transformer<String, SyncopeGrantedAuthority>() { - - @Override - public SyncopeGrantedAuthority transform(final String entitlement) { - return new SyncopeGrantedAuthority(entitlement, SyncopeConstants.ROOT_REALM); - } - }, authorities); - } - } else { - final Pair<User, Boolean> authResult = - AuthContextUtils.execWithAuthContext(domainKey, new Executable<Pair<User, Boolean>>() { - - @Override - public Pair<User, Boolean> exec() { - return dataAccessor.authenticate(authentication); - } - }); - authenticated = authResult.getValue(); - if (authResult.getLeft() != null && authResult.getRight() != null) { - username[0] = authResult.getLeft().getUsername(); - - if (authResult.getRight()) { - authorities.addAll(dataAccessor.getAuthorities(authResult.getLeft())); - } else { - AuthContextUtils.execWithAuthContext(domainKey, new Executable<Void>() { - - @Override - public Void exec() { - provisioningManager.internalSuspend(authResult.getLeft().getKey()); - return null; - } - }); - } - } - } - if (username[0] == null) { - username[0] = authentication.getPrincipal().toString(); - } - - final boolean isAuthenticated = authenticated != null && authenticated; - UsernamePasswordAuthenticationToken token; - if (isAuthenticated) { - token = AuthContextUtils.execWithAuthContext( - domainKey, new Executable<UsernamePasswordAuthenticationToken>() { - - @Override - public UsernamePasswordAuthenticationToken exec() { - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( - username[0], - null, - authorities); - token.setDetails(authentication.getDetails()); - - dataAccessor.audit(AuditElements.EventCategoryType.LOGIC, - AuditElements.AUTHENTICATION_CATEGORY, - null, - AuditElements.LOGIN_EVENT, - Result.SUCCESS, - null, - isAuthenticated, - authentication, - "Successfully authenticated, with entitlements: " + token.getAuthorities()); - return token; - } - }); - - LOG.debug("User {} successfully authenticated, with entitlements {}", - username[0], token.getAuthorities()); - } else { - AuthContextUtils.execWithAuthContext(domainKey, new Executable<Void>() { - - @Override - public Void exec() { - dataAccessor.audit(AuditElements.EventCategoryType.LOGIC, - AuditElements.AUTHENTICATION_CATEGORY, - null, - AuditElements.LOGIN_EVENT, - Result.FAILURE, - null, - isAuthenticated, - authentication, - "User " + username[0] + " not authenticated"); - return null; - } - }); - - LOG.debug("User {} not authenticated", username[0]); - - throw new BadCredentialsException("User " + username[0] + " not authenticated"); - } - - return token; - } - - @Override - public boolean supports(final Class<? extends Object> type) { - return type.equals(UsernamePasswordAuthenticationToken.class); - } -} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeDigestAuthenticationEntryPoint.java ---------------------------------------------------------------------- diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeDigestAuthenticationEntryPoint.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeDigestAuthenticationEntryPoint.java new file mode 100644 index 0000000..79b33c5 --- /dev/null +++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeDigestAuthenticationEntryPoint.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.core.spring.security; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint; + +/** + * Render Spring's {@link AuthenticationException} as other Syncope errors. + */ +public class SyncopeDigestAuthenticationEntryPoint extends DigestAuthenticationEntryPoint { + + @Override + public void commence(final HttpServletRequest request, final HttpServletResponse response, + final AuthenticationException authException) throws IOException, ServletException { + + response.addHeader(RESTHeaders.ERROR_INFO, authException.getMessage()); + + super.commence(request, response, authException); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java ---------------------------------------------------------------------- diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java index e23902b..578dd35 100644 --- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java +++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java @@ -18,6 +18,9 @@ */ package org.apache.syncope.core.spring.security; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -36,11 +39,13 @@ public class SyncopeGrantedAuthority implements GrantedAuthority { private static final long serialVersionUID = -5647624636011919735L; + @JsonProperty private final String entitlement; private final Set<String> realms = SetUtils.orderedSet(new HashSet<String>()); - public SyncopeGrantedAuthority(final String entitlement) { + @JsonCreator + public SyncopeGrantedAuthority(@JsonProperty("entitlement") final String entitlement) { this.entitlement = entitlement; } @@ -67,6 +72,7 @@ public class SyncopeGrantedAuthority implements GrantedAuthority { return Collections.unmodifiableSet(realms); } + @JsonIgnore @Override public String getAuthority() { return entitlement; http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java new file mode 100644 index 0000000..83a03c3 --- /dev/null +++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java @@ -0,0 +1,210 @@ +/* + * 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.spring.security; + +import javax.annotation.Resource; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditElements.Result; +import org.apache.syncope.common.lib.types.CipherAlgorithm; +import org.apache.syncope.core.spring.security.AuthContextUtils.Executable; +import org.apache.syncope.core.persistence.api.entity.Domain; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.UserProvisioningManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; + +@Configurable +public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider { + + protected static final Logger LOG = LoggerFactory.getLogger(UsernamePasswordAuthenticationProvider.class); + + @Autowired + protected AuthDataAccessor dataAccessor; + + @Autowired + protected UserProvisioningManager provisioningManager; + + @Resource(name = "adminUser") + protected String adminUser; + + @Resource(name = "adminPassword") + protected String adminPassword; + + @Resource(name = "adminPasswordAlgorithm") + protected String adminPasswordAlgorithm; + + @Resource(name = "anonymousUser") + protected String anonymousUser; + + @Resource(name = "anonymousKey") + protected String anonymousKey; + + protected final Encryptor encryptor = Encryptor.getInstance(); + + /** + * @param adminPassword the adminPassword to set + */ + public void setAdminPassword(final String adminPassword) { + this.adminPassword = adminPassword; + } + + /** + * @param adminPasswordAlgorithm the adminPasswordAlgorithm to set + */ + public void setAdminPasswordAlgorithm(final String adminPasswordAlgorithm) { + this.adminPasswordAlgorithm = adminPasswordAlgorithm; + } + + /** + * @param anonymousKey the anonymousKey to set + */ + public void setAnonymousKey(final String anonymousKey) { + this.anonymousKey = anonymousKey; + } + + @Override + public Authentication authenticate(final Authentication authentication) { + String domainKey = SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain(); + + final String[] username = new String[1]; + Boolean authenticated; + + if (anonymousUser.equals(authentication.getName())) { + username[0] = anonymousUser; + authenticated = authentication.getCredentials().toString().equals(anonymousKey); + } else if (adminUser.equals(authentication.getName())) { + username[0] = adminUser; + if (SyncopeConstants.MASTER_DOMAIN.equals(domainKey)) { + authenticated = encryptor.verify( + authentication.getCredentials().toString(), + CipherAlgorithm.valueOf(adminPasswordAlgorithm), + adminPassword); + } else { + final String domainToFind = domainKey; + authenticated = AuthContextUtils.execWithAuthContext( + SyncopeConstants.MASTER_DOMAIN, new Executable<Boolean>() { + + @Override + public Boolean exec() { + Domain domain = dataAccessor.findDomain(domainToFind); + + return encryptor.verify( + authentication.getCredentials().toString(), + domain.getAdminCipherAlgorithm(), + domain.getAdminPwd()); + } + }); + } + } else { + final Pair<User, Boolean> authResult = + AuthContextUtils.execWithAuthContext(domainKey, new Executable<Pair<User, Boolean>>() { + + @Override + public Pair<User, Boolean> exec() { + return dataAccessor.authenticate(authentication); + } + }); + authenticated = authResult.getValue(); + if (authResult.getLeft() != null && authResult.getRight() != null) { + username[0] = authResult.getLeft().getUsername(); + + if (!authResult.getRight()) { + AuthContextUtils.execWithAuthContext(domainKey, new Executable<Void>() { + + @Override + public Void exec() { + provisioningManager.internalSuspend(authResult.getLeft().getKey()); + return null; + } + }); + } + } + } + if (username[0] == null) { + username[0] = authentication.getPrincipal().toString(); + } + + final boolean isAuthenticated = authenticated != null && authenticated; + UsernamePasswordAuthenticationToken token; + if (isAuthenticated) { + token = AuthContextUtils.execWithAuthContext( + domainKey, new Executable<UsernamePasswordAuthenticationToken>() { + + @Override + public UsernamePasswordAuthenticationToken exec() { + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( + username[0], + null, + dataAccessor.getAuthorities(username[0])); + token.setDetails(authentication.getDetails()); + + dataAccessor.audit(AuditElements.EventCategoryType.LOGIC, + AuditElements.AUTHENTICATION_CATEGORY, + null, + AuditElements.LOGIN_EVENT, + Result.SUCCESS, + null, + isAuthenticated, + authentication, + "Successfully authenticated, with entitlements: " + token.getAuthorities()); + return token; + } + }); + + LOG.debug("User {} successfully authenticated, with entitlements {}", + username[0], token.getAuthorities()); + } else { + AuthContextUtils.execWithAuthContext(domainKey, new Executable<Void>() { + + @Override + public Void exec() { + dataAccessor.audit(AuditElements.EventCategoryType.LOGIC, + AuditElements.AUTHENTICATION_CATEGORY, + null, + AuditElements.LOGIN_EVENT, + Result.FAILURE, + null, + isAuthenticated, + authentication, + "User " + username[0] + " not authenticated"); + return null; + } + }); + + LOG.debug("User {} not authenticated", username[0]); + + throw new BadCredentialsException("User " + username[0] + " not authenticated"); + } + + return token; + } + + @Override + public boolean supports(final Class<? extends Object> type) { + return type.equals(UsernamePasswordAuthenticationToken.class); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/resources/security.properties ---------------------------------------------------------------------- diff --git a/core/spring/src/main/resources/security.properties b/core/spring/src/main/resources/security.properties index 8c85e21..d4f892b 100644 --- a/core/spring/src/main/resources/security.properties +++ b/core/spring/src/main/resources/security.properties @@ -22,6 +22,10 @@ anonymousUser=${anonymousUser} anonymousKey=${anonymousKey} secretKey=${secretKey} + +jwsKey=ZW7pRixehFuNUtnY5Se47IemgMryTzazPPJ9CGX5LTCmsOJpOgHAQEuPQeV9A28f +jwtIssuer=ApacheSyncope + # default for LDAP / RFC2307 SSHA digester.saltIterations=1 digester.saltSizeBytes=8 http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/core/spring/src/main/resources/securityContext.xml ---------------------------------------------------------------------- diff --git a/core/spring/src/main/resources/securityContext.xml b/core/spring/src/main/resources/securityContext.xml index 3aa6079..809b78d 100644 --- a/core/spring/src/main/resources/securityContext.xml +++ b/core/spring/src/main/resources/securityContext.xml @@ -28,53 +28,89 @@ under the License. <bean id="adminUser" class="java.lang.String"> <constructor-arg value="${adminUser}"/> </bean> + <bean id="adminPassword" class="java.lang.String"> + <constructor-arg value="${adminPassword}"/> + </bean> + <bean id="adminPasswordAlgorithm" class="java.lang.String"> + <constructor-arg value="${adminPasswordAlgorithm}"/> + </bean> + <bean id="anonymousUser" class="java.lang.String"> <constructor-arg value="${anonymousUser}"/> </bean> + <bean id="anonymousKey" class="java.lang.String"> + <constructor-arg value="${anonymousKey}"/> + </bean> + + <bean id="jwtIssuer" class="java.lang.String"> + <constructor-arg value="${jwtIssuer}"/> + </bean> + <bean id="jwsKey" class="java.lang.String"> + <constructor-arg value="${jwsKey}"/> + </bean> + <bean id="jwsSignatureVerifier" class="org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier"> + <constructor-arg value="${jwsKey}.bytes" index="0"/> + <constructor-arg index="1"> + <value type="org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm">HS512</value> + </constructor-arg> + </bean> + <bean id="jwsSignatureProvider" class="org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider"> + <constructor-arg value="${jwsKey}.bytes" index="0"/> + <constructor-arg index="1"> + <value type="org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm">HS512</value> + </constructor-arg> + </bean> <bean class="${passwordGenerator}"/> <bean class="org.apache.syncope.core.spring.DefaultRolesPrefixPostProcessor"/> <security:global-method-security pre-post-annotations="enabled"/> + <bean id="securityContextRepository" class='org.springframework.security.web.context.NullSecurityContextRepository'/> + <bean id="securityContextPersistenceFilter" + class="org.springframework.security.web.context.SecurityContextPersistenceFilter"> + <constructor-arg ref="securityContextRepository"/> + </bean> + <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy"> <security:filter-chain-map request-matcher="ant"> <security:filter-chain pattern="/**" filters="securityContextPersistenceFilter"/> </security:filter-chain-map> - </bean> - - <bean id="securityContextRepository" class='org.springframework.security.web.context.NullSecurityContextRepository'/> + </bean> - <bean id="securityContextPersistenceFilter" - class="org.springframework.security.web.context.SecurityContextPersistenceFilter"> - <constructor-arg ref="securityContextRepository"/> + <bean id="firewall" class="org.springframework.security.web.firewall.DefaultHttpFirewall"> + <property name="allowUrlEncodedSlash" value="true"/> </bean> + <security:http-firewall ref="firewall"/> - <bean id="syncopeAuthenticationDetailsSource" + <bean id="authenticationDetailsSource" class="org.apache.syncope.core.spring.security.SyncopeAuthenticationDetailsSource"/> - - <bean id="mustChangePasswordFilter" class="org.apache.syncope.core.spring.security.MustChangePasswordFilter"/> <bean id="basicAuthenticationEntryPoint" class="org.apache.syncope.core.spring.security.SyncopeBasicAuthenticationEntryPoint"> <property name="realmName" value="Apache Syncope authentication"/> </bean> - <bean id="syncopeAccessDeniedHandler" class="org.apache.syncope.core.spring.security.SyncopeAccessDeniedHandler"/> - - <bean id="firewall" class="org.springframework.security.web.firewall.DefaultHttpFirewall"> - <property name="allowUrlEncodedSlash" value="true"/> + <bean id="jwtAuthenticationFilter" class="org.apache.syncope.core.spring.security.JWTAuthenticationFilter"> + <property name="authenticationManager" ref="authenticationManager"/> + <property name="authenticationEntryPoint" ref="basicAuthenticationEntryPoint"/> + <property name="authenticationDetailsSource" ref="authenticationDetailsSource"/> </bean> - <security:http-firewall ref="firewall"/> + + <bean id="mustChangePasswordFilter" class="org.apache.syncope.core.spring.security.MustChangePasswordFilter"/> - <security:http security-context-repository-ref="securityContextRepository" + <bean id="syncopeAccessDeniedHandler" class="org.apache.syncope.core.spring.security.SyncopeAccessDeniedHandler"/> + + <security:http create-session="stateless" + security-context-repository-ref="securityContextRepository" entry-point-ref="basicAuthenticationEntryPoint" - use-expressions="false" disable-url-rewriting="false"> + use-expressions="false" disable-url-rewriting="false" + pattern="/**"> - <security:http-basic entry-point-ref="basicAuthenticationEntryPoint" - authentication-details-source-ref="syncopeAuthenticationDetailsSource"/> <security:anonymous username="${anonymousUser}"/> - <security:intercept-url pattern="/**"/> + + <security:custom-filter ref="jwtAuthenticationFilter" before="BASIC_AUTH_FILTER"/> + <security:http-basic authentication-details-source-ref="authenticationDetailsSource"/> <security:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="mustChangePasswordFilter"/> @@ -86,14 +122,14 @@ under the License. <bean class="org.apache.syncope.core.spring.security.AuthDataAccessor"/> - <bean id="syncopeAuthenticationProvider" - class="org.apache.syncope.core.spring.security.SyncopeAuthenticationProvider"> - <property name="adminPassword" value="${adminPassword}"/> - <property name="adminPasswordAlgorithm" value="${adminPasswordAlgorithm}"/> - <property name="anonymousKey" value="${anonymousKey}"/> - </bean> + <bean id="usernamePasswordAuthenticationProvider" + class="org.apache.syncope.core.spring.security.UsernamePasswordAuthenticationProvider"/> + + <bean id="jwtAuthenticationProvider" + class="org.apache.syncope.core.spring.security.JWTAuthenticationProvider"/> - <security:authentication-manager> - <security:authentication-provider ref="syncopeAuthenticationProvider"/> + <security:authentication-manager alias="authenticationManager"> + <security:authentication-provider ref="usernamePasswordAuthenticationProvider"/> + <security:authentication-provider ref="jwtAuthenticationProvider"/> </security:authentication-manager> </beans> http://git-wip-us.apache.org/repos/asf/syncope/blob/521f51a9/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java index 4003c3b..282f8dc 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java @@ -52,6 +52,7 @@ import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.ConnConfProperty; import org.apache.syncope.common.lib.types.PatchOperation; import org.apache.syncope.common.lib.types.SchemaType; +import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.service.AnyObjectService; import org.apache.syncope.common.rest.api.service.AnyTypeClassService; import org.apache.syncope.common.rest.api.service.AnyTypeService; @@ -284,7 +285,10 @@ public abstract class AbstractITCase { WebClient webClient = WebClient.fromClient(WebClient.client(adminClient.getService(serviceClass))); webClient.accept(clientFactory.getContentType().getMediaType()).to(location.toASCIIString(), false); - return webClient.get(resultClass); + return webClient. + header(RESTHeaders.DOMAIN, adminClient.getDomain()). + header(RESTHeaders.TOKEN, adminClient.getJWT()). + get(resultClass); } @SuppressWarnings("unchecked")