syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ilgro...@apache.org
Subject [2/2] syncope git commit: [SYNCOPE-1324] Implementation provided
Date Wed, 13 Jun 2018 16:04:11 GMT
[SYNCOPE-1324] Implementation provided


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/ebc1e056
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/ebc1e056
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/ebc1e056

Branch: refs/heads/master
Commit: ebc1e056e38b231c938da9ebd133e4d7af11d882
Parents: 72ca184
Author: Francesco Chicchiriccò <ilgrosso@apache.org>
Authored: Wed Jun 13 17:27:51 2018 +0200
Committer: Francesco Chicchiriccò <ilgrosso@apache.org>
Committed: Wed Jun 13 18:04:02 2018 +0200

----------------------------------------------------------------------
 .../policy/HaveIBeenPwnedPasswordRuleConf.java  |  30 ++++++
 .../jpa/dao/HaveIBeenPwnedPasswordRule.java     | 105 +++++++++++++++++++
 .../core/reference/ITImplementationLookup.java  |   3 +
 .../org/apache/syncope/fit/core/UserITCase.java |  38 +++++++
 .../reference-guide/concepts/policies.adoc      |  59 +++++++----
 5 files changed, 217 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/ebc1e056/common/lib/src/main/java/org/apache/syncope/common/lib/policy/HaveIBeenPwnedPasswordRuleConf.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/policy/HaveIBeenPwnedPasswordRuleConf.java
