diff --git a/indefero/src/IDF/Form/UserAccount.php b/indefero/src/IDF/Form/UserAccount.php
index 37ba62b..7f3381c 100644
--- a/indefero/src/IDF/Form/UserAccount.php
+++ b/indefero/src/IDF/Form/UserAccount.php
@@ -172,8 +172,25 @@ class IDF_Form_UserAccount extends Pluf_Form
'initial' => '',
'help_text' => __('You will get an email to confirm that you own the address you specify.'),
));
+ $otp = "";
+ if ($user_data->otpkey != "")
+ $otp = Pluf_Utils::convBase($this->user->otpkey, '0123456789abcdef', 'abcdefghijklmnopqrstuvwxyz234567');
+ $this->fields['otpkey'] = new Pluf_Form_Field_Varchar(
+ array('required' => false,
+ 'label' => __('Add a OTP Key'),
+ //'initial' => (!empty($user_data->otpkey)) ? : "",
+ //'initial' => (string)(!empty($user_data->otpkey)),
+ 'initial' => $otp,
+ 'help_text' => __('Key must be in base32 for generated QRcode and import into Google Authenticator.'),
+ 'widget_attrs' => array(
+ 'maxlength' => 50,
+ 'size' => 32,
+ ),
+ ));
}
+
+
private function send_validation_mail($new_email, $secondary_mail=false)
{
if ($secondary_mail) {
@@ -243,6 +260,8 @@ class IDF_Form_UserAccount extends Pluf_Form
}
if ($commit) {
+ if ($this->cleaned_data["otpkey"] != "")
+ $this->user->otpkey = Pluf_Utils::convBase($this->cleaned_data["otpkey"], 'abcdefghijklmnopqrstuvwxyz234567', '0123456789abcdef');
$this->user->update();
// FIXME: go the extra mile and check the input lengths for
diff --git a/indefero/src/IDF/Plugin/SyncMercurial.php b/indefero/src/IDF/Plugin/SyncMercurial.php
index 98998b3..097c050 100644
--- a/indefero/src/IDF/Plugin/SyncMercurial.php
+++ b/indefero/src/IDF/Plugin/SyncMercurial.php
@@ -231,6 +231,8 @@ class IDF_Plugin_SyncMercurial
$fcontent .= 'shortname).'>'."\n";
$fcontent .= 'AuthType Basic'."\n";
$fcontent .= 'AuthName "Restricted"'."\n";
+ $fcontent .= 'AuthExternal otpauth\n';
+ $fcontent .= 'AuthBasicProvider external\n';
$fcontent .= sprintf('AuthUserFile %s', Pluf::f('idf_plugin_syncmercurial_passwd_file'))."\n";
$fcontent .= sprintf('Require user %s', $user)."\n";
$fcontent .= ''."\n\n";
diff --git a/indefero/src/IDF/templates/idf/user/myaccount.html b/indefero/src/IDF/templates/idf/user/myaccount.html
index 4fceeae..49969fb 100644
--- a/indefero/src/IDF/templates/idf/user/myaccount.html
+++ b/indefero/src/IDF/templates/idf/user/myaccount.html
@@ -100,6 +100,16 @@
{$form.f.public_key.help_text}
+
+ {$form.f.otpkey.labelTag}: |
+ {if $form.f.otpkey.errors}{$form.f.otpkey.fieldErrors}{/if}
+ {$form.f.otpkey|unsafe} Generate
+ {$form.f.otpkey.help_text}
+
+
+
+ |
+
{trans "Secondary Emails"} |
{$form.f.secondary_mail.labelTag}: |
@@ -153,8 +163,10 @@
{trans 'The extra password is used to access some of the external systems and the API key is used to interact with this website using a program.'}
{/block}
-{block javascript}
{/block}
diff --git a/pluf/src/Pluf/User.php b/pluf/src/Pluf/User.php
index 8a8d17f..e70adee 100644
--- a/pluf/src/Pluf/User.php
+++ b/pluf/src/Pluf/User.php
@@ -21,6 +21,8 @@
#
# ***** END LICENSE BLOCK ***** */
+require_once dirname(__FILE__).'/thirdparty/otp/otphp.php';
+
/**
* User Model.
*/
@@ -155,6 +157,14 @@ class Pluf_User extends Pluf_Model
'verbose' => __('last login'),
'editable' => false,
),
+ 'otpkey' =>
+ array(
+ 'type' => 'Pluf_DB_Field_Varchar',
+ 'blank' => true,
+ 'size' => 50,
+ 'verbose' => __('OTP Key for user'),
+ 'help_text' => __('OTP Key used for authentication against repos.')
+ ),
);
$this->_a['idx'] = array(
'login_idx' =>
@@ -237,9 +247,7 @@ class Pluf_User extends Pluf_Model
{
//$salt = Pluf_Utils::getRandomString(5);
//$this->password = 'sha1:'.$salt.':'.sha1($salt.$password);
- //$this->password = sha1($password);
- //file_put_contents("/tmp/test", $password);
- $this->password = base64_encode(sha1($password, TRUE));
+ $this->password = base64_encode(sha1($password, TRUE));
return true;
}
@@ -254,10 +262,24 @@ class Pluf_User extends Pluf_Model
if ($this->password == '') {
return false;
}
- if ($this->password == base64_encode(sha1($password, TRUE)))
- return true;
- else
- return false;
+ if ($this->otpkey == "")
+ {
+ if ($this->password == base64_encode(sha1($password, TRUE)))
+ return true;
+ else
+ return false;
+ } else {
+ $otp = substr($password, 0, 6);
+ $pass = substr($password, 6);
+ $totp = new \OTPHP\TOTP(strtoupper($this->otpkey));
+ if ($totp->verify($otp) && $this->password == base64_encode(sha1($pass, TRUE)))
+ {
+ return true;
+ } else {
+ return false;
+ }
+
+ }
/*list($algo, $salt, $hash) = explode(':', $this->password);
if ($hash == $algo($salt.$password)) {
return true;
diff --git a/pluf/src/Pluf/Utils.php b/pluf/src/Pluf/Utils.php
index 95d4765..06d5fe0 100644
--- a/pluf/src/Pluf/Utils.php
+++ b/pluf/src/Pluf/Utils.php
@@ -71,6 +71,37 @@ class Pluf_Utils
return $string;
}
+ static public function convBase($numberInput, $fromBaseInput, $toBaseInput)
+ {
+ if ($fromBaseInput==$toBaseInput) return $numberInput;
+ $fromBase = str_split($fromBaseInput,1);
+ $toBase = str_split($toBaseInput,1);
+ $number = str_split($numberInput,1);
+ $fromLen=strlen($fromBaseInput);
+ $toLen=strlen($toBaseInput);
+ $numberLen=strlen($numberInput);
+ $retval='';
+ if ($toBaseInput == '0123456789')
+ {
+ $retval=0;
+ for ($i = 1;$i <= $numberLen; $i++)
+ $retval = bcadd($retval, bcmul(array_search($number[$i-1], $fromBase),bcpow($fromLen,$numberLen-$i)));
+ return $retval;
+ }
+ if ($fromBaseInput != '0123456789')
+ $base10=Pluf_Utils::convBase($numberInput, $fromBaseInput, '0123456789');
+ else
+ $base10 = $numberInput;
+ if ($base10generateOTP($count);
+ }
+
+
+ /**
+ * Verify if a password is valid for a specific counter value
+ *
+ * @param integer $otp the one-time password
+ * @param integer $counter the counter value
+ * @return bool true if the counter is valid, false otherwise
+ */
+ public function verify($otp, $counter) {
+ return ($otp == $this->at($counter));
+ }
+
+ /**
+ * Returns the uri for a specific secret for hotp method.
+ * Can be encoded as a image for simple configuration in
+ * Google Authenticator.
+ *
+ * @param string $name the name of the account / profile
+ * @param integer $initial_count the initial counter
+ * @return string the uri for the hmac secret
+ */
+ public function provisioning_uri($name, $initial_count) {
+ return "otpauth://hotp/".urlencode($name)."?secret={$this->secret}&counter=$initial_count";
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/pluf/src/Pluf/thirdparty/otp/otp.php b/pluf/src/Pluf/thirdparty/otp/otp.php
new file mode 100644
index 0000000..015a4f6
--- /dev/null
+++ b/pluf/src/Pluf/thirdparty/otp/otp.php
@@ -0,0 +1,120 @@
+digits = isset($opt['digits']) ? $opt['digits'] : 6;
+ $this->digest = isset($opt['digest']) ? $opt['digest'] : 'sha1';
+ $this->secret = $secret;
+ }
+
+ /**
+ * Generate a one-time password
+ *
+ * @param integer $input : number used to seed the hmac hash function.
+ * This number is usually a counter (HOTP) or calculated based on the current
+ * timestamp (see TOTP class).
+ * @return integer the one-time password
+ */
+ public function generateOTP($input) {
+ $hash = hash_hmac($this->digest, $this->intToBytestring($input), $this->byteSecret());
+ foreach(str_split($hash, 2) as $hex) { // stupid PHP has bin2hex but no hex2bin WTF
+ $hmac[] = hexdec($hex);
+ }
+ $offset = $hmac[19] & 0xf;
+ $code = ($hmac[$offset+0] & 0x7F) << 24 |
+ ($hmac[$offset + 1] & 0xFF) << 16 |
+ ($hmac[$offset + 2] & 0xFF) << 8 |
+ ($hmac[$offset + 3] & 0xFF);
+ return $code % pow(10, $this->digits);
+ }
+
+ /**
+ * Returns the binary value of the base32 encoded secret
+ * @access private
+ * This method should be private but was left public for
+ * phpunit tests to work.
+ * @return binary secret key
+ */
+ public function byteSecret() {
+ return \Base32::decode($this->secret);
+ }
+
+ /**
+ * Turns an integer in a OATH bytestring
+ * @param integer $int
+ * @access private
+ * @return string bytestring
+ */
+ public function intToBytestring($int) {
+ $result = Array();
+ while($int != 0) {
+ $result[] = chr($int & 0xFF);
+ $int >>= 8;
+ }
+ return str_pad(join(array_reverse($result)), 8, "\000", STR_PAD_LEFT);
+ }
+ }
+}
\ No newline at end of file
diff --git a/pluf/src/Pluf/thirdparty/otp/otphp.php b/pluf/src/Pluf/thirdparty/otp/otphp.php
new file mode 100644
index 0000000..6d5825f
--- /dev/null
+++ b/pluf/src/Pluf/thirdparty/otp/otphp.php
@@ -0,0 +1,26 @@
+interval = isset($opt['interval']) ? $opt['interval'] : 30;
+ parent::__construct($s, $opt);
+ }
+
+ /**
+ * Get the password for a specific timestamp value
+ *
+ * @param integer $timestamp the timestamp which is timecoded and
+ * used to seed the hmac hash function.
+ * @return integer the One Time Password
+ */
+ public function at($timestamp) {
+ return $this->generateOTP($this->timecode($timestamp));
+ }
+
+ /**
+ * Get the password for the current timestamp value
+ *
+ * @return integer the current One Time Password
+ */
+ public function now() {
+ return $this->generateOTP($this->timecode(time()));
+ }
+
+ /**
+ * Verify if a password is valid for a specific counter value
+ *
+ * @param integer $otp the one-time password
+ * @param integer $timestamp the timestamp for the a given time, defaults to current time.
+ * @return bool true if the counter is valid, false otherwise
+ */
+ public function verify($otp, $timestamp = null) {
+ if($timestamp === null)
+ $timestamp = time();
+ return ($otp == $this->at($timestamp));
+ }
+
+ /**
+ * Returns the uri for a specific secret for totp method.
+ * Can be encoded as a image for simple configuration in
+ * Google Authenticator.
+ *
+ * @param string $name the name of the account / profile
+ * @return string the uri for the hmac secret
+ */
+ public function provisioning_uri($name) {
+ return "otpauth://totp/".urlencode($name)."?secret={$this->secret}";
+ }
+
+ /**
+ * Transform a timestamp in a counter based on specified internal
+ *
+ * @param integer $timestamp
+ * @return integer the timecode
+ */
+ protected function timecode($timestamp) {
+ return (int)( (((int)$timestamp * 1000) / ($this->interval * 1000)));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/pluf/src/Pluf/thirdparty/otp/vendor/base32.php b/pluf/src/Pluf/thirdparty/otp/vendor/base32.php
new file mode 100644
index 0000000..9d01898
--- /dev/null
+++ b/pluf/src/Pluf/thirdparty/otp/vendor/base32.php
@@ -0,0 +1,82 @@
+'0', 'B'=>'1', 'C'=>'2', 'D'=>'3', 'E'=>'4', 'F'=>'5', 'G'=>'6', 'H'=>'7',
+ 'I'=>'8', 'J'=>'9', 'K'=>'10', 'L'=>'11', 'M'=>'12', 'N'=>'13', 'O'=>'14', 'P'=>'15',
+ 'Q'=>'16', 'R'=>'17', 'S'=>'18', 'T'=>'19', 'U'=>'20', 'V'=>'21', 'W'=>'22', 'X'=>'23',
+ 'Y'=>'24', 'Z'=>'25', '2'=>'26', '3'=>'27', '4'=>'28', '5'=>'29', '6'=>'30', '7'=>'31'
+ );
+
+ /**
+ * Use padding false when encoding for urls
+ *
+ * @return base32 encoded string
+ * @author Bryan Ruiz
+ **/
+ public static function encode($input, $padding = true) {
+ if(empty($input)) return "";
+ $input = str_split($input);
+ $binaryString = "";
+ for($i = 0; $i < count($input); $i++) {
+ $binaryString .= str_pad(base_convert(ord($input[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
+ }
+ $fiveBitBinaryArray = str_split($binaryString, 5);
+ $base32 = "";
+ $i=0;
+ while($i < count($fiveBitBinaryArray)) {
+ $base32 .= self::$map[base_convert(str_pad($fiveBitBinaryArray[$i], 5,'0'), 2, 10)];
+ $i++;
+ }
+ if($padding && ($x = strlen($binaryString) % 40) != 0) {
+ if($x == 8) $base32 .= str_repeat(self::$map[32], 6);
+ else if($x == 16) $base32 .= str_repeat(self::$map[32], 4);
+ else if($x == 24) $base32 .= str_repeat(self::$map[32], 3);
+ else if($x == 32) $base32 .= self::$map[32];
+ }
+ return $base32;
+ }
+
+ public static function decode($input) {
+ if(empty($input)) return;
+ $paddingCharCount = substr_count($input, self::$map[32]);
+ $allowedValues = array(6,4,3,1,0);
+ if(!in_array($paddingCharCount, $allowedValues)) return false;
+ for($i=0; $i<4; $i++){
+ if($paddingCharCount == $allowedValues[$i] &&
+ substr($input, -($allowedValues[$i])) != str_repeat(self::$map[32], $allowedValues[$i])) return false;
+ }
+ $input = str_replace('=','', $input);
+ $input = str_split($input);
+ $binaryString = "";
+ for($i=0; $i < count($input); $i = $i+8) {
+ $x = "";
+ if(!in_array($input[$i], self::$map)) return false;
+ for($j=0; $j < 8; $j++) {
+ $x .= str_pad(base_convert(@self::$flippedMap[@$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
+ }
+ $eightBits = str_split($x, 8);
+ for($z = 0; $z < count($eightBits); $z++) {
+ $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:"";
+ }
+ }
+ return $binaryString;
+ }
+}
diff --git a/pluf/src/Pluf/thirdparty/otp/vendor/libs.php b/pluf/src/Pluf/thirdparty/otp/vendor/libs.php
new file mode 100644
index 0000000..e509e51
--- /dev/null
+++ b/pluf/src/Pluf/thirdparty/otp/vendor/libs.php
@@ -0,0 +1,26 @@
+