From 4c8703bf015faf8db20210e1c459639429e9a57b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=B4me=20Chilliet?= <come@opensides.be>
Date: Mon, 17 Sep 2018 17:07:22 +0200
Subject: [PATCH] :sparkles: feat(users) Support creating locked users with a
 template

issue #3710
---
 .../class_password-methods-clear.inc          |  2 +-
 .../class_password-methods-crypt.inc          |  4 ++--
 .../class_password-methods-empty.inc          |  4 ++--
 .../class_password-methods-md5.inc            |  4 ++--
 .../class_password-methods-sasl.inc           |  6 ++---
 .../class_password-methods-sha.inc            |  6 ++---
 .../class_password-methods-smd5.inc           |  4 ++--
 .../class_password-methods-ssha.inc           |  6 ++---
 .../class_password-methods.inc                |  5 ++---
 .../attributes/class_BooleanAttribute.inc     |  8 ++++++-
 .../generic/class_UserPasswordAttribute.inc   | 22 +++++++++++++------
 plugins/personal/generic/class_user.inc       | 17 ++------------
 12 files changed, 44 insertions(+), 44 deletions(-)

diff --git a/include/password-methods/class_password-methods-clear.inc b/include/password-methods/class_password-methods-clear.inc
index d84ff16b9..70d13d1c1 100644
--- a/include/password-methods/class_password-methods-clear.inc
+++ b/include/password-methods/class_password-methods-clear.inc
@@ -57,7 +57,7 @@ class passwordMethodClear extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     return $pwd;
   }
diff --git a/include/password-methods/class_password-methods-crypt.inc b/include/password-methods/class_password-methods-crypt.inc
index 5b98a605c..5e76216e5 100644
--- a/include/password-methods/class_password-methods-crypt.inc
+++ b/include/password-methods/class_password-methods-crypt.inc
@@ -54,7 +54,7 @@ class passwordMethodCrypt extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     if ($this->hash == "crypt/standard-des") {
       $salt = "";
@@ -102,7 +102,7 @@ class passwordMethodCrypt extends passwordMethod
       $salt .= "\$";
     }
 
-    return "{CRYPT}".crypt($pwd, $salt);
+    return '{CRYPT}'.($locked ? '!' : '').crypt($pwd, $salt);
   }
 
   function checkPassword($pwd, $hash)
diff --git a/include/password-methods/class_password-methods-empty.inc b/include/password-methods/class_password-methods-empty.inc
index 38361352c..8fc8a7b4b 100644
--- a/include/password-methods/class_password-methods-empty.inc
+++ b/include/password-methods/class_password-methods-empty.inc
@@ -58,9 +58,9 @@ class passwordMethodEmpty extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
-    return '';
+    return ($locked ? static::LOCKVALUE : '');
   }
 
   /*!
diff --git a/include/password-methods/class_password-methods-md5.inc b/include/password-methods/class_password-methods-md5.inc
index 127a5178c..835a8adcd 100644
--- a/include/password-methods/class_password-methods-md5.inc
+++ b/include/password-methods/class_password-methods-md5.inc
@@ -54,9 +54,9 @@ class passwordMethodMd5 extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
-    return  '{MD5}'.base64_encode( pack('H*', md5($pwd)));
+    return  '{MD5}'.($locked ? '!' : '').base64_encode( pack('H*', md5($pwd)));
   }
 
   /*!
diff --git a/include/password-methods/class_password-methods-sasl.inc b/include/password-methods/class_password-methods-sasl.inc
index a170602d1..064e7154b 100644
--- a/include/password-methods/class_password-methods-sasl.inc
+++ b/include/password-methods/class_password-methods-sasl.inc
@@ -85,16 +85,16 @@ class passwordMethodsasl extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     if (empty($this->exop)) {
       if (empty($this->realm)) {
         msg_dialog::display(_('Error'), _('You need to fill saslRealm or saslExop in the configuration screen in order to use SASL'), ERROR_DIALOG);
       }
-      return '{SASL}'.$this->uid.'@'.$this->realm;
+      return '{SASL}'.($locked ? '!' : '').$this->uid.'@'.$this->realm;
     } else {
       // may not be the uid, see saslExop option
-      return '{SASL}'.$this->uid;
+      return '{SASL}'.($locked ? '!' : '').$this->uid;
     }
   }
 
diff --git a/include/password-methods/class_password-methods-sha.inc b/include/password-methods/class_password-methods-sha.inc
index 95f3dd3d6..8e05c7b74 100644
--- a/include/password-methods/class_password-methods-sha.inc
+++ b/include/password-methods/class_password-methods-sha.inc
@@ -53,12 +53,12 @@ class passwordMethodsha extends passwordMethod
    *
    * \param string $password Password
    */
