diff --git a/src/IDF/EmailAddress.php b/src/IDF/EmailAddress.php new file mode 100644 index 0000000..34b6e68 --- /dev/null +++ b/src/IDF/EmailAddress.php @@ -0,0 +1,96 @@ +_a['table'] = 'idf_emailaddresses'; + $this->_a['model'] = __CLASS__; + $this->_a['cols'] = array( + // It is mandatory to have an "id" column. + 'id' => + array( + 'type' => 'Pluf_DB_Field_Sequence', + //It is automatically added. + 'blank' => true, + ), + 'user' => + array( + 'type' => 'Pluf_DB_Field_Foreignkey', + 'model' => 'Pluf_User', + 'blank' => false, + 'verbose' => __('user'), + ), + 'address' => + array( + 'type' => 'Pluf_DB_Field_Email', + 'blank' => false, + 'verbose' => __('email'), + 'unique' => true, + ), + ); + // WARNING: Not using getSqlTable on the Pluf_User object to + // avoid recursion. + $t_users = $this->_con->pfx.'users'; + $this->_a['views'] = array( + 'join_user' => + array( + 'join' => 'LEFT JOIN '.$t_users + .' ON '.$t_users.'.id='.$this->_con->qn('user'), + 'select' => $this->getSelect().', ' + .$t_users.'.login AS login', + 'props' => array('login' => 'login'), + ) + ); + } + + function get_email_addresses_for_user($user) + { + $addr = $user->get_idf_emailaddress_list(); + $addr[] = (object)array("address" => $user->email, "id" => -1, "user" => $user); + return $addr; + } + + function get_user_for_email_address($email) + { + $sql = new Pluf_SQL('email=%s', array($email)); + $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); + if ($users->count() > 0) { + return $users[0]; + } + $sql = new Pluf_SQL('address=%s', array($email)); + $matches = Pluf::factory('IDF_EmailAddress')->getList(array('filter'=>$sql->gen())); + if ($matches->count() > 0) { + return new Pluf_User($matches[0]->user); + } + return null; + } +} + diff --git a/src/IDF/Form/Password.php b/src/IDF/Form/Password.php index 795a539..98b6949 100644 --- a/src/IDF/Form/Password.php +++ b/src/IDF/Form/Password.php @@ -86,7 +86,7 @@ class IDF_Form_Password extends Pluf_Form $return_url = Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecoveryInputCode'); $tmpl = new Pluf_Template('idf/user/passrecovery-email.txt'); $cr = new Pluf_Crypt(md5(Pluf::f('secret_key'))); - $code = trim($cr->encrypt($user->email.':'.$user->id.':'.time()), + $code = trim($cr->encrypt($user->email.':'.$user->id.':'.time().':primary'), '~'); $code = substr(md5(Pluf::f('secret_key').$code), 0, 2).$code; $url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::passwordRecovery', array($code), array(), false); diff --git a/src/IDF/Form/Register.php b/src/IDF/Form/Register.php index 752deb4..449d776 100644 --- a/src/IDF/Form/Register.php +++ b/src/IDF/Form/Register.php @@ -93,9 +93,7 @@ class IDF_Form_Register extends Pluf_Form function clean_email() { $this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email'])); - $guser = new Pluf_User(); - $sql = new Pluf_SQL('email=%s', $this->cleaned_data['email']); - if ($guser->getCount(array('filter' => $sql->gen())) > 0) { + if (Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($this->cleaned_data['email']) != null) { throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used. If you need, click on the help link to recover your password.'), $this->cleaned_data['email'])); } return $this->cleaned_data['email']; diff --git a/src/IDF/Form/UserAccount.php b/src/IDF/Form/UserAccount.php index d99923e..75867b4 100644 --- a/src/IDF/Form/UserAccount.php +++ b/src/IDF/Form/UserAccount.php @@ -165,6 +165,42 @@ class IDF_Form_UserAccount extends Pluf_Form 'widget' => 'Pluf_Form_Widget_TextareaInput', 'help_text' => __('Paste a SSH or monotone public key. Be careful to not provide your private key here!') )); + + $this->fields['secondary_mail'] = new Pluf_Form_Field_Email( + array('required' => false, + 'label' => __('Add a secondary mail address'), + 'initial' => '', + 'help_text' => __('You will get a mail to confirm that you own the address you specify.'), + )); + } + + private function send_validation_mail($new_email, $secondary_mail=false) + { + if ($secondary_mail) { + $type = "secondary"; + } else { + $type = "primary"; + } + $cr = new Pluf_Crypt(md5(Pluf::f('secret_key'))); + $encrypted = trim($cr->encrypt($new_email.':'.$this->user->id.':'.time().':'.$type), '~'); + $key = substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted; + $url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailDo', array($key), array(), false); + $urlik = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailInputKey', array(), array(), false); + $context = new Pluf_Template_Context( + array('key' => Pluf_Template::markSafe($key), + 'url' => Pluf_Template::markSafe($url), + 'urlik' => Pluf_Template::markSafe($urlik), + 'email' => $new_email, + 'user'=> $this->user, + ) + ); + $tmpl = new Pluf_Template('idf/user/changeemail-email.txt'); + $text_email = $tmpl->render($context); + $email = new Pluf_Mail(Pluf::f('from_email'), $new_email, + __('Confirm your new email address.')); + $email->addTextMessage($text_email); + $email->sendMail(); + $this->user->setMessage(sprintf(__('A validation email has been sent to "%s" to validate the email address change.'), Pluf_esc($new_email))); } /** @@ -190,26 +226,7 @@ class IDF_Form_UserAccount extends Pluf_Form $new_email = $this->cleaned_data['email']; unset($this->cleaned_data['email']); if ($old_email != $new_email) { - $cr = new Pluf_Crypt(md5(Pluf::f('secret_key'))); - $encrypted = trim($cr->encrypt($new_email.':'.$this->user->id.':'.time()), '~'); - $key = substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted; - $url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailDo', array($key), array(), false); - $urlik = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailInputKey', array(), array(), false); - $context = new Pluf_Template_Context( - array('key' => Pluf_Template::markSafe($key), - 'url' => Pluf_Template::markSafe($url), - 'urlik' => Pluf_Template::markSafe($urlik), - 'email' => $new_email, - 'user'=> $this->user, - ) - ); - $tmpl = new Pluf_Template('idf/user/changeemail-email.txt'); - $text_email = $tmpl->render($context); - $email = new Pluf_Mail(Pluf::f('from_email'), $new_email, - __('Confirm your new email address.')); - $email->addTextMessage($text_email); - $email->sendMail(); - $this->user->setMessage(sprintf(__('A validation email has been sent to "%s" to validate the email address change.'), Pluf_esc($new_email))); + $this->send_validation_mail($new_email); } $this->user->setFromFormData($this->cleaned_data); // Add key as needed. @@ -221,6 +238,9 @@ class IDF_Form_UserAccount extends Pluf_Form $key->create(); } } + if ('' !== $this->cleaned_data['secondary_mail']) { + $this->send_validation_mail($this->cleaned_data['secondary_mail'], true); + } if ($commit) { $this->user->update(); @@ -395,15 +415,22 @@ class IDF_Form_UserAccount extends Pluf_Form function clean_email() { $this->cleaned_data['email'] = mb_strtolower(trim($this->cleaned_data['email'])); - $guser = new Pluf_User(); - $sql = new Pluf_SQL('email=%s AND id!=%s', - array($this->cleaned_data['email'], $this->user->id)); - if ($guser->getCount(array('filter' => $sql->gen())) > 0) { + $user = Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($this->cleaned_data['email']); + if ($user != null and $user->id != $this->user->id) { throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used.'), $this->cleaned_data['email'])); } return $this->cleaned_data['email']; } + function clean_secondary_mail() + { + $this->cleaned_data['secondary_mail'] = mb_strtolower(trim($this->cleaned_data['secondary_mail'])); + if (Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($this->cleaned_data['secondary_mail']) != null) { + throw new Pluf_Form_Invalid(sprintf(__('The email "%s" is already used.'), $this->cleaned_data['secondary_mail'])); + } + return $this->cleaned_data['secondary_mail']; + } + function clean_public_key() { $this->cleaned_data['public_key'] = diff --git a/src/IDF/Form/UserChangeEmail.php b/src/IDF/Form/UserChangeEmail.php index c9175bd..308bd6e 100644 --- a/src/IDF/Form/UserChangeEmail.php +++ b/src/IDF/Form/UserChangeEmail.php @@ -53,7 +53,7 @@ class IDF_Form_UserChangeEmail extends Pluf_Form * Throw a Pluf_Form_Invalid exception if the key is not valid. * * @param string Key - * @return array array($new_email, $user_id, time()) + * @return array array($new_email, $user_id, time(), [primary|secondary]) */ public static function validateKey($key) { @@ -63,7 +63,7 @@ class IDF_Form_UserChangeEmail extends Pluf_Form throw new Pluf_Form_Invalid(__('The validation key is not valid. Please copy/paste it from your confirmation email.')); } $cr = new Pluf_Crypt(md5(Pluf::f('secret_key'))); - return explode(':', $cr->decrypt($encrypted), 3); + return explode(':', $cr->decrypt($encrypted), 4); } diff --git a/src/IDF/Migrations/16AddUserMail.php b/src/IDF/Migrations/16AddUserMail.php new file mode 100644 index 0000000..9856339 --- /dev/null +++ b/src/IDF/Migrations/16AddUserMail.php @@ -0,0 +1,43 @@ +model = new IDF_EmailAddress(); + $schema->createTables(); +} + +function IDF_Migrations_16AddUserMail_down($params=null) +{ + $db = Pluf::db(); + $schema = new Pluf_DB_Schema($db); + $schema->model = new IDF_EmailAddress(); + $schema->dropTables(); +} diff --git a/src/IDF/Scm/Git.php b/src/IDF/Scm/Git.php index 608a5e5..dc07e03 100644 --- a/src/IDF/Scm/Git.php +++ b/src/IDF/Scm/Git.php @@ -325,14 +325,12 @@ class IDF_Scm_Git extends IDF_Scm if (!preg_match('/<(.*)>/', $author, $match)) { return null; } - foreach (array('email', 'login') as $what) { - $sql = new Pluf_SQL($what.'=%s', array($match[1])); - $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); - if ($users->count() > 0) { - return $users[0]; - } + $sql = new Pluf_SQL('login=%s', array($match[1])); + $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); + if ($users->count() > 0) { + return $users[0]; } - return null; + return Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($match[1]); } public static function getAnonymousAccessUrl($project, $commit=null) diff --git a/src/IDF/Scm/Mercurial.php b/src/IDF/Scm/Mercurial.php index b52e9f9..a413cb1 100644 --- a/src/IDF/Scm/Mercurial.php +++ b/src/IDF/Scm/Mercurial.php @@ -67,9 +67,7 @@ class IDF_Scm_Mercurial extends IDF_Scm if (!preg_match('/<(.*)>/', $author, $match)) { return null; } - $sql = new Pluf_SQL('email=%s', array($match[1])); - $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); - return ($users->count() > 0) ? $users[0] : null; + return Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($match[1]); } public function getMainBranch() diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index b47dfd0..5ec5bfa 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -400,14 +400,12 @@ class IDF_Scm_Monotone extends IDF_Scm if (!preg_match('/([^ ]+@[^ ]+)/', $author, $match)) { return null; } - foreach (array('email', 'login') as $what) { - $sql = new Pluf_SQL($what.'=%s', array($match[1])); - $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); - if ($users->count() > 0) { - return $users[0]; - } + $sql = new Pluf_SQL('login=%s', array($match[1])); + $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); + if ($users->count() > 0) { + return $users[0]; } - return null; + return Pluf::factory('IDF_EmailAddress')->get_user_for_email_address($match[1]); } /** diff --git a/src/IDF/Views/Api.php b/src/IDF/Views/Api.php index c29ef0e..40753f3 100644 --- a/src/IDF/Views/Api.php +++ b/src/IDF/Views/Api.php @@ -55,6 +55,7 @@ class IDF_Views_Api * Create a new issue. */ public $issueCreate_precond = array('IDF_Precondition::apiSetUser', + 'Pluf_Precondition::loginRequired', 'IDF_Precondition::accessIssues'); public function issueCreate($request, $match) { diff --git a/src/IDF/Views/User.php b/src/IDF/Views/User.php index 6086ae2..739b44c 100644 --- a/src/IDF/Views/User.php +++ b/src/IDF/Views/User.php @@ -127,12 +127,14 @@ class IDF_Views_User $form = new IDF_Form_UserAccount(null, $params); } $keys = $request->user->get_idf_key_list(); + $mailaddrs = Pluf::factory('IDF_EmailAddress')->get_email_addresses_for_user($request->user); return Pluf_Shortcuts_RenderToResponse('idf/user/myaccount.html', array('page_title' => __('Your Account'), 'api_key' => $api_key, 'ext_pass' => $ext_pass, 'keys' => $keys, + 'mailaddrs' => $mailaddrs, 'form' => $form), $request); } @@ -157,6 +159,26 @@ class IDF_Views_User return new Pluf_HTTP_Response_Redirect($url); } + /** + * Delete a mail address. + * + * This is redirecting to the preferences + */ + public $deleteMail_precond = array('Pluf_Precondition::loginRequired'); + public function deleteMail($request, $match) + { + $url = Pluf_HTTP_URL_urlForView('IDF_Views_User::myAccount'); + if ($request->method == 'POST') { + $address = Pluf_Shortcuts_GetObjectOr404('IDF_EmailAddress', $match[1]); + if ($address->user != $request->user->id) { + return new Pluf_HTTP_Response_Forbidden($request); + } + $address->delete(); + $request->user->setMessage(__('The address has been deleted.')); + } + return new Pluf_HTTP_Response_Redirect($url); + } + /** * Enter the key to change an email address. * @@ -190,7 +212,7 @@ class IDF_Views_User $key = $match[1]; $url = Pluf_HTTP_URL_urlForView('IDF_Views_User::changeEmailInputKey'); try { - list($email, $id, $time) = IDF_Form_UserChangeEmail::validateKey($key); + list($email, $id, $time, $type) = IDF_Form_UserChangeEmail::validateKey($key); } catch (Pluf_Form_Invalid $e) { return new Pluf_HTTP_Response_Redirect($url); } @@ -198,8 +220,15 @@ class IDF_Views_User return new Pluf_HTTP_Response_Redirect($url); } // Now we have a change link coming from the right user. - $request->user->email = $email; - $request->user->update(); + if ($type == "primary") { + $request->user->email = $email; + $request->user->update(); + } else { + $mailaddress = new IDF_EmailAddress(); + $mailaddress->user = $request->user; + $mailaddress->address = $email; + $mailaddress->create(); + } $request->user->setMessage(sprintf(__('Your new email address "%s" has been validated. Thank you!'), Pluf_esc($email))); $url = Pluf_HTTP_URL_urlForView('IDF_Views_User::myAccount'); return new Pluf_HTTP_Response_Redirect($url); diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index b341580..3c5af99 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -471,6 +471,11 @@ $ctl[] = array('regex' => '#^/preferences/email/ak/(.*)/$#', 'model' => 'IDF_Views_User', 'method' => 'changeEmailDo'); +$ctl[] = array('regex' => '#^/preferences/email/(\d+)/delete/$#', + 'base' => $base, + 'model' => 'IDF_Views_User', + 'method' => 'deleteMail'); + $ctl[] = array('regex' => '#^/preferences/key/(\d+)/delete/$#', 'base' => $base, 'model' => 'IDF_Views_User', diff --git a/src/IDF/relations.php b/src/IDF/relations.php index ffc2376..ea89d8d 100644 --- a/src/IDF/relations.php +++ b/src/IDF/relations.php @@ -44,6 +44,7 @@ $m['IDF_Commit'] = array('relate_to' => array('IDF_Project', 'Pluf_User')); $m['IDF_Scm_Cache_Git'] = array('relate_to' => array('IDF_Project')); $m['IDF_UserData'] = array('relate_to' => array('Pluf_User')); +$m['IDF_EmailAddress'] = array('relate_to' => array('Pluf_User')); Pluf_Signal::connect('Pluf_Template_Compiler::construct_template_tags_modifiers', array('IDF_Middleware', 'updateTemplateTagsModifiers')); diff --git a/src/IDF/templates/idf/user/myaccount.html b/src/IDF/templates/idf/user/myaccount.html index 0b49a97..f84dbec 100644 --- a/src/IDF/templates/idf/user/myaccount.html +++ b/src/IDF/templates/idf/user/myaccount.html @@ -100,6 +100,14 @@ {$form.f.public_key.help_text} +{trans "Secondary Emails"} + +{$form.f.secondary_mail.labelTag}: +{if $form.f.secondary_mail.errors}{$form.f.secondary_mail.fieldErrors}{/if} +{$form.f.secondary_mail|unsafe}
+{$form.f.secondary_mail.help_text} + + {trans 'Extra password'}: {$ext_pass}
@@ -129,6 +137,15 @@ {/foreach} {/if} +{if count($mailaddrs)>1} + + +{foreach $mailaddrs as $mail}{if $mail.id != -1} +{/if}{/foreach} +
{trans 'Your additional email addresses'}
+{$mail.address}
+
+{/if} {/block} {block context}