b/common/lib/src/main/java/org/apache/syncope/common/lib/policy/HaveIBeenPwnedPasswordRuleConf.java
new file mode 100644
index 0000000..60eb62c
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/policy/HaveIBeenPwnedPasswordRuleConf.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.common.lib.policy;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "haveIBeenPwnedPasswordRuleConf")
+@XmlType
+public class HaveIBeenPwnedPasswordRuleConf extends AbstractPasswordRuleConf {
+
+    private static final long serialVersionUID = -8962889598888347921L;
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/ebc1e056/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/HaveIBeenPwnedPasswordRule.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/HaveIBeenPwnedPasswordRule.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/HaveIBeenPwnedPasswordRule.java
new file mode 100644
index 0000000..cc5bf94
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/HaveIBeenPwnedPasswordRule.java
@@ -0,0 +1,105 @@
+/*
+ * 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.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.stream.Stream;
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.policy.HaveIBeenPwnedPasswordRuleConf;
+import org.apache.syncope.common.lib.policy.PasswordRuleConf;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.persistence.api.dao.PasswordRule;
+import org.apache.syncope.core.persistence.api.dao.PasswordRuleConfClass;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.utils.policy.PasswordPolicyException;
+import org.apache.syncope.core.spring.security.Encryptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestTemplate;
+
+@PasswordRuleConfClass(HaveIBeenPwnedPasswordRuleConf.class)
+public class HaveIBeenPwnedPasswordRule implements PasswordRule {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(HaveIBeenPwnedPasswordRule.class);
+
+    private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
+
+    private HaveIBeenPwnedPasswordRuleConf conf;
+
+    @Override
+    public HaveIBeenPwnedPasswordRuleConf getConf() {
+        return conf;
+    }
+
+    @Override
+    public void setConf(final PasswordRuleConf conf) {
+        if (conf instanceof HaveIBeenPwnedPasswordRuleConf) {
+            this.conf = (HaveIBeenPwnedPasswordRuleConf) conf;
+        } else {
+            throw new IllegalArgumentException(
+                    HaveIBeenPwnedPasswordRuleConf.class.getName() + " expected, got " +
conf.getClass().getName());
+        }
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public void enforce(final User user) {
+        String clearPassword = user.getClearPassword();
+        String password = user.getPassword();
+
+        if (password != null && clearPassword != null) {
+            try {
+                final String sha1 = ENCRYPTOR.encode(clearPassword, CipherAlgorithm.SHA1);
+
+                HttpHeaders headers = new HttpHeaders();
+                headers.set(HttpHeaders.USER_AGENT, "Apache Syncope");
+                ResponseEntity<String> response = new RestTemplate().exchange(
+                        URI.create("https://api.pwnedpasswords.com/range/" + sha1.substring(0,
5)),
+                        HttpMethod.GET,
+                        new HttpEntity<>(null, headers),
+                        String.class);
+                if (StringUtils.isNotBlank(response.getBody())) {
+                    if (Stream.of(response.getBody().split("\\n")).anyMatch(line
+                            -> sha1.equals(sha1.substring(0, 5) + StringUtils.substringBefore(line,
":")))) {
+
+                        throw new PasswordPolicyException("Password pwned");
+                    }
+                }
+            } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException
+                    | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException
e) {
+
+                LOG.error("Could not encode the password value as SHA1", e);
+            } catch (HttpStatusCodeException e) {
+                LOG.error("Error while contacting the PwnedPasswords service", e);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/ebc1e056/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
index 352692e..a08eaf8 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
@@ -29,6 +29,7 @@ import org.apache.syncope.common.lib.policy.AccountRuleConf;
 import org.apache.syncope.common.lib.policy.DefaultAccountRuleConf;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
 import org.apache.syncope.common.lib.policy.DefaultPullCorrelationRuleConf;
+import org.apache.syncope.common.lib.policy.HaveIBeenPwnedPasswordRuleConf;
 import org.apache.syncope.common.lib.policy.PasswordRuleConf;
 import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
 import org.apache.syncope.common.lib.report.AuditReportletConf;
@@ -65,6 +66,7 @@ import org.apache.syncope.core.persistence.jpa.attrvalue.validation.EmailAddress
 import org.apache.syncope.core.persistence.jpa.dao.DefaultAccountRule;
 import org.apache.syncope.core.persistence.jpa.dao.DefaultPasswordRule;
 import org.apache.syncope.core.persistence.jpa.dao.DefaultPullCorrelationRule;
+import org.apache.syncope.core.persistence.jpa.dao.HaveIBeenPwnedPasswordRule;
 import org.apache.syncope.core.provisioning.java.propagation.AzurePropagationActions;
 import org.apache.syncope.core.provisioning.java.propagation.DBPasswordPropagationActions;
 import org.apache.syncope.core.provisioning.java.propagation.GoogleAppsPropagationActions;
@@ -121,6 +123,7 @@ public class ITImplementationLookup implements ImplementationLookup {
         {
             put(TestPasswordRuleConf.class, TestPasswordRule.class);
             put(DefaultPasswordRuleConf.class, DefaultPasswordRule.class);
+            put(HaveIBeenPwnedPasswordRuleConf.class, HaveIBeenPwnedPasswordRule.class);
         }
     };
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/ebc1e056/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
index 1303027..d67be2a 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
@@ -38,6 +38,7 @@ import java.util.UUID;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.time.DateFormatUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.jaxrs.client.WebClient;
@@ -53,6 +54,7 @@ import org.apache.syncope.common.lib.patch.StatusPatch;
 import org.apache.syncope.common.lib.patch.StringReplacePatchItem;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.policy.AccountPolicyTO;
+import org.apache.syncope.common.lib.policy.HaveIBeenPwnedPasswordRuleConf;
 import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.to.BulkAction;
@@ -1341,4 +1343,40 @@ public class UserITCase extends AbstractITCase {
         // verify user was removed by the backend REST service
         assertEquals(404, webClient.get().getStatus());
     }
+
+    @Test
+    public void haveIBeenPwned() {
+        ImplementationTO rule = new ImplementationTO();
+        rule.setKey("HaveIBeenPwnedPasswordRuleConf" + getUUIDString());
+        rule.setEngine(ImplementationEngine.JAVA);
+        rule.setType(ImplementationType.PASSWORD_RULE);
+        rule.setBody(POJOHelper.serialize(new HaveIBeenPwnedPasswordRuleConf()));
+        Response response = implementationService.create(rule);
+        rule.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+
+        PasswordPolicyTO pwdPolicy = new PasswordPolicyTO();
+        pwdPolicy.setDescription("Have I Been Pwned?");
+        pwdPolicy.getRules().add(rule.getKey());
+        pwdPolicy = createPolicy(PolicyType.PASSWORD, pwdPolicy);
+        assertNotNull(pwdPolicy.getKey());
+
+        RealmTO realm = new RealmTO();
+        realm.setName("hibp");
+        realm.setPasswordPolicy(pwdPolicy.getKey());
+        realmService.create(SyncopeConstants.ROOT_REALM, realm);
+
+        UserTO user = getUniqueSampleTO("hibp@syncope.apache.org");
+        user.setRealm("/hibp");
+        user.setPassword("password");
+        try {
+            createUser(user);
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.InvalidUser, e.getType());
+            assertEquals("InvalidPassword: Password pwned", e.getElements().iterator().next());
+        }
+
+        user.setPassword(RandomStringUtils.randomAlphanumeric(10));
+        user = createUser(user).getEntity();
+        assertNotNull(user.getKey());
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/ebc1e056/src/main/asciidoc/reference-guide/concepts/policies.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/reference-guide/concepts/policies.adoc b/src/main/asciidoc/reference-guide/concepts/policies.adoc
index e842834..ebae238 100644
--- a/src/main/asciidoc/reference-guide/concepts/policies.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/policies.adoc
@@ -145,6 +145,38 @@ When defining a password policy, the following information must be provided:
 
 Password rules define constraints to apply to password values.
 
+Some implementations are provided out-of-the-box, custom ones can be provided on given deployment.
+
+[TIP]
+====
+Writing custom account rules means:
+
+. providing configuration parameters in an implementation of
+ifeval::["{snapshotOrRelease}" == "release"]
+https://github.com/apache/syncope/blob/syncope-{docVersion}/common/lib/src/main/java/org/apache/syncope/common/lib/policy/AccountRuleConf.java[AccountRuleConf^]
+endif::[]
+ifeval::["{snapshotOrRelease}" == "snapshot"]
+https://github.com/apache/syncope/blob/master/common/lib/src/main/java/org/apache/syncope/common/lib/policy/AccountRuleConf.java[AccountRuleConf^]
+endif::[]
+. implementing enforcement in an implementation of
+ifeval::["{snapshotOrRelease}" == "release"]
+https://github.com/apache/syncope/blob/syncope-{docVersion}/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccountRule.java[AccountRule^]
+endif::[]
+ifeval::["{snapshotOrRelease}" == "snapshot"]
+https://github.com/apache/syncope/blob/master/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccountRule.java[AccountRule^]
+endif::[]
+annotated via
+ifeval::["{snapshotOrRelease}" == "release"]
+https://github.com/apache/syncope/blob/syncope-{docVersion}/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccountRuleConfClass.java[@AccountRuleConfClass^]
+endif::[]
+ifeval::["{snapshotOrRelease}" == "snapshot"]
+https://github.com/apache/syncope/blob/master/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AccountRuleConfClass.java[@AccountRuleConfClass^]
+endif::[]
+referring to the configuration class
+====
+
+====== Default Password Rule
+
 The default password rule (enforced by
 ifeval::["{snapshotOrRelease}" == "release"]
 https://github.com/apache/syncope/blob/syncope-{docVersion}/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/DefaultPasswordRule.java[DefaultPasswordRule^]
@@ -186,33 +218,24 @@ endif::[]
 * prefixes not permitted - list of strings that cannot be present as a prefix;
 * suffixes not permitted - list of strings that cannot be present as a suffix.
 
-[TIP]
-====
-Writing custom password rules means:
+====== "Have I Been Pwned?" Password Rule
 
-. providing configuration parameters in an implementation of
-ifeval::["{snapshotOrRelease}" == "release"]
-https://github.com/apache/syncope/blob/syncope-{docVersion}/common/lib/src/main/java/org/apache/syncope/common/lib/policy/PasswordRuleConf.java[PasswordRuleConf^]
-endif::[]
-ifeval::["{snapshotOrRelease}" == "snapshot"]
-https://github.com/apache/syncope/blob/master/common/lib/src/main/java/org/apache/syncope/common/lib/policy/PasswordRuleConf.java[PasswordRuleConf^]
-endif::[]
-. implementing enforcement in an implementation of
+This password rule (enforced by
 ifeval::["{snapshotOrRelease}" == "release"]
-https://github.com/apache/syncope/blob/syncope-{docVersion}/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PasswordRule.java[PasswordRule^]
+https://github.com/apache/syncope/blob/syncope-{docVersion}/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/HaveIBeenPwnedPasswordRule.java[HaveIBeenPwnedPasswordRule^]
 endif::[]
 ifeval::["{snapshotOrRelease}" == "snapshot"]
-https://github.com/apache/syncope/blob/master/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PasswordRule.java[PasswordRule^]
+https://github.com/apache/syncope/blob/master/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/HaveIBeenPwnedPasswordRule.java[HaveIBeenPwnedPasswordRule^]
 endif::[]
-annotated via
+and configurable via
 ifeval::["{snapshotOrRelease}" == "release"]
-https://github.com/apache/syncope/blob/syncope-{docVersion}/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PasswordRuleConfClass.java[@PasswordRuleConfClass^]
+https://github.com/apache/syncope/blob/syncope-{docVersion}/common/lib/src/main/java/org/apache/syncope/common/lib/policy/HaveIBeenPwnedPasswordRuleConf.java[HaveIBeenPwnedPasswordRuleConf^]
 endif::[]
 ifeval::["{snapshotOrRelease}" == "snapshot"]
-https://github.com/apache/syncope/blob/master/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PasswordRuleConfClass.java[@PasswordRuleConfClass^]
+https://github.com/apache/syncope/blob/master/common/lib/src/main/java/org/apache/syncope/common/lib/policy/HaveIBeenPwnedPasswordRuleConf.java[HaveIBeenPwnedPasswordRuleConf^]
 endif::[]
-referring to the configuration class
-====
+) checks the provided password values against the popular
+https://haveibeenpwned.com["Have I Been Pwned?"^] service.
 
 [[policies-pull]]
 ==== Pull


Mime
View raw message