-  function generate_hash($password)
+  function generate_hash($password, $locked = FALSE)
   {
     if (function_exists('sha1')) {
-      $hash = '{SHA}' . base64_encode(pack('H*', sha1($password)));
+      $hash = '{SHA}'.($locked ? '!' : '').base64_encode(pack('H*', sha1($password)));
     } elseif (function_exists('mhash')) {
-      $hash = '{SHA}' . base64_encode(mHash(MHASH_SHA1, $password));
+      $hash = '{SHA}'.($locked ? '!' : '').base64_encode(mHash(MHASH_SHA1, $password));
     } else {
       msg_dialog::display(_('Configuration error'), msgPool::missingext('mhash'), ERROR_DIALOG);
       return FALSE;
diff --git a/include/password-methods/class_password-methods-smd5.inc b/include/password-methods/class_password-methods-smd5.inc
index 8f4246255..655bb75a2 100644
--- a/include/password-methods/class_password-methods-smd5.inc
+++ b/include/password-methods/class_password-methods-smd5.inc
@@ -53,11 +53,11 @@ class passwordMethodsmd5 extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     $salt0  = substr(pack('h*', md5(random_int(0, PHP_INT_MAX))), 0, 8);
     $salt   = substr(pack('H*', md5($salt0 . $pwd)), 0, 4);
-    return '{SMD5}'.base64_encode(pack('H*', md5($pwd . $salt)) . $salt);
+    return '{SMD5}'.($locked ? '!' : '').base64_encode(pack('H*', md5($pwd . $salt)) . $salt);
   }
 
   function checkPassword($pwd, $hash)
diff --git a/include/password-methods/class_password-methods-ssha.inc b/include/password-methods/class_password-methods-ssha.inc
index 92081f1be..c0d287b55 100644
--- a/include/password-methods/class_password-methods-ssha.inc
+++ b/include/password-methods/class_password-methods-ssha.inc
@@ -53,15 +53,15 @@ class passwordMethodssha extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     if (function_exists('sha1')) {
       $salt = substr(pack('h*', md5(random_int(0, PHP_INT_MAX))), 0, 8);
       $salt = substr(pack('H*', sha1($salt.$pwd)), 0, 4);
-      $pwd  = '{SSHA}'.base64_encode(pack('H*', sha1($pwd.$salt)).$salt);
+      $pwd  = '{SSHA}'.($locked ? '!' : '').base64_encode(pack('H*', sha1($pwd.$salt)).$salt);
     } elseif (function_exists('mhash')) {
       $salt = mhash_keygen_s2k(MHASH_SHA1, $pwd, substr(pack('h*', md5(random_int(0, PHP_INT_MAX))), 0, 8), 4);
-      $pwd  = '{SSHA}'.base64_encode(mhash(MHASH_SHA1, $pwd.$salt).$salt);
+      $pwd  = '{SSHA}'.($locked ? '!' : '').base64_encode(mhash(MHASH_SHA1, $pwd.$salt).$salt);
     } else {
       msg_dialog::display(_('Configuration error'), msgPool::missingext('mhash'), ERROR_DIALOG);
       return FALSE;
diff --git a/include/password-methods/class_password-methods.inc b/include/password-methods/class_password-methods.inc
index 342e717b7..5ad44c626 100644
--- a/include/password-methods/class_password-methods.inc
+++ b/include/password-methods/class_password-methods.inc
@@ -77,7 +77,7 @@ class passwordMethod
    *
    * \param string $dn The DN
    */
-  function is_locked($dn = "")
+  function is_locked($dn = '', $pwd = '')
   {
     global $config;
     if (!$this->lockable) {
@@ -85,7 +85,6 @@ class passwordMethod
     }
 
     /* Get current password hash */
-    $pwd = "";
     if (!empty($dn)) {
       $ldap = $config->get_ldap_link();
       $ldap->cd($config->current['BASE']);
@@ -181,7 +180,7 @@ class passwordMethod
     } else {
       /* Unlock entry */
       if ($pwd == passwordMethodEmpty::LOCKVALUE) {
-        $pwd = array();
+        $pwd = '';
       } else {
         $pwd = preg_replace("/(^[^\}]+\})!(.*$)/",  "\\1\\2",   $pwd);
       }
diff --git a/include/simpleplugin/attributes/class_BooleanAttribute.inc b/include/simpleplugin/attributes/class_BooleanAttribute.inc
index 0ba929c97..c1b1bba5a 100644
--- a/include/simpleplugin/attributes/class_BooleanAttribute.inc
+++ b/include/simpleplugin/attributes/class_BooleanAttribute.inc
@@ -25,6 +25,7 @@ class BooleanAttribute extends Attribute
 {
   public $trueValue;
   public $falseValue;
+  protected $templatable = TRUE;
 
   /*! \brief The constructor of BooleanAttribute
    *
@@ -44,6 +45,11 @@ class BooleanAttribute extends Attribute
     $this->falseValue = $falseValue;
   }
 
+  function setTemplatable($bool)
+  {
+    $this->templatable = $bool;
+  }
+
   function inputValue ($value)
   {
     return ($value == $this->trueValue);
@@ -92,7 +98,7 @@ class BooleanAttribute extends Attribute
 
   function renderTemplateInput ()
   {
-    if (!$this->submitForm && empty($this->managedAttributes)) {
+    if (!$this->submitForm && empty($this->managedAttributes) && $this->templatable) {
       /* Allow to set to %askme% if we are not (de)activating other fields */
 
       $id = $this->getHtmlId();
diff --git a/plugins/personal/generic/class_UserPasswordAttribute.inc b/plugins/personal/generic/class_UserPasswordAttribute.inc
index 5b6e3ce80..220db5933 100644
--- a/plugins/personal/generic/class_UserPasswordAttribute.inc
+++ b/plugins/personal/generic/class_UserPasswordAttribute.inc
@@ -58,7 +58,9 @@ class UserPasswordAttribute extends CompositeAttribute
         new HiddenAttribute(
           $ldapName.'_hash'
         ),
-        new HiddenAttribute(
+        new BooleanAttribute(
+          /* Label/Description will only be visible for templates */
+          _('Locked'), _('Whether accounts created with this template will be locked'),
           $ldapName.'_locked', FALSE,
           FALSE
         )
@@ -66,6 +68,7 @@ class UserPasswordAttribute extends CompositeAttribute
       '', '', $acl, $label
     );
     $this->attributes[0]->setSubmitForm(TRUE);
+    $this->attributes[4]->setTemplatable(FALSE);
   }
 
   public function setParent(&$plugin)
@@ -79,6 +82,9 @@ class UserPasswordAttribute extends CompositeAttribute
         $this->attributes[0]->setValue($hash);
         $this->attributes[0]->setDisabled(TRUE);
       }
+      if (!$this->plugin->is_template) {
+        $this->attributes[4]->setVisible(FALSE);
+      }
       $this->checkIfMethodNeedsPassword();
     }
   }
@@ -165,12 +171,17 @@ class UserPasswordAttribute extends CompositeAttribute
   }
 
   function readValues($value)
