/** * Initiates the password reset process on behalf of the user * Generates a unique hash and an expiration time that the hash is valid up until (defaults to 15 minutes) * This key will internally expire (but not be expunged) after that time */ public function initPasswordResetProcess() { if (!$this->validate()) { return false; } $hash = Cii::generateSafeHash(); $expires = strtotime("+15 minutes"); $meta = UserMetadata::model()->findByAttributes(array('user_id' => $this->_user->id, 'key' => 'passwordResetCode')); if ($meta === NULL) { $meta = new UserMetadata(); } $meta->user_id = $this->_user->id; $meta->key = 'passwordResetCode'; $meta->value = $hash; $meta->save(); $meta = UserMetadata::model()->findByAttributes(array('user_id' => $this->_user->id, 'key' => 'passwordResetExpires')); if ($meta === NULL) { $meta = new UserMetadata(); } $meta->user_id = $this->_user->id; $meta->key = 'passwordResetExpires'; $meta->value = $expires; $meta->save(); $emailSettings = new EmailSettings(); $emailSettings->send($this->_user, Yii::t('ciims.email', 'Your Password Reset Information'), 'webroot.themes.' . Cii::getConfig('theme', 'default') . '.views.email.forgot', array('user' => $this->_user, 'hash' => $hash), true, true); // Set success flash Yii::app()->user->setFlash('success', Yii::t('ciims.controllers.Site', 'An email has been sent to {{email}} with further instructions on how to reset your password', array('{{email}}' => $this->email))); return true; }
/** * Creates a new Dashboard card with the $id dashboard_card_{ID_HERE} * $id is a randomly generated hash based upon the unique data of the card.json + name, and contains 2 key pieces of information * 1) The class name * 2) The path in.dot.format * * Once created, the unique instance is added to the user's specific dashboard. This is an Ajax specific request * @param string $id The unique id of the card as defined on creation of said card type * @return bool */ public function actionAdd($id) { if ($id == NULL) { throw new CHttpException(400, Yii::t('Dashboard.main', 'An ID must be specified')); } $config = Configuration::model()->findByAttributes(array('key' => $id)); if ($config == NULL) { throw new CHttpException(400, Yii::t('Dashboard.main', 'No card type exists with that ID')); } $name = CJSON::decode($config->value); Yii::import($name['path'] . '.*'); $card = new $name['class'](); $card->create($id, $name['path']); $data = CJSON::decode($card->getJSON($name['path'])); $data['id'] = $card->id; // Update the user's card information $meta = UserMetadata::model()->findByAttributes(array('user_id' => Yii::app()->user->id, 'key' => 'dashboard')); if ($meta == NULL) { $meta = new UserMetadata(); $meta->key = 'dashboard'; $meta->user_id = Yii::app()->user->id; $meta->value = array(); } if (!is_array($meta->value)) { $order = CJSON::decode($meta->value); } else { $order = $meta->value; } $order[] = $card->id; $meta->value = CJSON::encode($order); $meta->save(); return $card->render(); }
/** * Sends an invite to a new user * @return boolean */ public function invite() { if (!$this->validate()) { return false; } $user = new Users(); $user->attributes = array('email' => $this->email, 'firstName' => null, 'lastName' => null, 'displayName' => null, 'password' => null, 'user_role' => 5, 'status' => Users::PENDING_INVITATION); // Create a new user, but bypass validation if ($user->save(false)) { $meta = new UserMetadata(); $meta->attributes = array('user_id' => $user->id, 'key' => 'invitationKey', 'value' => Cii::generateSafeHash()); // If the key was savedm send the email out if ($meta->save()) { $emailSettings = new EmailSettings(); $emailSettings->send($user, Yii::t('ciims.models.InvitationForm', "You've Been Invited..."), 'webroot.themes.' . Cii::getConfig('theme', 'default') . '.views.email.invite', array('user' => $user, 'hash' => $meta->value), true, true); return true; } $user->delete(); } return false; }
/** * Generates a new change key * @return boolean */ public function setNewEmailChangeKey() { $metadata = UserMetadata::model()->findByAttributes(array('user_id' => $this->_user->id, 'key' => 'newEmailAddressChangeKey')); if ($metadata == NULL) { $metadata = new UserMetadata(); $metadata->attributes = array('user_id' => $this->_user->id, 'key' => 'newEmailAddressChangeKey'); } // Generate a new key $metadata->value = Cii::generateSafeHash(); // Save the record if ($metadata->save()) { return $metadata->value; } throw new CHttpException(500, Yii::t('ciims.ProfileForm', 'Unable to save change key')); }
/** * Creates a new user, and sends the appropriate messaging out * @return boolean */ public function save($sendEmail = true) { if (!$this->validate()) { return false; } $this->_user = new Users(); // Set the model attributes $this->_user->attributes = array('email' => $this->email, 'password' => $this->password, 'username' => $this->username, 'user_role' => 1, 'status' => $sendEmail ? Users::PENDING_INVITATION : Users::ACTIVE); // If we saved the user model, return true if ($this->_user->save()) { // This class my be extended by other modules, in which case we don't need to send an activation form if we don't want need it to. if ($sendEmail) { $meta = new UserMetadata(); $meta->attributes = array('user_id' => $this->_user->id, 'key' => 'activationKey', 'value' => Cii::generateSafeHash()); $meta->save(); // Send the registration email $emailSettings = new EmailSettings(); $emailSettings->send($this->_user, Yii::t('ciims.email', 'Activate Your Account'), 'base.themes.' . Cii::getConfig('theme', 'default') . '.views.email.register', array('user' => $this->_user, 'hash' => $meta->value), true, true); } return true; } return false; }
/** * Retrieves all comments that the user has flagged * @return array */ public function getFlaggedComments($model = false) { $flags = UserMetadata::model()->findByAttributes(array('user_id' => $this->id, 'key' => 'flaggedComments')); if ($flags === NULL) { $flags = new UserMetadata(); $flags->attributes = array('user_id' => $this->id, 'key' => 'flaggedComments', 'value' => CJSON::encode(array())); $flags->save(); if ($model == true) { return $flags; } return array(); } if ($model == true) { return $flags; } return CJSON::decode($flags->value); }
/** * Provides functionality for "liking and un-liking" a post * @param int $id The Content ID */ public function actionLike($id = NULL) { $this->layout = false; header('Content-type: application/json'); // Load the content $content = ContentMetadata::model()->findByAttributes(array('content_id' => $id, 'key' => 'likes')); if ($content === NULL) { $content = new ContentMetadata(); $content->content_id = $id; $content->key = 'likes'; $content->value = 0; } if ($id === NULL || $content === NULL) { echo CJavaScript::jsonEncode(array('status' => 'error', 'message' => Yii::t('ciims.controllers.Content', 'Unable to access post'))); return Yii::app()->end(); } // Load the user likes, create one if it does not exist $user = UserMetadata::model()->findByAttributes(array('user_id' => Yii::app()->user->id, 'key' => 'likes')); if ($user === NULL) { $user = new UserMetadata(); $user->user_id = Yii::app()->user->id; $user->key = 'likes'; $user->value = json_encode(array()); } $type = "inc"; $likes = json_decode($user->value, true); if (in_array($id, array_values($likes))) { $type = "dec"; $content->value -= 1; if ($content->value <= 0) { $content->value = 0; } $element = array_search($id, $likes); unset($likes[$element]); } else { $content->value += 1; array_push($likes, $id); } $user->value = json_encode($likes); if (!$user->save()) { echo CJavaScript::jsonEncode(array('status' => 'error', 'message' => Yii::t('ciims.controllers.Content', 'Unable to save user like'))); return Yii::app()->end(); } if (!$content->save()) { echo CJavaScript::jsonEncode(array('status' => 'error', 'message' => Yii::t('ciims.controllers.Content', 'Unable to save like'))); return Yii::app()->end(); } echo CJavaScript::jsonEncode(array('status' => 'success', 'type' => $type, 'message' => Yii::t('ciims.controllers.Content', 'Liked saved'))); return Yii::app()->end(); }
/** * Registration page * **/ public function actionRegister() { $this->setPageTitle(Yii::t('ciims.controllers.Site', '{{app_name}} | {{label}}', array('{{app_name}}' => Cii::getConfig('name', Yii::app()->name), '{{label}}' => Yii::t('ciims.controllers.Site', 'Sign Up')))); $this->layout = '//layouts/main'; $model = new RegisterForm(); $user = new Users(); $error = ''; if (isset($_POST) && !empty($_POST)) { $model->attributes = $_POST['RegisterForm']; if ($model->validate()) { if (!function_exists('password_hash')) { require_once YiiBase::getPathOfAlias('ext.bcrypt.bcrypt') . '.php'; } // Bcrypt the initial password instead of just using the basic hashing mechanism $hash = Users::model()->encryptHash(Cii::get($_POST['RegisterForm'], 'email'), Cii::get($_POST['RegisterForm'], 'password'), Yii::app()->params['encryptionKey']); $cost = Cii::getBcryptCost(); $password = password_hash($hash, PASSWORD_BCRYPT, array('cost' => $cost)); $user->attributes = array('email' => Cii::get($_POST['RegisterForm'], 'email'), 'password' => $password, 'firstName' => NULL, 'lastName' => NULL, 'displayName' => Cii::get($_POST['RegisterForm'], 'displayName'), 'user_role' => 1, 'status' => Users::INACTIVE); try { if ($user->save()) { $hash = mb_strimwidth(hash("sha256", md5(time() . md5(hash("sha512", time())))), 0, 16); $meta = new UserMetadata(); $meta->user_id = $user->id; $meta->key = 'activationKey'; $meta->value = $hash; $meta->save(); // Send the registration email $this->sendEmail($user, Yii::t('ciims.email', 'Activate Your Account'), '//email/register', array('user' => $user, 'hash' => $hash), true, true); $this->redirect($this->createUrl('/register-success')); return; } } catch (CDbException $e) { $model->addError(null, Yii::t('ciims.controllers.Site', 'The email address has already been associated to an account. Do you want to login instead?')); } } } $this->render('register', array('model' => $model, 'error' => $error, 'user' => $user)); }
/** * Internal API endpoint for inviting new users to join the site * **/ public function actionCreate() { $validator = new CEmailValidator(); if (!$validator->validateValue(Cii::get($_POST, 'email', NULL))) { throw new CHttpException(400, Yii::t('Dashboard.main', 'The email address you provided is invalid.')); } if (Users::model()->countByAttributes(array('email' => Cii::get($_POST, 'email', NULL)))) { throw new CHttpException(400, Yii::t('Dashboard.main', 'A user with that email address already exists.')); } $user = new Users(); $user->attributes = array('status' => Users::PENDING_INVITATION, 'email' => Cii::get($_POST, 'email', NULL), 'user_role' => 5, 'about' => '', 'password' => '', 'displayName' => '', 'firstName' => '', 'lastName' => ''); $user->created = new CDbExpression('UTC_TIMESTAMP()'); $user->updated = new CDbExpression('UTC_TIMESTAMP()'); // Save the user, and ignore all validation if ($user->save(false)) { $hash = mb_strimwidth(hash("sha256", md5(time() . md5(hash("sha512", time())))), 0, 16); $meta = new UserMetadata(); $meta->user_id = $user->id; $meta->key = 'activationKey'; $meta->value = $hash; $meta->save(); // Send an invitation email $this->sendEmail($user, Yii::t('Dashboard.email', "You've Been Invited To Join a Blog!"), '/email/invite', array('user' => $user, 'hash' => $hash), true, true); // End the request return $this->renderPartial('/users/userList', array('data' => $user)); } throw new CHttpException(400, Yii::t('Dashboard.main', 'An unexpected error occured fulfilling your request.')); }
/** * Main mehod to handle login attempts. If the user passes authentication with their * chosen provider then it displays a form for them to choose their username and email. * The email address they choose is *not* verified. * * @throws Exception if a provider isn't supplied, or it has non-alpha characters */ private function hybridAuth($provider = NULL) { if ($provider == NULL) { throw new CException(Yii::t('Hybridauth.main', "You haven't supplied a provider")); } if (!function_exists('password_hash')) { require_once YiiBase::getPathOfAlias('ext.bcrypt.bcrypt') . '.php'; } $identity = new RemoteUserIdentity(); if ($identity->authenticate($provider)) { // If we found a user and authenticated them, bind this data to the user if it does not already exist $user = UserMetadata::model()->findByAttributes(array('key' => $provider . 'Provider', 'value' => $identity->userData['id'])); if ($user === NULL) { $user = new UserMetadata(); $user->user_id = Users::model()->findByAttributes(array('email' => $identity->userData['email']))->id; $user->key = $provider . 'Provider'; $user->value = $identity->userData['id']; $user->save(); } $user = Users::model()->findByPk($user->user_id); // Log the user in with just their email address $model = new LoginForm(true); // CiiMS 1.7 provided authentication schemes against md5 hashes. If we have any users in the system who still have md5 hashes // as their password, allow authentication, but immediatly upgrade their password to something more secure. $model->attributes = array('username' => isset($user->email) ? $user->email : $identity->userData['email'], 'password' => md5('PUBUSER')); // validate user input and redirect to the previous page if valid if ($model->validate() && $model->login()) { // Upgradee the user's password to bcrypt so they don't stick out in database dumps if ($user->password == md5('PUBUSER')) { $user->password = password_hash($identity->userData['email'], PASSWORD_BCRYPT, array('cost' => 13)); $user->save(); } $this->redirect(Yii::app()->user->returnUrl); } // If the prevvious authentication failed, then the user has been upgraded, and we should attempt to use the bcrypt hash isntead of the md5 one $model->attributes = array('username' => isset($user->email) ? $user->email : $identity->userData['email'], 'password' => password_hash($identity->userData['email'], PASSWORD_BCRYPT, array('cost' => 13))); // validate user input and redirect to the previous page if valid if ($model->validate() && $model->login()) { $this->redirect(Yii::app()->user->returnUrl); } throw new CException(Yii::t('Hybridauth.main', 'Unable to bind to local user')); } else { if ($identity->errorCode == RemoteUserIdentity::ERROR_USERNAME_INVALID) { // If the user authenticatd against the remote network, but we didn't find them locally // Create a local account, and bind this information to it. $user = new Users(); $user->attributes = array('email' => $identity->userData['email'], 'password' => password_hash($identity->userData['email'], PASSWORD_BCRYPT, array('cost' => 13)), 'firstName' => Cii::get($identity->userData, 'firstName', 'UNKNOWN'), 'lastName' => Cii::get($identity->userData, 'lastName', 'UNKNOWN'), 'displayName' => $provider == 'twitter' ? $identity->userData['firstName'] : $identity->userData['displayName'], 'user_role' => 1, 'status' => 1); $user->save(); $meta = new UserMetadata(); $meta->user_id = $user->id; $meta->key = $provider . 'Provider'; $meta->value = $identity->userData['id']; $meta->save(); // Log the user in with just their email address $model = new LoginForm(true); $model->attributes = array('username' => $identity->userData['email'], 'password' => password_hash($identity->userData['email'], PASSWORD_BCRYPT, array('cost' => 13))); // validate user input and redirect to the previous page if valid if ($model->validate() && $model->login()) { $this->redirect(Yii::app()->user->returnUrl); } throw new CException(Yii::t('Hybridauth.main', 'Unable to bind new user locally')); } else { // Panic? throw new CException(Yii::t('Hybridauth.main', 'We were able to authenticate you against the remote network, but could not sign you in locally.')); } } }
/** * Bind behaviors for changing the user's email, and allow them to make the appropriate changes on their end. * The intention behind this, is that the user has to first, verify that they requested the change, and second * verify that they own both email addresses. * * The intention behind this is to protect the user from changes to their account, either by an administrator or a malicious user. * This doesn't protect from database attacks, it only protects from malicious attacks from within CiiMS. * * @return parent::afterSave(); */ public function beforeSave() { // If the user's email address is about to change if (isset($this->_oldAttributes['email']) && $this->_oldAttributes['email'] != $this->email) { // Store the new email address $newEmail = $this->email; // Reset the email addres and block the change internally $this->email = $this->_oldAttributes['email']; // Save the NEW email address in the database as a metadata record $meta = UserMetadata::model()->findByAttributes(array('user_id' => $this->id, 'key' => 'newEmailAddress')); if ($meta === NULL) { $meta = new UserMetadata(); } $meta->user_id = $this->id; $meta->key = 'newEmailAddress'; $meta->value = $newEmail; $meta->save(); $meta = UserMetadata::model()->findByAttributes(array('user_id' => $this->id, 'key' => 'newEmailAddressChangeKey')); if ($meta === NULL) { $meta = new UserMetadata(); } $meta->user_id = $this->id; $meta->key = 'newEmailAddressChangeKey'; $key = $meta->value = md5(md5($newEmail . time()) . Yii::app()->params['encryptionKey']); $meta->save(); // Delete all API tokens associated to this account $response = Yii::app()->db->createCommand('DELETE FROM user_metadata WHERE `key` LIKE "api_key%" AND user_id = :id')->bindParam(':id', $this->id)->execute(); // Fire off an email to the OLD email address asking them VERIFY the change $response = Yii::app()->controller->sendEmail($this, Yii::t('Dashboard.email', 'CiiMS Email Change Notification'), 'application.modules.dashboard.views.email.email-change', array('key' => $key)); } return parent::beforeSave(); }
/** * Provides functionality to create a new user. This method will create a new user if the user does not already exist. * And then it will send an email invitation to the user so that they can join the blog. * @return array */ private function createUser() { $validator = new CEmailValidator(); if (!$validator->validateValue(Cii::get($_POST, 'email', NULL))) { throw new CHttpException(400, Yii::t('Api.user', 'The email address you provided is invalid.')); } if (Users::model()->countByAttributes(array('email' => Cii::get($_POST, 'email', NULL)))) { throw new CHttpException(400, Yii::t('Api.user', 'A user with that email address already exists.')); } // Passowrds cannot be set through the API unset($_POST['password']); // Relational data cannot be set through this API unset($_POST['comments']); unset($_POST['content']); unset($_POST['tags']); unset($_POST['metadata']); unset($_POST['role']); $user = new Users(); $user->attributes = array('status' => Users::PENDING_INVITATION, 'email' => Cii::get($_POST, 'email', NULL), 'user_role' => 1, 'about' => '', 'password' => '', 'displayName' => '', 'firstName' => '', 'lastName' => ''); $user->attributes = $_POST; $user->created = new CDbExpression('UTC_TIMESTAMP()'); $user->updated = new CDbExpression('UTC_TIMESTAMP()'); // Save the user, and ignore all validation if ($user->save(false)) { $hash = mb_strimwidth(hash("sha256", md5(time() . md5(hash("sha512", time())))), 0, 16); $meta = new UserMetadata(); $meta->user_id = $user->id; $meta->key = 'activationKey'; $meta->value = $hash; $meta->save(); // Send an invitation email $this->sendEmail($user, Yii::t('Api.user', "You've Been Invited To Join a Blog!"), 'application.modules.dashboard.views.email.invite', array('user' => $user, 'hash' => $hash), true, true); // End the request return $user->getAPIAttributes(array('password', 'activation_key')); } throw new CHttpException(400, Yii::t('Api.user', 'An unexpected error occured fulfilling your request.')); }
/** * * @param unknown $slug */ public function actionEdit($slug) { $user = User::model()->findbyAttributes(array('slug' => $slug, 'status' => User::STATUS_ACTIVE)); if (isset($user)) { // save the current mail $oldMail = $user->email; // if there isn't an instance of UserPersonalInfo we have to create it. if (!isset($user->personalInfo)) { $user->personalInfo = new UserPersonalInfo(); } if (isset($_POST['User']) && isset($_POST['UserPersonalInfo'])) { $user->attributes = $_POST['User']; // check if at least one of these field is setted if (!empty($_POST['UserPersonalInfo']['first_name']) || !empty($_POST['UserPersonalInfo']['last_name']) || !empty($_POST['UserPersonalInfo']['gender']) || !empty($_POST['UserPersonalInfo']['birthdate'])) { $user->personalInfo->attributes = $_POST['UserPersonalInfo']; $user->personalInfo->user_id = $user->id; } // used to check if the user has already changed his own mail if ($oldMail != $user->email) { $user->setScenario('editProfile'); } // validation of the models $this->performAjaxValidationProfileForm($user); //verify if the mail was changed if ($oldMail != $user->email) { $usersMetadata = new UserMetadata(); $usersMetadata->user_id = $user->id; $usersMetadata->key = User::METADATA_KEY_EMAIL_CHANGE_NEW_ADDRESS; $usersMetadata->value = $user->email; $usersMetadata->save(); $usersMetadata2 = new UserMetadata(); $usersMetadata2->user_id = $user->id; $usersMetadata2->key = User::METADATA_KEY_EMAIL_CHANGE_TOKEN; $usersMetadata2->value = UserHelper::generateActivationToken(); $usersMetadata2->save(); //confirmation mail $activation_link = Yii::app()->createAbsoluteUrl('profile/email?token=' . $usersMetadata2->value); BasicNotifier::sendTemplatedEmail($oldMail, Yii::t('ProfileModule.edit', 'email.subject.changeEmail'), 'profile/change_email_confirmation', array('{CHANGE_EMAIL_ADDRESS}' => $user->email, '{CHANGE_EMAIL_CONFIRMATION_LINK}' => $activation_link), Yii::app()->session['lang']); /* set flash messages */ Yii::app()->user->setFlash('pending', 'email'); //assign to as mail of the user his old mail. The new mail before to replace the old one have to be confirmed $user->email = $oldMail; } /* save user and related models */ $user->withRelated->save(true, array('personalInfo')); /* set flash messages */ Yii::app()->user->setFlash('success', true); /* back to profile */ $this->redirect(Yii::app()->createUrl('profile')); } $this->render('edit', array('user' => $user, 'userPersonalInfo' => $user->personalInfo, 'slug' => $slug)); } }