+  {
+    return $this->readUserPasswordValues($value, $this->plugin->is_template);
+  }
+
+  function readUserPasswordValues($value, $istemplate)
   {
     global $config;
     $pw_storage = $config->get_cfg_value('passwordDefaultHash', 'ssha');
     $locked     = FALSE;
     $password   = '';
-    if ($this->plugin->is_template && !empty($value)) {
+    if ($istemplate && !empty($value)) {
       if ($value == '%askme%') {
         return array('%askme%', '', '', $value, $locked);
       }
@@ -180,10 +191,7 @@ class UserPasswordAttribute extends CompositeAttribute
       $tmp = passwordMethod::get_method($value);
       if (is_object($tmp)) {
         $pw_storage = $tmp->get_hash();
-        $locked     = $tmp->is_locked($this->plugin->dn);
-        if ($this->plugin->is_template) {
-          $value = $tmp->generate_hash($password);
-        }
+        $locked     = $tmp->is_locked('', $value);
       }
     } elseif ($value != '') {
       $pw_storage = 'clear';
@@ -209,7 +217,7 @@ class UserPasswordAttribute extends CompositeAttribute
     $test = new $temp[$values[0]]($this->plugin->dn, $this->plugin);
     $test->set_hash($values[0]);
     if ($this->plugin->is_template) {
-      return $test->generate_hash($values[1]).'|'.$values[1];
+      return $test->generate_hash($values[1], $values[4]).'|'.$values[1];
     } else {
       return $test->generate_hash($values[1]);
     }
diff --git a/plugins/personal/generic/class_user.inc b/plugins/personal/generic/class_user.inc
index 4c33cdcd0..c32c1c099 100644
--- a/plugins/personal/generic/class_user.inc
+++ b/plugins/personal/generic/class_user.inc
@@ -384,22 +384,9 @@ class user extends simplePlugin
     if ($this->uid != '') {
       $skip[] = 'uid';
     }
-    parent::adapt_from_template($attrs, $skip);
+    parent::adapt_from_template($attrs, array_merge($skip, array('userPassword')));
     if (isset($this->attrs['userPassword']) && !in_array('userPassword', $skip)) {
-      list($hash,$password) = explode('|', $this->attrs['userPassword'][0], 2);
-      if (preg_match ('/^{[^}]+}/', $hash)) {
-        $tmp = passwordMethod::get_method($hash);
-        if (is_object($tmp)) {
-          $hash = $tmp->generate_hash($password);
-        }
-      }
-      $this->userPassword = array(
-        '',
-        $password,
-        $password,
-        $hash,
-        $this->attributesAccess['userPassword']->isLocked()
-      );
+      $this->userPassword = $this->attributesAccess['userPassword']->readUserPasswordValues($this->attrs['userPassword'][0], TRUE);
     }
   }
 
-- 
GitLab