Enh #117: Refactored authentication (pluggable yii2-authclient support)

This commit is contained in:
Lucas Bartholemy 2016-01-15 01:53:10 +01:00
parent de93b1b625
commit 8191d4ef86
47 changed files with 2294 additions and 829 deletions

View File

@ -18,6 +18,7 @@
"yiisoft/yii2": ">=2.0.4",
"yiisoft/yii2-bootstrap": "*",
"yiisoft/yii2-swiftmailer": "*",
"yiisoft/yii2-authclient": "*",
"raoul2000/yii2-jcrop-widget": "*",
"cebe/markdown": "1.0.2",
"yiisoft/yii2-jui": "^2.0",

View File

@ -24,10 +24,17 @@ class HForm extends \yii\base\Component
public $models = array();
public $definition = array();
public function __construct($definition, $primaryModel = null)
/**
* @var boolean manually mark form as submitted
*/
public $markedAsSubmitted = false;
public function __construct($definition = [], $primaryModel = null)
{
$this->definition = $definition;
$this->primaryModel = $primaryModel;
$this->init();
}
public function submitted($buttonName = "")
@ -43,6 +50,8 @@ class HForm extends \yii\base\Component
}
return true;
}
} elseif ($this->markedAsSubmitted) {
return true;
}
return false;
@ -66,6 +75,17 @@ class HForm extends \yii\base\Component
return !$hasErrors;
}
public function clearErrors()
{
if ($this->primaryModel !== null) {
$this->primaryModel->clearErrors();
}
foreach ($this->models as $model) {
$model->clearErrors();
}
}
public function save()
{
$hasErrors = false;

View File

@ -85,6 +85,10 @@ $config = [
'charset' => 'utf8',
'enableSchemaCache' => true,
],
'authClientCollection' => [
'class' => 'humhub\modules\user\authclient\Collection',
'clients' => [],
],
],
'params' => [
'installed' => false,

View File

@ -0,0 +1,72 @@
Authentication
==============
## Example: GitHub Authentication
- Obtain Application ID and Secret from GitHub
- Add following block to protected/config/common.php
```
// ...
'components' => [
// ...
'authClientCollection' => [
'class' => 'yii\authclient\Collection',
'clients' => [
'github' => [
'class' => 'yii\authclient\clients\GitHub',
'clientId' => '--->your-client-id<---',
'clientSecret' => '--->your-client-secret<---',
'normalizeUserAttributeMap' => [
'username' => 'login',
'firstname' => function ($attributes) {
list($f, $l) = mb_split(' ', $attributes['name'], 2);
return $f;
},
'lastname' => function ($attributes) {
list($f, $l) = mb_split(' ', $attributes['name'], 2);
return $l;
},
],
],
],
],
// ..
],
// ...
```
## Example: Facebook
- Obtain Application ID and Secret from Facebook
- Add following block to protected/config/common.php
```
// ...
'components' => [
// ...
'authClientCollection' => [
'class' => 'yii\authclient\Collection',
'clients' => [
'facebook' => [
'class' => 'yii\authclient\clients\Facebook',
'clientId' => '1662633133992750',
'clientSecret' => '3c786f625a26f7f3649bd5cc8d8c6a61',
'normalizeUserAttributeMap' => [
'username' => 'name',
'firstname' => function ($attributes) {
list($f, $l) = mb_split(' ', $attributes['name'], 2);
return $f;
},
'lastname' => function ($attributes) {
list($f, $l) = mb_split(' ', $attributes['name'], 2);
return $l;
},
],
],
],
],
// ..
],
// ...
```

View File

@ -171,7 +171,7 @@ class SelfTest
// Checks LDAP Extension
$title = 'LDAP Support';
if (\humhub\modules\user\libs\Ldap::isAvailable()) {
if (\humhub\modules\user\authclient\ZendLdapClient::isLdapAvailable()) {
$checks[] = array(
'title' => Yii::t('base', $title),
'state' => 'OK'

View File

@ -0,0 +1,51 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\libs;
/**
* StringHelper
*
* @since 1.1
* @author luke
*/
class StringHelper extends \yii\helpers\StringHelper
{
/**
* Converts (LDAP) Binary to Ascii GUID
*
* @param string $object_guid a binary string containing data.
* @return string the guid
*/
public static function binaryToGuid($object_guid)
{
$hex_guid = bin2hex($object_guid);
if ($hex_guid == "")
return "";
$hex_guid_to_guid_str = '';
for ($k = 1; $k <= 4; ++$k) {
$hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
}
$hex_guid_to_guid_str .= '-';
for ($k = 1; $k <= 2; ++$k) {
$hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
}
$hex_guid_to_guid_str .= '-';
for ($k = 1; $k <= 2; ++$k) {
$hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
}
$hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
return strtolower($hex_guid_to_guid_str);
}
}

View File

@ -187,6 +187,7 @@ class SettingController extends Controller
Yii::$app->response->redirect(Url::toRoute('/admin/setting/authentication-ldap'));
}
$enabled = false;
$userCount = 0;
$errorMessage = "";
@ -194,11 +195,9 @@ class SettingController extends Controller
if (Setting::Get('enabled', 'authentication_ldap')) {
$enabled = true;
try {
if (Ldap::getInstance()->ldap !== null) {
$userCount = Ldap::getInstance()->ldap->count(Setting::Get('userFilter', 'authentication_ldap'), Setting::Get('baseDn', 'authentication_ldap'), \Zend\Ldap\Ldap::SEARCH_SCOPE_SUB);
} else {
$errorMessage = Yii::t('AdminModule.controllers_SettingController', 'Could not load LDAP! - Check PHP Extension');
}
$ldapAuthClient = new \humhub\modules\user\authclient\ZendLdapClient();
$ldap = $ldapAuthClient->getLdap();
$userCount = $ldap->count(Setting::Get('userFilter', 'authentication_ldap'), Setting::Get('baseDn', 'authentication_ldap'), \Zend\Ldap\Ldap::SEARCH_SCOPE_SUB);
} catch (\Zend\Ldap\Exception\LdapException $ex) {
$errorMessage = $ex->getMessage();
} catch (Exception $ex) {

View File

@ -11,9 +11,9 @@ namespace humhub\modules\admin\controllers;
use Yii;
use yii\helpers\Url;
use humhub\compat\HForm;
use humhub\modules\user\models\forms\Registration;
use humhub\modules\admin\components\Controller;
use humhub\modules\user\models\User;
use humhub\modules\user\models\Password;
use humhub\modules\user\models\Group;
/**
@ -80,15 +80,6 @@ class UserController extends Controller
'super_admin' => array(
'type' => 'checkbox',
),
'auth_mode' => array(
'type' => 'dropdownlist',
'disabled' => 'disabled',
'class' => 'form-control',
'items' => array(
User::AUTH_MODE_LOCAL => Yii::t('AdminModule.controllers_UserController', 'Local'),
User::AUTH_MODE_LDAP => Yii::t('AdminModule.controllers_UserController', 'LDAP'),
),
),
'status' => array(
'type' => 'dropdownlist',
'class' => 'form-control',
@ -149,110 +140,13 @@ class UserController extends Controller
public function actionAdd()
{
$userModel = new User();
$userModel->scenario = 'registration';
$userPasswordModel = new Password();
$userPasswordModel->scenario = 'registration';
$profileModel = $userModel->profile;
$profileModel->scenario = 'registration';
// Build Form Definition
$definition = array();
$definition['elements'] = array();
$groupModels = \humhub\modules\user\models\Group::find()->orderBy('name ASC')->all();
$defaultUserGroup = \humhub\models\Setting::Get('defaultUserGroup', 'authentication_internal');
$groupFieldType = "dropdownlist";
if ($defaultUserGroup != "") {
$groupFieldType = "hidden";
} else if (count($groupModels) == 1) {
$groupFieldType = "hidden";
$defaultUserGroup = $groupModels[0]->id;
$registration = new Registration();
$registration->enableEmailField = true;
$registration->enableUserApproval = false;
if ($registration->submitted('save') && $registration->validate() && $registration->register()) {
return $this->redirect(Url::to(['edit', 'id' => $registration->getUser()->id]));
}
if ($groupFieldType == 'hidden') {
$userModel->group_id = $defaultUserGroup;
}
// Add User Form
$definition['elements']['User'] = array(
'type' => 'form',
'title' => Yii::t('UserModule.controllers_AuthController', 'Account'),
'elements' => array(
'username' => array(
'type' => 'text',
'class' => 'form-control',
'maxlength' => 25,
),
'email' => array(
'type' => 'text',
'class' => 'form-control',
'maxlength' => 100,
),
'group_id' => array(
'type' => $groupFieldType,
'class' => 'form-control',
'items' => \yii\helpers\ArrayHelper::map($groupModels, 'id', 'name'),
'value' => $defaultUserGroup,
),
),
);
// Add User Password Form
$definition['elements']['UserPassword'] = array(
'type' => 'form',
#'title' => 'Password',
'elements' => array(
'newPassword' => array(
'type' => 'password',
'class' => 'form-control',
'maxlength' => 255,
),
'newPasswordConfirm' => array(
'type' => 'password',
'class' => 'form-control',
'maxlength' => 255,
),
),
);
// Add Profile Form
$definition['elements']['Profile'] = array_merge(array('type' => 'form'), $profileModel->getFormDefinition());
// Get Form Definition
$definition['buttons'] = array(
'save' => array(
'type' => 'submit',
'class' => 'btn btn-primary',
'label' => Yii::t('UserModule.controllers_AuthController', 'Create account'),
),
);
$form = new HForm($definition);
$form->models['User'] = $userModel;
$form->models['UserPassword'] = $userPasswordModel;
$form->models['Profile'] = $profileModel;
if ($form->submitted('save') && $form->validate()) {
$this->forcePostRequest();
$form->models['User']->status = User::STATUS_ENABLED;
if ($form->models['User']->save()) {
// Save User Profile
$form->models['Profile']->user_id = $form->models['User']->id;
$form->models['Profile']->save();
// Save User Password
$form->models['UserPassword']->user_id = $form->models['User']->id;
$form->models['UserPassword']->setPassword($form->models['UserPassword']->newPassword);
$form->models['UserPassword']->save();
return $this->redirect(Url::to(['index']));
}
}
return $this->render('add', array('hForm' => $form));
return $this->render('add', array('hForm' => $registration));
}
/**

View File

@ -4,7 +4,6 @@ use yii\widgets\ActiveForm;
use humhub\compat\CHtml;
use humhub\models\Setting;
use yii\helpers\Url;
?>
<div class="panel panel-default">
<div class="panel-heading"><?php echo Yii::t('AdminModule.views_setting_authentication', '<strong>Authentication</strong> - Basic'); ?></div>
@ -14,11 +13,9 @@ use yii\helpers\Url;
<li class="active"><a
href="<?php echo Url::toRoute('authentication'); ?>"><?php echo Yii::t('AdminModule.views_setting_authentication', 'Basic'); ?></a>
</li>
<?php if (humhub\modules\user\libs\Ldap::isAvailable()): ?>
<li>
<a href="<?php echo Url::toRoute('authentication-ldap'); ?>"><?php echo Yii::t('AdminModule.views_setting_authentication', 'LDAP'); ?></a>
</li>
<?php endif; ?>
</ul>

View File

@ -38,7 +38,6 @@ class InstallController extends Controller
$user = new User();
$user->group_id = 1;
$user->username = "Admin";
$user->auth_mode = 'local';
$user->email = 'humhub@example.com';
$user->status = User::STATUS_ENABLED;
$user->language = '';

View File

@ -243,7 +243,6 @@ class ConfigController extends Controller
$profileModel->scenario = 'registration';
$userModel->status = User::STATUS_ENABLED;
$userModel->auth_mode = "local";
$userModel->username = "david1986";
$userModel->email = "david.roberts@humhub.com";
$userModel->super_admin = 0;
@ -273,7 +272,6 @@ class ConfigController extends Controller
$profileModel2->scenario = 'registration';
$userModel2->status = User::STATUS_ENABLED;
$userModel2->auth_mode = "local";
$userModel2->username = "sara1989";
$userModel2->email = "sara.schuster@humhub.com";
$userModel2->super_admin = 0;
@ -361,7 +359,7 @@ class ConfigController extends Controller
$userModel = new User();
$userModel->scenario = 'registration';
$userModel->scenario = 'registration_email';
$userPasswordModel = new Password();
$userPasswordModel->scenario = 'registration';
$profileModel = $userModel->profile;

View File

@ -2,6 +2,7 @@
namespace humhub\modules\user;
use Yii;
use humhub\modules\user\models\User;
use humhub\modules\user\models\GroupAdmin;
use humhub\modules\user\models\Password;
@ -22,7 +23,7 @@ class Events extends \yii\base\Object
/**
* On rebuild of the search index, rebuild all user records
*
* @param type $event
* @param \yii\base\Event $event
*/
public static function onSearchRebuild($event)
{
@ -34,7 +35,7 @@ class Events extends \yii\base\Object
/**
* On delete of a Content or ContentAddon
*
* @param type $event
* @param \yii\base\Event $event
*/
public static function onContentDelete($event)
{
@ -45,7 +46,7 @@ class Events extends \yii\base\Object
/**
* Callback to validate module database records.
*
* @param Event $event
* @param \yii\base\Event $event
*/
public static function onIntegrityCheck($event)
{
@ -126,14 +127,22 @@ class Events extends \yii\base\Object
}
}
/**
* Tasks on hourly cron job
*
* @param \yii\base\Event $event
*/
public static function onHourlyCron($event)
{
$controller = $event->sender;
if (Setting::Get('enabled', 'authentication_ldap') && Setting::Get('refreshUsers', 'authentication_ldap') && Ldap::isAvailable()) {
$controller->stdout("Refresh ldap users... ");
Ldap::getInstance()->refreshUsers();
$controller->stdout('done.' . PHP_EOL, \yii\helpers\Console::FG_GREEN);
foreach (Yii::$app->authClientCollection->getClients() as $authClient) {
if ($authClient instanceof authclient\interfaces\AutoSyncUsers) {
/**
* @var authclient\interfaces\AutoSyncUsers $authClient
*/
$authClient->syncUsers();
}
}
}

View File

@ -1,10 +1,32 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user;
/**
* User Module
*/
class Module extends \humhub\components\Module
{
/**
* @inheritdoc
*/
public $controllerNamespace = 'humhub\modules\user\controllers';
/**
* @var boolean default state of remember me checkbox on login page
*/
public $loginRememberMeDefault = true;
/**
* @var int number of seconds that the user can remain in logged-in status if remember me is clicked on login
*/
public $loginRememberMeDuration = 3600 * 24 * 30;
}

View File

@ -0,0 +1,238 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\authclient;
use Yii;
use yii\authclient\ClientInterface;
use humhub\modules\user\models\Auth;
use humhub\modules\user\models\User;
/**
* AuthClientHelper provides helper functions fo auth clients
*
* @since 1.1
* @author luke
*/
class AuthClientHelpers
{
/**
* Returns the user object which is linked against given authClient
*
* @param ClientInterface $authClient the authClient
* @return User the user model or null if not found
*/
public static function getUserByAuthClient(ClientInterface $authClient)
{
$attributes = $authClient->getUserAttributes();
if ($authClient instanceof interfaces\PrimaryClient) {
/**
* @var interfaces\PrimaryClient $authClient
*/
return User::findOne([
$authClient->getUserTableIdAttribute() => $attributes['id'],
'auth_mode' => $authClient->getId()
]);
}
$auth = Auth::find()->where(['source' => $authClient->getId(), 'source_id' => $attributes['id']])->one();
if ($auth !== null) {
return $auth->user;
}
}
/**
* Stores an authClient to an user record
*
* @param \yii\authclient\BaseClient $authClient
* @param User $user
*/
public static function storeAuthClientForUser(ClientInterface $authClient, User $user)
{
$attributes = $authClient->getUserAttributes();
if ($authClient instanceof interfaces\PrimaryClient) {
$user->auth_mode = $authClient->getId();
$user->save();
} else {
$auth = Auth::findOne(['user_id' => $user->id, 'source' => $authClient->getId(), 'source_id' => $attributes['id']]);
if ($auth === null) {
$auth = new \humhub\modules\user\models\Auth([
'user_id' => $user->id,
'source' => (string) $authClient->getId(),
'source_id' => (string) $attributes['id'],
]);
$auth->save();
}
}
}
/**
* Removes Authclient for a user
*
* @param \yii\authclient\BaseClient $authClient
* @param User $user
*/
public static function removeAuthClientForUser(ClientInterface $authClient, User $user)
{
Auth::deleteAll([
'user_id' => $user->id,
'source' => (string) $authClient->getId()
]);
}
/**
* Updates (or creates) a user in HumHub using AuthClients Attributes
* This method will be called after login or by cron sync.
*
* @param \yii\authclient\BaseClient $authClient
* @param User $user
* @return boolean succeed
*/
public static function updateUser(ClientInterface $authClient, User $user = null)
{
if ($user === null) {
$user = self::getUserByAuthClient($authClient);
if ($user === null) {
return false;
}
}
$authClient->trigger(BaseClient::EVENT_UPDATE_USER, new \yii\web\UserEvent(['identity' => $user]));
if ($authClient instanceof interfaces\SyncAttributes) {
$attributes = $authClient->getUserAttributes();
foreach ($authClient->getSyncAttributes() as $attributeName) {
if (isset($attributes[$attributeName])) {
if (in_array($attributeName, ['email', 'username'])) {
$user->setAttribute($attributeName, $attributes[$attributeName]);
} else {
$user->profile->setAttribute($attributeName, $attributes[$attributeName]);
}
}
}
if (count($user->getDirtyAttributes()) !== 0 && !$user->save()) {
Yii::error('Could not update user attributes by AuthClient (UserId: ' . $user->id . ") - Error: " . print_r($user->getErrors(), 1));
return false;
}
if (count($user->profile->getDirtyAttributes()) !== 0 && !$user->profile->save()) {
Yii::error('Could not update user attributes by AuthClient (UserId: ' . $user->id . ") - Error: " . print_r($user->getErrors(), 1));
return false;
}
}
return true;
}
/**
* Automatically creates user by auth client attributes
*
* @param \yii\authclient\BaseClient $authClient
* @return boolean success status
*/
public static function createUser(ClientInterface $authClient)
{
$attributes = $authClient->getUserAttributes();
if (!isset($attributes['id'])) {
return false;
}
$registration = new \humhub\modules\user\models\forms\Registration();
$registration->enablePasswordForm = false;
if ($authClient instanceof interfaces\ApprovalBypass) {
$registration->enableUserApproval = false;
}
unset($attributes['id']);
$registration->getUser()->setAttributes($attributes, false);
$registration->getProfile()->setAttributes($attributes, false);
if ($registration->validate() && $registration->register($authClient)) {
return $registration->getUser();
}
return null;
}
/**
* Returns all users which are using an given authclient
*
* @param ClientInterface $authClient
* @return \yii\db\ActiveQuery
*/
public static function getUsersByAuthClient(ClientInterface $authClient)
{
$query = User::find();
if ($authClient instanceof interfaces\PrimaryClient) {
$query->where([
'auth_mode' => $authClient->getId()
]);
} else {
$query->where(['user_auth.source' => $authClient->getId()]);
}
return $query;
}
/**
* Returns AuthClients used by given User
*
* @param User $user
* @return ClientInterface[] the users authclients
*/
public static function getAuthClientsByUser(User $user)
{
$authClients = [];
foreach (Yii::$app->authClientCollection->getClients() as $client) {
/**
* @var $client ClientInterface
*/
// Add primary authClient
if ($user->auth_mode == $client->getId()) {
$authClients[] = $client;
}
// Add additional authClient
foreach ($user->auths as $auth) {
if ($auth->source == $client->getId()) {
$authClients[] = $client;
}
}
}
return $authClients;
}
/**
* Returns a list of all synchornized user attributes
*
* @param User $user
* @return array attribute names
*/
public static function getSyncAttributesByUser(User $user)
{
$attributes = [];
foreach (self::getAuthClientsByUser($user) as $authClient) {
if ($authClient instanceof interfaces\SyncAttributes) {
$attributes = array_merge($attributes, $authClient->getSyncAttributes());
}
}
return $attributes;
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\authclient;
/**
* Extended BaseClient with additional events
*
* @since 1.1
* @author luke
*/
class BaseClient extends \yii\authclient\BaseClient
{
/**
* @event Event an event raised on update user data.
* @see AuthClientHelpers::updateUser()
*/
const EVENT_UPDATE_USER = 'update';
/**
* @event Event an event raised on create user.
* @see AuthClientHelpers::createUser()
*/
const EVENT_CREATE_USER = 'create';
}

View File

@ -0,0 +1,37 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace humhub\modules\user\authclient;
use yii\base\NotSupportedException;
use humhub\modules\user\models\forms\Login;
/**
* BaseFormAuth is a base class for AuthClients using the Login Form
*
* @since 1.1
*/
class BaseFormAuth extends BaseClient
{
/**
* @var Login the login form model
*/
public $login = null;
/**
* Authenticate the user using the login form.
*
* @throws NotSupportedException
*/
public function auth()
{
throw new NotSupportedException('Method "' . get_class($this) . '::' . __FUNCTION__ . '" not implemented.');
}
}

View File

@ -0,0 +1,140 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\authclient;
use Yii;
use yii\base\Component;
use yii\base\InvalidParamException;
use humhub\models\Setting;
/**
* Extended AuthClient collection with event support
*
* @author luke
* @since 1.1
*/
class Collection extends Component
{
/**
* @event Event an event raised after the clients are set.
*/
const EVENT_AFTER_CLIENTS_SET = 'client_set';
/**
* @var array list of Auth clients with their configuration in format: 'clientId' => [...]
*/
private $_clients = [];
/**
* @param array $clients list of auth clients
*/
public function setClients(array $clients)
{
$this->_clients = array_merge($this->getDefaultClients(), $clients);
$this->trigger(self::EVENT_AFTER_CLIENTS_SET);
}
/**
* @return ClientInterface[] list of auth clients.
*/
public function getClients($load = true)
{
$clients = [];
foreach ($this->_clients as $id => $client) {
$clients[$id] = $this->getClient($id, $load);
}
return $clients;
}
/**
* @param string $id service id.
* @return ClientInterface auth client instance.
* @throws InvalidParamException on non existing client request.
*/
public function getClient($id, $load = true)
{
if (!array_key_exists($id, $this->_clients)) {
throw new InvalidParamException("Unknown auth client '{$id}'.");
}
if (!is_object($this->_clients[$id]) && $load) {
$this->_clients[$id] = $this->createClient($id, $this->_clients[$id]);
}
return $this->_clients[$id];
}
/**
* Checks if client exists in the hub.
* @param string $id client id.
* @return boolean whether client exist.
*/
public function hasClient($id)
{
return array_key_exists($id, $this->_clients);
}
/**
* Sets a client by id and config
*
* @param string $id auth client id.
* @param array $config auth client instance configuration.
*/
public function setClient($id, $config)
{
$this->_clients[$id] = $config;
}
/**
* Removes client by id
*
* @param string $id client id.
*/
public function removeClient($id)
{
unset($this->_clients[$id]);
}
/**
* Creates auth client instance from its array configuration.
* @param string $id auth client id.
* @param array $config auth client instance configuration.
* @return ClientInterface auth client instance.
*/
protected function createClient($id, $config)
{
$config['id'] = $id;
return Yii::createObject($config);
}
/**
* Returns the configuration of default auth clients
*
* @return array the default auth clients
*/
protected function getDefaultClients()
{
$clients = [];
$clients['password'] = [
'class' => 'humhub\modules\user\authclient\Password'
];
if (Setting::Get('enabled', 'authentication_ldap')) {
$clients['ldap'] = [
'class' => 'humhub\modules\user\authclient\ZendLdapClient'
];
}
return $clients;
}
}

View File

@ -0,0 +1,68 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace humhub\modules\user\authclient;
use humhub\modules\user\models\User;
/**
* Standard password authentication client
*
* @since 1.1
*/
class Password extends BaseFormAuth implements interfaces\PrimaryClient
{
/**
* @inheritdoc
*/
public function getId()
{
return 'local';
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return 'password';
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'Password';
}
/**
* @inheritdoc
*/
public function getUserTableIdAttribute()
{
return 'id';
}
/**
* @inheritdoc
*/
public function auth()
{
$user = User::find()->where(['username' => $this->login->username])->orWhere(['email' => $this->login->password])->one();
if ($user !== null && $user->currentPassword !== null && $user->currentPassword->validatePassword($this->login->password)) {
$this->setUserAttributes(['id' => $user->id]);
return true;
}
return false;
}
}

View File

@ -0,0 +1,342 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace humhub\modules\user\authclient;
use Yii;
use Zend\Ldap\Ldap;
use Zend\Ldap\Node;
use Zend\Ldap\Exception\LdapException;
use humhub\models\Setting;
use humhub\modules\user\models\User;
use humhub\modules\user\models\ProfileField;
use humhub\modules\user\authclient\AuthClientHelpers;
/**
* LDAP Authentication
*
* @todo create base ldap authentication, to bypass ApprovalByPass Interface
* @since 1.1
*/
class ZendLdapClient extends BaseFormAuth implements interfaces\AutoSyncUsers, interfaces\SyncAttributes, interfaces\ApprovalBypass, interfaces\PrimaryClient
{
/**
* @var \Zend\Ldap\Ldap
*/
private $_ldap = null;
/**
* ID attribute to uniquely identify user
* If set to null, automatically a value email or objectguid will be used if available.
*
* @var string attribute name to identify node
*/
public $idAttribute = null;
/**
* @var string attribute name to user record
*/
public $userTableIdAttribute = 'guid';
/**
* @inheritdoc
*/
public $byPassApproval = true;
/**
* @inheritdoc
*/
public function getId()
{
return 'ldap';
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return 'ldap';
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'LDAP';
}
/**
* @inheritdoc
*/
public function getIdAttribute()
{
return $this->idAttribute;
}
/**
* @inheritdoc
*/
public function getUserTableIdAttribute()
{
return $this->userTableIdAttribute;
}
/**
* @inheritdoc
*/
public function auth()
{
$node = $this->getUserNode();
if ($node !== null) {
$this->setUserAttributes($node->getAttributes());
return true;
}
return false;
}
/**
* @inheritdoc
*/
protected function defaultNormalizeUserAttributeMap()
{
$map = [];
// Username field
$usernameAttribute = Setting::Get('usernameAttribute', 'authentication_ldap');
if ($usernameAttribute == '') {
$usernameAttribute = 'sAMAccountName';
}
$map['username'] = strtolower($usernameAttribute);
// E-Mail field
$emailAttribute = Setting::Get('emailAttribute', 'authentication_ldap');
if ($emailAttribute == '') {
$emailAttribute = 'mail';
}
$map['email'] = strtolower($emailAttribute);
// Profile Field Mapping
foreach (ProfileField::find()->andWhere(['!=', 'ldap_attribute', ''])->all() as $profileField) {
$map[$profileField->internal_name] = strtolower($profileField->ldap_attribute);
}
return $map;
}
/**
* @inheritdoc
*/
protected function normalizeUserAttributes($attributes)
{
$normalized = [];
// Fix LDAP Attributes
foreach ($attributes as $name => $value) {
if (is_array($value) && count($value) == 1) {
$normalized[$name] = $value[0];
} else {
$normalized[$name] = $value;
}
}
if (isset($normalized['objectguid'])) {
$normalized['objectguid'] = \humhub\libs\StringHelper::binaryToGuid($normalized['objectguid']);
}
return parent::normalizeUserAttributes($normalized);
}
/**
* @return array list of user attributes
*/
public function getUserAttributes()
{
$attributes = parent::getUserAttributes();
// Try to automatically set id and usertable id attribute by available attributes
if ($this->getIdAttribute() === null || $this->getUserTableIdAttribute() === null) {
if (isset($attributes['objectguid'])) {
$this->idAttribute = 'objectguid';
$this->userTableIdAttribute = 'guid';
} elseif (isset($attributes['mail'])) {
$this->idAttribute = 'mail';
$this->userTableIdAttribute = 'email';
} else {
throw new \yii\base\Exception("Could not automatically determine unique user id from ldap node!");
}
}
// Make sure id attributes sits on id attribute key
if (isset($attributes[$this->getIdAttribute()])) {
$attributes['id'] = $attributes[$this->getIdAttribute()];
}
// Map usertable id attribute against ldap id attribute
$attributes[$this->getUserTableIdAttribute()] = $attributes[$this->getIdAttribute()];
return $attributes;
}
/**
* Returns Users LDAP Node
*
* @return Node the users ldap node
*/
protected function getUserNode()
{
$dn = $this->getUserDn();
if ($dn !== '') {
return $this->getLdap()->getNode($dn);
}
return null;
}
/**
* Returns the users LDAP DN
*
* @return string the user dn if found
*/
protected function getUserDn()
{
// ToDo: Search user even by e-mail address
try {
$this->getLdap()->bind($this->login->username, $this->login->password);
return $this->getLdap()->getCanonicalAccountName($this->login->username, Ldap::ACCTNAME_FORM_DN);
} catch (LdapException $ex) {
// User not found in LDAP
}
return '';
}
/**
* Returns Zend LDAP
*
* @return \Zend\Ldap\Ldap
*/
public function getLdap()
{
if ($this->_ldap === null) {
$options = array(
'host' => Setting::Get('hostname', 'authentication_ldap'),
'port' => Setting::Get('port', 'authentication_ldap'),
'username' => Setting::Get('username', 'authentication_ldap'),
'password' => Setting::Get('password', 'authentication_ldap'),
'useStartTls' => (Setting::Get('encryption', 'authentication_ldap') == 'tls'),
'useSsl' => (Setting::Get('encryption', 'authentication_ldap') == 'ssl'),
'bindRequiresDn' => true,
'baseDn' => Setting::Get('baseDn', 'authentication_ldap'),
'accountFilterFormat' => Setting::Get('loginFilter', 'authentication_ldap'),
);
$this->_ldap = new \Zend\Ldap\Ldap($options);
$this->_ldap->bind();
}
return $this->_ldap;
}
/**
* Sets an Zend LDAP Instance
*
* @param \Zend\Ldap\Ldap $ldap
*/
public function setLdap(\Zend\Ldap\Ldap $ldap)
{
$this->_ldap = $ldap;
}
/**
* @inheritdoc
*/
public function getSyncAttributes()
{
$attributes = ['username', 'email'];
foreach (ProfileField::find()->andWhere(['!=', 'ldap_attribute', ''])->all() as $profileField) {
$attributes[] = $profileField->internal_name;
}
return $attributes;
}
/**
* Refresh ldap users
*
* New users (found in ldap) will be automatically created if all required fiélds are set.
* Profile fields which are bind to LDAP will automatically updated.
*/
public function syncUsers()
{
if (!Setting::Get('enabled', 'authentication_ldap') || !Setting::Get('refreshUsers', 'authentication_ldap')) {
return;
}
$userFilter = Setting::Get('userFilter', 'authentication_ldap');
$baseDn = Setting::Get('baseDn', 'authentication_ldap');
$userCollection = $this->getLdap()->search($userFilter, $baseDn, Ldap::SEARCH_SCOPE_SUB);
$ids = [];
foreach ($userCollection as $attributes) {
$authClient = new static;
$authClient->setUserAttributes($attributes);
$attributes = $authClient->getUserAttributes();
$user = AuthClientHelpers::getUserByAuthClient($authClient);
if ($user === null) {
if (!AuthClientHelpers::createUser($authClient)) {
Yii::warning('Could not automatically create LDAP user ' . $attributes['email'] . ' - check required attributes!');
}
} else {
AuthClientHelpers::updateUser($authClient, $user);
}
$ids[] = $attributes['id'];
}
/**
* Since userTableAttribute can be automatically set on user attributes
* try to take it from initialized authclient instance.
*/
$userTableIdAttribute = $this->getUserTableIdAttribute();
if ($authClient !== null) {
$userTableIdAttribute = $authClient->getUserTableIdAttribute();
}
// Disable not longer existing users
foreach (AuthClientHelpers::getUsersByAuthClient($this)->each() as $user) {
if ($user->status !== User::STATUS_DISABLED && !in_array($user->getAttribute($userTableIdAttribute), $ids)) {
$user->status = User::STATUS_DISABLED;
$user->save();
Yii::warning('Disabled user ' . $user->username . ' (' . $user->id . ') - Not found in LDAP!');
}
}
}
/**
* Checks if LDAP is supported
*/
public static function isLdapAvailable()
{
if (!class_exists('Zend\Ldap\Ldap')) {
return false;
}
if (!function_exists('ldap_bind')) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\authclient\interfaces;
/**
* ApprovalBypass interface allow user approvals of an authclient
*
* @since 1.1
* @author luke
*/
interface ApprovalBypass
{
}

View File

@ -0,0 +1,22 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\authclient\interfaces;
/**
* AutoSyncUsers interface adds the possiblity to automatically update/create users via AuthClient.
* If this interface is implemented the cron will hourly execute the authclient's
* syncronization method.
*
* @author luke
*/
interface AutoSyncUsers
{
public function syncUsers();
}

View File

@ -0,0 +1,29 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\authclient\interfaces;
/**
* PrimaryClient authclient interface
*
* It's not possible to have to primary auth clients at the same time.
* E.g. LDAP and Password
*
* @author luke
*/
interface PrimaryClient
{
/**
* Returns the user model attribute name which should be mapped against
* the id attribute in the authClient user attributes.
*
* @return string the user model id attribute e.g. id, guid or email
*/
public function getUserTableIdAttribute();
}

View File

@ -0,0 +1,29 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\authclient\interfaces;
/**
* SyncAttributes interface allows the possiblitz to specify user attributes which will be automatically
* updated on login or by daily cronjob if AutoSyncUsers is enabled.
*
* These attributes are also not writable by user.
*
* @since 1.1
* @author luke
*/
interface SyncAttributes
{
/**
* Returns attribute names which should be synced on login
*
* @return array attribute names to be synced
*/
public function getSyncAttributes();
}

View File

@ -8,6 +8,10 @@
namespace humhub\modules\user\components;
use Yii;
use yii\authclient\ClientInterface;
use humhub\modules\user\authclient\AuthClientHelpers;
/**
* Description of User
*
@ -16,6 +20,11 @@ namespace humhub\modules\user\components;
class User extends \yii\web\User
{
/**
* @var ClientInterface[] the users authclients
*/
private $_authClients = null;
/**
* @var PermissionManager
*/
@ -63,4 +72,58 @@ class User extends \yii\web\User
return $this->permissionManager;
}
public function canChangePassword()
{
foreach ($this->getAuthClients() as $authClient) {
if ($authClient->className() == \humhub\modules\user\authclient\Password::className()) {
return true;
}
}
return false;
}
public function canChangeEmail()
{
if (in_array('email', AuthClientHelpers::getSyncAttributesByUser($this->getIdentity()))) {
return false;
}
return true;
}
public function canDeleteAccount()
{
foreach ($this->getAuthClients() as $authClient) {
if ($authClient instanceof \humhub\modules\user\authclient\interfaces\AutoSyncUsers) {
return false;
}
}
return true;
}
public function getAuthClients()
{
if ($this->_authClients === null) {
$this->_authClients = AuthClientHelpers::getAuthClientsByUser($this->getIdentity());
}
return $this->_authClients;
}
public function setCurrentAuthClient(ClientInterface $authClient)
{
Yii::$app->session->set('currentAuthClientId', $authClient->getId());
}
public function getCurrentAuthClient()
{
foreach ($this->getAuthClients() as $authClient) {
if ($authClient->getId() == Yii::$app->session->get('currentAuthClientId')) {
return $authClient;
}
}
return null;
}
}

View File

@ -109,6 +109,40 @@ class AccountController extends Controller
return $this->render('editSettings', array('model' => $model, 'languages' => Yii::$app->params['availableLanguages']));
}
public function actionConnectedAccounts()
{
if (Yii::$app->request->isPost && Yii::$app->request->get('disconnect')) {
foreach (Yii::$app->user->getAuthClients() as $authClient) {
if ($authClient->getId() == Yii::$app->request->get('disconnect')) {
\humhub\modules\user\authclient\AuthClientHelpers::removeAuthClientForUser($authClient, Yii::$app->user->getIdentity());
}
}
return $this->redirect(['connected-accounts']);
}
$clients = [];
foreach (Yii::$app->get('authClientCollection')->getClients() as $client) {
if (!$client instanceof humhub\modules\user\authclient\BaseFormAuth && !$client instanceof \humhub\modules\user\authclient\interfaces\PrimaryClient) {
$clients[] = $client;
}
}
$currentAuthProviderId = "";
if (Yii::$app->user->getCurrentAuthClient() !== null) {
$currentAuthProviderId = Yii::$app->user->getCurrentAuthClient()->getId();
}
$activeAuthClientIds = [];
foreach (Yii::$app->user->getAuthClients() as $authClient) {
$activeAuthClientIds[] = $authClient->getId();
}
return $this->render('connected-accounts', [
'authClients' => $clients,
'currentAuthProviderId' => $currentAuthProviderId,
'activeAuthClientIds' => $activeAuthClientIds
]);
}
/**
* Allows the user to enable user specifc modules
*/
@ -159,8 +193,8 @@ class AccountController extends Controller
$isSpaceOwner = false;
$user = Yii::$app->user->getIdentity();
if ($user->auth_mode != User::AUTH_MODE_LOCAL) {
throw new HttpException(500, 'This is not a local account! You cannot delete it. (e.g. LDAP)!');
if (!Yii::$app->user->canDeleteAccount()) {
throw new HttpException(500, 'Account deletion not allowed');
}
foreach (\humhub\modules\space\models\Membership::GetUserSpaces() as $space) {
@ -213,9 +247,8 @@ class AccountController extends Controller
*/
public function actionChangeEmail()
{
$user = Yii::$app->user->getIdentity();
if ($user->auth_mode != User::AUTH_MODE_LOCAL) {
throw new HttpException(500, Yii::t('UserModule.controllers_AccountController', 'You cannot change your e-mail address here.'));
if (!Yii::$app->user->canChangeEmail()) {
throw new HttpException(500, 'Change E-Mail is not allowed');
}
$model = new \humhub\modules\user\models\forms\AccountChangeEmail;
@ -233,15 +266,15 @@ class AccountController extends Controller
*/
public function actionChangeEmailValidate()
{
$user = Yii::$app->user->getIdentity();
if ($user->auth_mode != User::AUTH_MODE_LOCAL) {
throw new CHttpException(500, Yii::t('UserModule.controllers_AccountController', 'You cannot change your e-mail address here.'));
if (!Yii::$app->user->canChangeEmail()) {
throw new HttpException(500, 'Change E-Mail is not allowed');
}
$token = Yii::$app->request->get('token');
$email = Yii::$app->request->get('email');
$user = Yii::$app->user->getIdentity();
// Check if Token is valid
if (md5(\humhub\models\Setting::Get('secret') . $user->guid . $email) != $token) {
throw new HttpException(404, Yii::t('UserModule.controllers_AccountController', 'Invalid link! Please make sure that you entered the entire url.'));
@ -264,10 +297,8 @@ class AccountController extends Controller
*/
public function actionChangePassword()
{
$user = Yii::$app->user->getIdentity();
if ($user->auth_mode != User::AUTH_MODE_LOCAL) {
throw new CHttpException(500, Yii::t('UserModule.controllers_AccountController', 'You cannot change your e-mail address here.'));
if (!Yii::$app->user->canChangePassword()) {
throw new HttpException(500, 'Password change is not allowed');
}
$userPassword = new \humhub\modules\user\models\Password();

View File

@ -10,6 +10,11 @@ namespace humhub\modules\user\controllers;
use Yii;
use humhub\components\Controller;
use humhub\modules\user\models\User;
use humhub\modules\user\models\Auth;
use humhub\modules\user\models\Invite;
use humhub\modules\user\models\forms\Login;
use humhub\modules\user\authclient\AuthClientHelpers;
/**
* AuthController handles login and logout
@ -38,60 +43,148 @@ class AuthController extends Controller
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
],
'external-auth' => [
'class' => 'yii\authclient\AuthAction',
'successCallback' => [$this, 'onAuthSuccess'],
],
];
}
/**
* @inheritdoc
*/
public function beforeAction($action)
{
// Remove authClient from session - if already exists
Yii::$app->session->remove('authClient');
return parent::beforeAction($action);
}
/**
* Displays the login page
*/
public function actionLogin()
{
// If user is already logged in, redirect him to the dashboard
if (!Yii::$app->user->isGuest) {
$this->redirect(Yii::$app->user->returnUrl);
}
// Show/Allow Anonymous Registration
$loginModel = new \humhub\modules\user\models\forms\AccountLogin;
if ($loginModel->load(Yii::$app->request->post()) && $loginModel->login()) {
// Login Form Handling
$login = new Login;
if ($login->load(Yii::$app->request->post()) && $login->validate()) {
return $this->onAuthSuccess($login->authClient);
}
// Self Invite
$invite = new Invite();
$invite->scenario = 'invite';
if ($invite->load(Yii::$app->request->post()) && $invite->selfInvite()) {
if (Yii::$app->request->getIsAjax()) {
return $this->htmlRedirect(Yii::$app->user->returnUrl);
return $this->render('register_success_modal', ['model' => $invite]);
} else {
return $this->redirect(Yii::$app->user->returnUrl);
}
}
$loginModel->password = "";
$canRegister = \humhub\models\Setting::Get('anonymousRegistration', 'authentication_internal');
$registerModel = new \humhub\modules\user\models\forms\AccountRegister;
if ($canRegister) {
if ($registerModel->load(Yii::$app->request->post()) && $registerModel->validate()) {
$invite = \humhub\modules\user\models\Invite::findOne(['email' => $registerModel->email]);
if ($invite === null) {
$invite = new \humhub\modules\user\models\Invite();
}
$invite->email = $registerModel->email;
$invite->source = \humhub\modules\user\models\Invite::SOURCE_SELF;
$invite->language = Yii::$app->language;
$invite->save();
$invite->sendInviteMail();
if (Yii::$app->request->getIsAjax()) {
return $this->render('register_success_modal', ['model' => $registerModel]);
} else {
return $this->render('register_success', ['model' => $registerModel]);
}
return $this->render('register_success', ['model' => $invite]);
}
}
if (Yii::$app->request->getIsAjax()) {
return $this->renderAjax('login_modal', array('model' => $loginModel, 'registerModel' => $registerModel, 'canRegister' => $canRegister));
} else {
return $this->render('login', array('model' => $loginModel, 'registerModel' => $registerModel, 'canRegister' => $canRegister));
return $this->renderAjax('login_modal', array('model' => $login, 'invite' => $invite, 'canRegister' => $invite->allowSelfInvite()));
}
return $this->render('login', array('model' => $login, 'invite' => $invite, 'canRegister' => $invite->allowSelfInvite()));
}
/**
* Handle successful authentication
*
* @param \yii\authclient\BaseClient $authClient
* @return Response
*/
public function onAuthSuccess(\yii\authclient\BaseClient $authClient)
{
$attributes = $authClient->getUserAttributes();
// User already logged in - Add new authclient to existing user
if (!Yii::$app->user->isGuest) {
AuthClientHelpers::storeAuthClientForUser($authClient, Yii::$app->user->getIdentity());
return $this->redirect(['/user/account/connected-accounts']);
}
// Login existing user
$user = AuthClientHelpers::getUserByAuthClient($authClient);
if ($user !== null) {
return $this->login($user, $authClient);
}
// Check if E-Mail is given
if (!isset($attributes['email'])) {
Yii::$app->session->setFlash('error', "Missing E-Mail Attribute from AuthClient.");
return $this->redirect(['/user/auth/login']);
}
if (!isset($attributes['id'])) {
Yii::$app->session->setFlash('error', "Missing ID AuthClient Attribute from AuthClient.");
return $this->redirect(['/user/auth/login']);
}
// Check if e-mail is already taken
if (User::findOne(['email' => $attributes['email']]) !== null) {
Yii::$app->session->setFlash('error', Yii::t('UserModule.base', 'User with the same email already exists but isn\'t linked to you. Login using your email first to link it.'));
return $this->redirect(['/user/auth/login']);
}
// Try automatically create user & login user
$user = AuthClientHelpers::createUser($authClient);
if ($user !== null) {
return $this->login($user, $authClient);
}
// Make sure we normalized user attributes before put it in session (anonymous functions)
$authClient->setNormalizeUserAttributeMap([]);
// Store authclient in session - for registration controller
Yii::$app->session->set('authClient', $authClient);
// Start registration process
return $this->redirect(['/user/registration']);
}
/**
* Login user
*
* @param User $user
* @param \yii\authclient\BaseClient $authClient
* @return Response the current response object
*/
protected function login($user, $authClient)
{
$redirectUrl = ['/user/auth/login'];
if ($user->status == User::STATUS_ENABLED) {
$duration = 0;
if ($authClient instanceof \humhub\modules\user\authclient\BaseFormAuth) {
if ($authClient->login->rememberMe) {
$duration = Yii::$app->getModule('user')->loginRememberMeDuration;
}
}
AuthClientHelpers::updateUser($authClient, $user);
if (Yii::$app->user->login($user, $duration)) {
Yii::$app->user->setCurrentAuthClient($authClient);
$url = Yii::$app->user->returnUrl;
}
} elseif ($user->status == User::STATUS_DISABLED) {
Yii::$app->session->setFlash('error', 'Your account is disabled!');
} elseif ($user->status == User::STATUS_NEED_APPROVAL) {
Yii::$app->session->setFlash('error', 'Your account is not approved yet!');
} else {
Yii::$app->session->setFlash('error', 'Unknown user status!');
}
if (Yii::$app->request->getIsAjax()) {
return $this->htmlRedirect($redirectUrl);
}
return $this->redirect($redirectUrl);
}
/**

View File

@ -9,13 +9,15 @@
namespace humhub\modules\user\controllers;
use Yii;
use yii\web\HttpException;
use yii\helpers\Url;
use yii\web\HttpException;
use yii\authclient\ClientInterface;
use humhub\components\Controller;
use humhub\modules\user\models\Invite;
use humhub\compat\HForm;
use humhub\modules\user\models\User;
use humhub\modules\user\models\Password;
use humhub\modules\user\models\Invite;
use humhub\modules\user\models\forms\Registration;
use humhub\modules\user\authclient\AuthClientHelpers;
use humhub\modules\user\authclient\interfaces\ApprovalBypass;
/**
* RegistrationController handles new user registration
@ -35,6 +37,18 @@ class RegistrationController extends Controller
*/
public $subLayout = "_layout";
/**
* @inheritdoc
*/
public function beforeAction($action)
{
if (!Yii::$app->user->isGuest) {
throw new HttpException(401, 'Your are already logged in! - Logout first!');
}
return parent::beforeAction($action);
}
/**
* Registration Form
*
@ -43,136 +57,78 @@ class RegistrationController extends Controller
*/
public function actionIndex()
{
$needApproval = \humhub\models\Setting::Get('needApproval', 'authentication_internal');
$registration = new Registration();
if (!Yii::$app->user->isGuest)
throw new HttpException(401, 'Your are already logged in! - Logout first!');
/**
* @var \yii\authclient\BaseClient
*/
$authClient = null;
$inviteToken = Yii::$app->request->get('token', '');
$userInvite = Invite::findOne(['token' => Yii::$app->request->get('token')]);
if (!$userInvite)
throw new HttpException(404, 'Token not found!');
if ($userInvite->language)
Yii::$app->language = $userInvite->language;
$userModel = new User();
$userModel->scenario = 'registration';
$userModel->email = $userInvite->email;
$userPasswordModel = new Password();
$userPasswordModel->scenario = 'registration';
$profileModel = $userModel->profile;
$profileModel->scenario = 'registration';
// Build Form Definition
$definition = array();
$definition['elements'] = array();
$groupModels = \humhub\modules\user\models\Group::find()->orderBy('name ASC')->all();
$defaultUserGroup = \humhub\models\Setting::Get('defaultUserGroup', 'authentication_internal');
$groupFieldType = "dropdownlist";
if ($defaultUserGroup != "") {
$groupFieldType = "hidden";
} else if (count($groupModels) == 1) {
$groupFieldType = "hidden";
$defaultUserGroup = $groupModels[0]->id;
}
if ($groupFieldType == 'hidden') {
$userModel->group_id = $defaultUserGroup;
if ($inviteToken != '') {
$this->handleInviteRegistration($inviteToken, $registration);
} elseif (Yii::$app->session->has('authClient')) {
$authClient = Yii::$app->session->get('authClient');
$this->handleAuthClientRegistration($authClient, $registration);
} else {
Yii::$app->session->setFlash('error', 'Registration failed.');
return $this->redirect(['/user/auth/login']);
}
// Add User Form
$definition['elements']['User'] = array(
'type' => 'form',
'title' => Yii::t('UserModule.controllers_AuthController', 'Account'),
'elements' => array(
'username' => array(
'type' => 'text',
'class' => 'form-control',
'maxlength' => 25,
),
'group_id' => array(
'type' => $groupFieldType,
'class' => 'form-control',
'items' => \yii\helpers\ArrayHelper::map($groupModels, 'id', 'name'),
'value' => $defaultUserGroup,
),
),
);
if ($registration->submitted('save') && $registration->validate() && $registration->register($authClient)) {
Yii::$app->session->remove('authClient');
// Add User Password Form
$definition['elements']['UserPassword'] = array(
'type' => 'form',
#'title' => 'Password',
'elements' => array(
'newPassword' => array(
'type' => 'password',
'class' => 'form-control',
'maxlength' => 255,
),
'newPasswordConfirm' => array(
'type' => 'password',
'class' => 'form-control',
'maxlength' => 255,
),
),
);
// Add Profile Form
$definition['elements']['Profile'] = array_merge(array('type' => 'form'), $profileModel->getFormDefinition());
// Get Form Definition
$definition['buttons'] = array(
'save' => array(
'type' => 'submit',
'class' => 'btn btn-primary',
'label' => Yii::t('UserModule.controllers_AuthController', 'Create account'),
),
);
$form = new HForm($definition);
$form->models['User'] = $userModel;
$form->models['UserPassword'] = $userPasswordModel;
$form->models['Profile'] = $profileModel;
if ($form->submitted('save') && $form->validate()) {
$this->forcePostRequest();
// Registe User
$form->models['User']->email = $userInvite->email;
$form->models['User']->language = Yii::$app->language;
if ($form->models['User']->save()) {
// Save User Profile
$form->models['Profile']->user_id = $form->models['User']->id;
$form->models['Profile']->save();
// Save User Password
$form->models['UserPassword']->user_id = $form->models['User']->id;
$form->models['UserPassword']->setPassword($form->models['UserPassword']->newPassword);
$form->models['UserPassword']->save();
// Autologin user
if (!$needApproval) {
Yii::$app->user->switchIdentity($form->models['User']);
// Autologin when user is enabled (no approval required)
if ($registration->getUser()->status === User::STATUS_ENABLED) {
Yii::$app->user->switchIdentity($registration->models['User']);
return $this->redirect(Url::to(['/dashboard/dashboard']));
}
return $this->render('success', array(
'form' => $form,
'needApproval' => $needApproval,
));
}
return $this->render('success', [
'form' => $registration,
'needApproval' => ($registration->getUser()->status === User::STATUS_NEED_APPROVAL)
]);
}
return $this->render('index', array(
'hForm' => $form,
'needAproval' => $needApproval)
);
return $this->render('index', ['hForm' => $registration]);
}
protected function handleInviteRegistration($inviteToken, Registration $form)
{
$userInvite = Invite::findOne(['token' => $inviteToken]);
if (!$userInvite) {
throw new HttpException(404, 'Invalid registration token!');
}
if ($userInvite->language) {
Yii::$app->language = $userInvite->language;
}
$form->getUser()->email = $userInvite->email;
}
/**
* @param \yii\authclient\BaseClient $authClient
* @param Registration $registration
* @return boolean already all registration data gathered
* @throws Exception
*/
protected function handleAuthClientRegistration(ClientInterface $authClient, Registration $registration)
{
$attributes = $authClient->getUserAttributes();
if (!isset($attributes['id'])) {
throw new Exception("No user id given by authclient!");
}
$registration->enablePasswordForm = false;
if ($authClient instanceof ApprovalBypass) {
$registration->enableUserApproval = false;
}
// do not store id attribute
unset($attributes['id']);
$registration->getUser()->setAttributes($attributes, false);
$registration->getProfile()->setAttributes($attributes, false);
}
}

View File

@ -1,281 +0,0 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\libs;
use Exception;
use Yii;
use yii\base\Object;
use humhub\models\Setting;
use humhub\libs\ParameterEvent;
use humhub\modules\user\models\User;
use humhub\modules\user\models\Group;
use humhub\modules\user\models\ProfileField;
use humhub\modules\space\models\Space;
/**
* LDAP Connector
*
* @since 0.5
*/
class Ldap extends \yii\base\Component
{
/**
* @event event when a ldap user is updated
*/
const EVENT_UPDATE_USER = 'update_user';
/**
* @var Zend_Ldap instance
*/
public $ldap = null;
/**
* @var SILDAP instance
*/
static private $instance = null;
/**
* Returns the current HLdap Instance.
*
* @return HLdap
*/
static public function getInstance()
{
if (null === self::$instance) {
self::$instance = new self;
}
return self::$instance;
}
/**
* @var User currently handled user
*/
public $currentUser = null;
/**
* Creates singleton HLdap Instance which configured Zend_Ldap Class
*/
public function __construct()
{
try {
$options = array(
'host' => Setting::Get('hostname', 'authentication_ldap'),
'port' => Setting::Get('port', 'authentication_ldap'),
'username' => Setting::Get('username', 'authentication_ldap'),
'password' => Setting::Get('password', 'authentication_ldap'),
'useStartTls' => (Setting::Get('encryption', 'authentication_ldap') == 'tls'),
'useSsl' => (Setting::Get('encryption', 'authentication_ldap') == 'ssl'),
'bindRequiresDn' => true,
'baseDn' => Setting::Get('baseDn', 'authentication_ldap'),
'accountFilterFormat' => Setting::Get('loginFilter', 'authentication_ldap'),
);
$this->ldap = new \Zend\Ldap\Ldap($options);
$this->ldap->bind();
} catch (\Zend\Ldap\Exception\LdapException $ex) {
Yii::error('Cound not bind to LDAP Server. Error: ' . $ex->getMessage());
} catch (Exception $ex) {
Yii::error('Cound not bind to LDAP Server. Error: ' . $ex->getMessage());
}
}
/**
* Authenticates user against LDAP Backend
*
* @param type $username
* @param type $password
* @return boolean
*/
public function authenticate($username, $password)
{
try {
$username = $this->ldap->getCanonicalAccountName($username, \Zend\Ldap\Ldap::ACCTNAME_FORM_DN);
$this->ldap->bind($username, $password);
// Update Users Data
$node = $this->ldap->getNode($username);
$this->handleLdapUser($node);
return true;
} catch (\Zend\Ldap\Exception\LdapException $ex) {
return false;
} catch (Exception $ex) {
return false;
}
}
/**
* Reads out all users from configured ldap backend and creates or update
* existing users.
*
* Also disabling deleted ldap users in humhub
*/
public function refreshUsers()
{
$ldapUserIds = array();
try {
$items = $this->ldap->search(Setting::Get('userFilter', 'authentication_ldap'), Setting::Get('baseDn', 'authentication_ldap'), \Zend\Ldap\Ldap::SEARCH_SCOPE_SUB);
foreach ($items as $item) {
$node = \Zend\Ldap\Node::fromArray($item);
$user = $this->handleLdapUser($node);
if ($user != null)
$ldapUserIds[] = $user->id;
}
foreach (User::find()->where(['auth_mode' => User::AUTH_MODE_LDAP])->andWhere(['!=', 'status', User::STATUS_DISABLED])->each() as $user) {
if (!in_array($user->id, $ldapUserIds)) {
// User no longer available in ldap
$user->status = User::STATUS_DISABLED;
$user->save();
Yii::warning('Disabled user ' . $user->username . ' (' . $user->id . ') - Not found in LDAP!');
}
}
} catch (Exception $ex) {
Yii::error($ex->getMessage());
}
}
/**
* Updates or creates user by given ldap node
*
* @param Zend_Ldap_Node $node
* @return User User Object
*/
public function handleLdapUser($node)
{
$usernameAttribute = Setting::Get('usernameAttribute', 'authentication_ldap');
if ($usernameAttribute == '') {
$usernameAttribute = 'sAMAccountName';
}
$emailAttribute = Setting::Get('emailAttribute', 'authentication_ldap');
if ($emailAttribute == '') {
$emailAttribute = 'mail';
}
$username = $node->getAttribute($usernameAttribute, 0);
$email = $node->getAttribute($emailAttribute, 0);
$guid = $this->binToStrGuid($node->getAttribute('objectGUID', 0));
// Try to load User:
$userChanged = false;
$user = null;
if ($guid != "") {
$user = User::findOne(array('guid' => $guid, 'auth_mode' => User::AUTH_MODE_LDAP));
} else {
// Fallback use e-mail
$user = User::findOne(array('email' => $email, 'auth_mode' => User::AUTH_MODE_LDAP));
}
if ($user === null) {
$user = new User();
if ($guid != "") {
$user->guid = $guid;
}
$user->status = User::STATUS_ENABLED;
$user->auth_mode = User::AUTH_MODE_LDAP;
$user->group_id = 1;
Yii::info('Create ldap user ' . $username . '!');
}
// Update Users Field
if ($user->username != $username) {
$userChanged = true;
$user->username = $username;
}
if ($user->email != $email) {
$userChanged = true;
$user->email = $email;
}
if ($user->validate()) {
// Only Save user when something is changed
if ($userChanged || $user->isNewRecord) {
$user->save();
}
// Update Profile Fields
foreach (ProfileField::find()->andWhere(['!=', 'ldap_attribute', ''])->all() as $profileField) {
$ldapAttribute = $profileField->ldap_attribute;
$profileFieldName = $profileField->internal_name;
$user->profile->$profileFieldName = $node->getAttribute($ldapAttribute, 0);
}
if ($user->profile->validate() && $user->profile->save()) {
$this->trigger(self::EVENT_UPDATE_USER, new ParameterEvent(['user' => $user, 'node' => $node]));
} else {
Yii::error('Could not create or update ldap user profile! (' . print_r($user->profile->getErrors(), true) . ")");
}
} else {
Yii::error('Could not create or update ldap user '.$user->username.'! (' . print_r($user->getErrors(), true) . ")");
}
return $user;
}
/**
* Converts LDAP Binary GUID to Ascii
*
* @param type $object_guid
* @return type
*/
private function binToStrGuid($object_guid)
{
$hex_guid = bin2hex($object_guid);
if ($hex_guid == "")
return "";
$hex_guid_to_guid_str = '';
for ($k = 1; $k <= 4; ++$k) {
$hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
}
$hex_guid_to_guid_str .= '-';
for ($k = 1; $k <= 2; ++$k) {
$hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
}
$hex_guid_to_guid_str .= '-';
for ($k = 1; $k <= 2; ++$k) {
$hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
}
$hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
return strtolower($hex_guid_to_guid_str);
}
/**
* Checks if LDAP is supported
*/
public static function isAvailable()
{
if (!class_exists('Zend\Ldap\Ldap')) {
return false;
}
if (!function_exists('ldap_bind')) {
return false;
}
return true;
}
}
?>

View File

@ -0,0 +1,37 @@
<?php
use yii\db\Schema;
use yii\db\Migration;
class m151226_164234_authclient extends Migration
{
public function up()
{
$this->createTable('user_auth', array(
'id' => $this->primaryKey(),
'user_id' => $this->integer()->notNull(),
'source' => $this->string(255)->notNull(),
'source_id' => $this->string(255)->notNull(),
));
$this->addForeignKey('fk_user_id', 'user_auth', 'user_id', 'user', 'id', 'CASCADE', 'CASCADE');
}
public function down()
{
echo "m151226_164234_authclient cannot be reverted.\n";
return false;
}
/*
// Use safeUp/safeDown to run migration code within a transaction
public function safeUp()
{
}
public function safeDown()
{
}
*/
}

View File

@ -0,0 +1,59 @@
<?php
namespace humhub\modules\user\models;
use Yii;
/**
* This is the model class for table "user_auth".
*
* @property integer $id
* @property integer $user_id
* @property string $source
* @property string $source_id
*
* @property User $user
*/
class Auth extends \yii\db\ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'user_auth';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['user_id', 'source', 'source_id'], 'required'],
[['user_id'], 'integer'],
[['source', 'source_id'], 'string', 'max' => 255]
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'user_id' => 'User ID',
'source' => 'Source',
'source_id' => 'Source ID',
];
}
/**
* @return \yii\db\ActiveQuery
*/
public function getUser()
{
return $this->hasOne(User::className(), ['id' => 'user_id']);
}
}

View File

@ -187,4 +187,25 @@ class Group extends \yii\db\ActiveRecord
return true;
}
/**
* Returns groups which are available in user registration
*/
public static function getRegistrationGroups()
{
$groups = [];
$defaultGroup = \humhub\models\Setting::Get('defaultUserGroup', 'authentication_internal');
if ($defaultGroup != '') {
$group = self::findOne(['id' => $defaultGroup]);
if ($group !== null) {
$groups[] = $group;
return $groups;
}
} else {
$groups = self::find()->orderBy('name ASC')->all();
}
return $groups;
}
}

View File

@ -32,6 +32,7 @@ class Invite extends \yii\db\ActiveRecord
const SOURCE_SELF = "self";
const SOURCE_INVITE = "invite";
const TOKEN_LENGTH = 12;
/**
* @inheritdoc
@ -48,50 +49,61 @@ class Invite extends \yii\db\ActiveRecord
{
return [
[['user_originator_id', 'space_invite_id', 'created_by', 'updated_by'], 'integer'],
[['email'], 'required'],
[['created_at', 'updated_at', 'firstname', 'lastname'], 'safe'],
[['email', 'source', 'token'], 'string', 'max' => 45],
[['language'], 'string', 'max' => 10],
[['email'], 'unique'],
[['token'], 'unique'],
[['firstname', 'lastname'], 'string', 'max' => 255],
[['email', 'source', 'token'], 'string', 'max' => 45],
[['language'], 'string', 'max' => 10],
[['email'], 'required'],
[['email'], 'unique'],
[['email'], 'email'],
[['email'], 'unique', 'targetClass' => \humhub\modules\user\models\User::className(), 'message' => Yii::t('UserModule.base', 'E-Mail is already in use! - Try forgot password.')],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
public function scenarios()
{
return [
'id' => 'ID',
'user_originator_id' => 'User Originator ID',
'space_invite_id' => 'Space Invite ID',
'email' => 'Email',
'source' => 'Source',
'token' => 'Token',
'created_at' => 'Created At',
'created_by' => 'Created By',
'updated_at' => 'Updated At',
'updated_by' => 'Updated By',
'language' => 'Language',
'firstname' => 'Firstname',
'lastname' => 'Lastname'
];
$scenarios = parent::scenarios();
$scenarios['invite'] = ['email'];
return $scenarios;
}
/**
* @inheritdoc
*/
public function beforeSave($insert)
{
if ($insert) {
$this->token = uniqid() . mt_rand();
if ($insert && $this->token == '') {
$this->token = Yii::$app->security->generateRandomString(self::TOKEN_LENGTH);
}
return parent::beforeSave($insert);
}
public function selfInvite()
{
$this->source = self::SOURCE_SELF;
$this->language = Yii::$app->language;
// Delete existing invite for e-mail - but reuse token
$existingInvite = Invite::findOne(['email' => $this->email]);
if ($existingInvite !== null) {
$this->token = $existingInvite->token;
$existingInvite->delete();
}
if ($this->allowSelfInvite() && $this->validate() && $this->save()) {
$this->sendInviteMail();
return true;
}
return false;
}
/**
* Sends the invite e-mail
*
*/
public function sendInviteMail()
{
@ -128,14 +140,34 @@ class Invite extends \yii\db\ActiveRecord
}
}
/**
* Return user which triggered this invite
*
* @return \yii\db\ActiveQuery
*/
public function getOriginator()
{
return $this->hasOne(\humhub\modules\user\models\User::className(), ['id' => 'user_originator_id']);
}
/**
* Return space which is involved in this invite
*
* @return \yii\db\ActiveQuery
*/
public function getSpace()
{
return $this->hasOne(\humhub\modules\space\models\Space::className(), ['id' => 'space_invite_id']);
}
/**
* Allow users to invite themself
*
* @return boolean allow self invite
*/
public function allowSelfInvite()
{
return (\humhub\models\Setting::Get('anonymousRegistration', 'authentication_internal'));
}
}

View File

@ -160,10 +160,14 @@ class Profile extends \yii\db\ActiveRecord
*/
public function getFormDefinition()
{
$definition = array();
$definition['elements'] = array();
$syncAttributes = [];
if ($this->user !== null) {
$syncAttributes = \humhub\modules\user\authclient\AuthClientHelpers::getSyncAttributesByUser($this->user);
}
foreach (ProfileFieldCategory::find()->orderBy('sort_order')->all() as $profileFieldCategory) {
$category = array(
@ -190,7 +194,7 @@ class Profile extends \yii\db\ActiveRecord
}
// Dont allow editing of ldap syned fields - will be overwritten on next ldap sync.
if ($this->user !== null && $this->user->auth_mode == User::AUTH_MODE_LDAP && $profileField->ldap_attribute != "") {
if (in_array($profileField->internal_name, $syncAttributes)) {
$profileField->editable = false;
}

View File

@ -41,12 +41,6 @@ use humhub\modules\user\components\ActiveQueryUser;
class User extends ContentContainerActiveRecord implements \yii\web\IdentityInterface, \humhub\modules\search\interfaces\Searchable
{
/**
* Authentication Modes
*/
const AUTH_MODE_LDAP = "ldap";
const AUTH_MODE_LOCAL = "local";
/**
* User Status Flags
*/
@ -90,10 +84,11 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte
[['guid'], 'string', 'max' => 45],
[['username'], 'string', 'max' => 25, 'min' => Yii::$app->params['user']['minUsernameLength']],
[['time_zone'], 'in', 'range' => \DateTimeZone::listIdentifiers()],
[['email'], 'string', 'max' => 100],
[['auth_mode'], 'string', 'max' => 10],
[['language'], 'string', 'max' => 5],
[['email'], 'unique'],
[['email'], 'email'],
[['email'], 'string', 'max' => 100],
[['username'], 'unique'],
[['guid'], 'unique'],
[['wall_id'], 'unique']
@ -122,8 +117,9 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte
{
$scenarios = parent::scenarios();
$scenarios['login'] = ['username', 'password'];
$scenarios['editAdmin'] = ['username', 'email', 'group_id', 'super_admin', 'auth_mode', 'status'];
$scenarios['registration'] = ['username', 'email', 'group_id'];
$scenarios['editAdmin'] = ['username', 'email', 'group_id', 'super_admin', 'status'];
$scenarios['registration_email'] = ['username', 'email', 'group_id'];
$scenarios['registration'] = ['username', 'group_id'];
return $scenarios;
}
@ -261,11 +257,11 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte
*/
public function beforeSave($insert)
{
if ($insert) {
if ($this->auth_mode == "") {
$this->auth_mode = self::AUTH_MODE_LOCAL;
if ($this->auth_mode == '') {
$passwordAuth = new \humhub\modules\user\authclient\Password();
$this->auth_mode = $passwordAuth->getId();
}
if (\humhub\models\Setting::Get('allowGuestAccess', 'authentication_internal')) {
@ -277,13 +273,8 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte
$this->last_activity_email = new \yii\db\Expression('NOW()');
// Set Status
if ($this->status == "") {
if (\humhub\models\Setting::Get('needApproval', 'authentication_internal')) {
$this->status = User::STATUS_NEED_APPROVAL;
} else {
$this->status = User::STATUS_ENABLED;
}
$this->status = self::STATUS_ENABLED;
}
if ((\humhub\models\Setting::Get('defaultUserGroup', 'authentication_internal'))) {
@ -296,8 +287,18 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte
}
if ($this->group_id == "") {
// Try autoset group
$availableGroups = Group::getRegistrationGroups();
$defaultUserGroup = \humhub\models\Setting::Get('defaultUserGroup', 'authentication_internal');
if ($defaultUserGroup != '') {
$this->group_id = $defaultUserGroup;
} elseif (isset($availableGroups[0])) {
// Fallback to first available group
$this->group_id = $availableGroups[0]->id;
} else {
throw new \yii\base\Exception("Could not save user without group!");
}
}
return parent::beforeSave($insert);
}
@ -525,4 +526,12 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte
return false;
}
/**
* @return type
*/
public function getAuths()
{
return $this->hasMany(\humhub\modules\user\models\Auth::className(), ['user_id' => 'id']);
}
}

View File

@ -1,120 +0,0 @@
<?php
namespace humhub\modules\user\models\forms;
use Yii;
use yii\base\Model;
use humhub\modules\user\models\User;
use humhub\modules\user\libs\Ldap;
use humhub\models\Setting;
use yii\db\Expression;
/**
* LoginForm is the model behind the login form.
*/
class AccountLogin extends Model
{
/**
* @var string user's username or email address
*/
public $username;
public $password;
public $rememberMe = true;
private $_user = false;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['username', 'password'], 'required'],
['rememberMe', 'boolean'],
['password', 'validatePassword'],
['username', 'validateUser'],
];
}
/**
* Validates the password.
* This method serves as the inline validation for password.
*
* @param string $attribute the attribute currently being validated
* @param array $params the additional name-value pairs given in the rule
*/
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if ($user !== null) {
if ($user->auth_mode === User::AUTH_MODE_LOCAL && $user->currentPassword->validatePassword($this->password)) {
return;
} elseif ($user->auth_mode === User::AUTH_MODE_LDAP && Ldap::isAvailable() && Ldap::getInstance()->authenticate($user->username, $this->password)) {
return;
}
}
$this->addError($attribute, 'Incorrect username or password.');
}
}
public function validateUser($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if ($user !== null) {
if ($user->status == User::STATUS_DISABLED) {
$this->addError($attribute, 'Your account is disabled!');
}
if ($user->status == User::STATUS_NEED_APPROVAL) {
$this->addError($attribute, 'Your account is not approved yet!');
}
}
}
}
/**
* Logs in a user using the provided username and password.
* @return boolean whether the user is logged in successfully
*/
public function login()
{
if ($this->validate() && Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0)) {
$this->_user->last_login = new Expression('NOW()');
$this->_user->save();
return true;
} else {
return false;
}
}
/**
* Finds user by [[username]]
*
* @return User|null
*/
public function getUser()
{
if ($this->_user === false) {
$this->_user = User::find()->where(['username' => $this->username])->orWhere(['email' => $this->username])->one();
// Could not found user -> lookup in LDAP
if ($this->_user === null && Ldap::isAvailable() && Setting::Get('enabled', 'authentication_ldap')) {
try {
// Try load/create LDAP user
$usernameDn = Ldap::getInstance()->ldap->getCanonicalAccountName($this->username, \Zend\Ldap\Ldap::ACCTNAME_FORM_DN);
Ldap::getInstance()->handleLdapUser(Ldap::getInstance()->ldap->getNode($usernameDn));
// Check if user is availble now
$this->_user = User::find()->where(['username' => $this->username])->orWhere(['email' => $this->username])->one();
} catch (\Zend\Ldap\Exception\LdapException $ex) {
// User not found
}
}
}
return $this->_user;
}
}

View File

@ -52,7 +52,9 @@ class AccountRecoverPassword extends \yii\base\Model
if ($this->email != "") {
$user = User::findOne(array('email' => $this->email));
if ($user != null && $user->auth_mode != "local") {
$passwordAuth = new \humhub\modules\user\authclient\Password();
if ($user != null && $user->auth_mode !== $passwordAuth->getId()) {
$this->addError($attribute, Yii::t('UserModule.forms_AccountRecoverPasswordForm', Yii::t('UserModule.forms_AccountRecoverPasswordForm', "Password recovery is not possible on your account type!")));
}
}

View File

@ -1,43 +0,0 @@
<?php
namespace humhub\modules\user\models\forms;
use Yii;
/**
* Register Form just collects users e-mail and sends an invite
*
* @package humhub.modules_core.user.forms
* @since 0.5
* @author Luke
*/
class AccountRegister extends \yii\base\Model
{
public $email;
/**
* Declares the validation rules.
*/
public function rules()
{
return array(
[['email'], 'required'],
[['email'], 'email'],
[['email'], 'unique', 'targetClass' => \humhub\modules\user\models\User::className(), 'message' => Yii::t('UserModule.forms_AccountRegisterForm', 'E-Mail is already in use! - Try forgot password.')],
);
}
/**
* Declares customized attribute labels.
* If not declared here, an attribute would have a label that is
* the same as its name with the first letter in upper case.
*/
public function attributeLabels()
{
return array(
'email' => Yii::t('UserModule.forms_AccountRegisterForm', 'E-Mail'),
);
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace humhub\modules\user\models\forms;
use Yii;
use yii\base\Model;
use humhub\modules\user\authclient\BaseFormAuth;
use humhub\modules\user\models\User;
use humhub\modules\user\libs\Ldap;
use humhub\models\Setting;
use yii\db\Expression;
/**
* LoginForm is the model behind the login form.
*/
class Login extends Model
{
/**
* @var string user's username or email address
*/
public $username;
/**
* @var string password
*/
public $password;
/**
* @var boolean remember user
*/
public $rememberMe = false;
/**
* @var \yii\authclient\BaseClient auth client used to authenticate
*/
public $authClient = null;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['username', 'password'], 'required'],
['rememberMe', 'boolean'],
];
}
/**
* @inheritdoc
*/
public function init()
{
$this->rememberMe = Yii::$app->getModule('user')->loginRememberMeDefault;
parent::init();
}
/**
* Validation
*/
public function afterValidate()
{
$user = null;
// Loop over enabled authclients
foreach (Yii::$app->authClientCollection->getClients() as $authClient) {
if ($authClient instanceof BaseFormAuth) {
$authClient->login = $this;
if ($authClient->auth()) {
$this->authClient = $authClient;
// Delete password after successful auth
$this->password = "";
return;
}
}
}
if ($user === null) {
$this->addError('password', 'User or Password incorrect.');
}
// Delete current password value
$this->password = "";
parent::afterValidate();
}
}

View File

@ -0,0 +1,291 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\models\forms;
use Yii;
use yii\helpers\ArrayHelper;
use humhub\compat\HForm;
use humhub\modules\user\models\User;
use humhub\modules\user\models\Profile;
use humhub\modules\user\models\Password;
/**
* Description of Registration
*
* @author luke
*/
class Registration extends HForm
{
/**
* @var boolean show password creation form
*/
public $enablePasswordForm = true;
/**
* @var boolean show e-mail field
*/
public $enableEmailField = false;
/**
* @var boolean|null require user approval by admin after registration.
*/
public $enableUserApproval = false;
/**
* @var User
*/
private $_user = null;
/**
* @var Password
*/
private $_password = null;
/**
* @var Profile
*/
private $_profile = null;
public function init()
{
if (\humhub\models\Setting::Get('needApproval', 'authentication_internal')) {
$this->enableUserApproval = true;
} else {
$this->enableUserApproval = false;
}
return parent::init();
}
/**
* @inheritdoc
*/
public function render($form)
{
$this->setFormDefinition();
return parent::render($form);
}
/**
* Builds HForm Definition to automatically build form output
*/
protected function setFormDefinition()
{
$this->definition = [];
$this->definition['elements'] = [];
$this->definition['elements']['User'] = $this->getUserFormDefinition();
if ($this->enablePasswordForm) {
$this->definition['elements']['Password'] = $this->getPasswordFormDefinition();
}
$this->definition['elements']['Profile'] = array_merge(array('type' => 'form'), $this->getProfile()->getFormDefinition());
$this->definition['buttons'] = array(
'save' => array(
'type' => 'submit',
'class' => 'btn btn-primary',
'label' => Yii::t('UserModule.controllers_AuthController', 'Create account'),
),
);
}
/**
* Create User Model form fields required for registration
*
* @return array form definition
*/
protected function getUserFormDefinition()
{
$groupModels = \humhub\modules\user\models\Group::find()->orderBy('name ASC')->all();
$defaultUserGroup = \humhub\models\Setting::Get('defaultUserGroup', 'authentication_internal');
$groupFieldType = "dropdownlist";
if ($defaultUserGroup != "") {
$groupFieldType = "hidden";
} else if (count($groupModels) == 1) {
$groupFieldType = "hidden";
$defaultUserGroup = $groupModels[0]->id;
}
if ($groupFieldType == 'hidden') {
$this->getUser()->group_id = $defaultUserGroup;
}
$form = array(
'type' => 'form',
'title' => Yii::t('UserModule.controllers_AuthController', 'Account'),
'elements' => [],
);
$form['elements']['username'] = [
'type' => 'text',
'class' => 'form-control',
'maxlength' => 25,
];
if ($this->enableEmailField) {
$form['elements']['email'] = [
'type' => 'text',
'class' => 'form-control',
];
}
$form['elements']['group_id'] = [
'type' => $groupFieldType,
'class' => 'form-control',
'items' => ArrayHelper::map($groupModels, 'id', 'name'),
'value' => $defaultUserGroup,
];
return $form;
}
/**
* Create Password Model form fields required for registration
*
* @return array form definition
*/
protected function getPasswordFormDefinition()
{
return array(
'type' => 'form',
'elements' => array(
'newPassword' => array(
'type' => 'password',
'class' => 'form-control',
'maxlength' => 255,
),
'newPasswordConfirm' => array(
'type' => 'password',
'class' => 'form-control',
'maxlength' => 255,
),
),
);
}
/**
* Set models User, Profile and Password to Form
*/
protected function setModels()
{
// Set Models
$this->models['User'] = $this->getUser();
$this->models['Profile'] = $this->getProfile();
if ($this->enablePasswordForm) {
$this->models['Password'] = $this->getPassword();
}
return true;
}
/**
* @inheritdoc
*/
public function validate()
{
// Ensure Models
$this->setModels();
return parent::validate();
}
/**
* @inheritdoc
*/
public function submitted($buttonName = "")
{
// Ensure Models
$this->setModels();
return parent::submitted($buttonName);
}
/**
* Registers users
*
* @return boolean state
*/
public function register(\yii\authclient\ClientInterface $authClient = null)
{
$this->models['User']->language = Yii::$app->language;
if ($this->enableUserApproval) {
$this->models['User']->status = User::STATUS_NEED_APPROVAL;
}
if ($this->models['User']->save()) {
// Save User Profile
$this->models['Profile']->user_id = $this->models['User']->id;
$this->models['Profile']->save();
if ($this->enablePasswordForm) {
// Save User Password
$this->models['Password']->user_id = $this->models['User']->id;
$this->models['Password']->setPassword($this->models['Password']->newPassword);
$this->models['Password']->save();
}
if ($authClient !== null) {
\humhub\modules\user\authclient\AuthClientHelpers::storeAuthClientForUser($authClient, $this->models['User']);
$authClient->trigger(\humhub\modules\user\authclient\BaseClient::EVENT_CREATE_USER, new \yii\web\UserEvent(['identity' => $this->models['User']]));
}
return true;
}
return false;
}
/**
* Returns User model
*
* @return User
*/
public function getUser()
{
if ($this->_user === null) {
$this->_user = new User();
if ($this->enableEmailField) {
$this->_user->scenario = 'registration_email';
} else {
$this->_user->scenario = 'registration';
}
}
return $this->_user;
}
/**
* Returns Profile model
*
* @return Profile
*/
public function getProfile()
{
if ($this->_profile === null) {
$this->_profile = $this->getUser()->profile;
$this->_profile->scenario = 'registration';
}
return $this->_profile;
}
/**
* Returns Password model
*
* @return Password
*/
public function getPassword()
{
if ($this->_password === null) {
$this->_password = new Password();
$this->_password->scenario = 'registration';
}
return $this->_password;
}
}

View File

@ -0,0 +1,35 @@
<?php
use \yii\bootstrap\Html;
use yii\helpers\Url;
use yii\widgets\ActiveForm;
use \humhub\models\Setting;
?>
<div class="panel-heading">
<?php echo Yii::t('UserModule.base', '<strong>Connected</strong> accounts'); ?>
</div>
<div class="panel-body">
<?= humhub\modules\user\widgets\AccountSettingsMenu::widget(); ?>
<br />
<p />
<?php foreach ($authClients as $client) : ?>
<div class="media">
<div class="media-body">
<h4 class="media-heading"><strong><?php echo $client->getTitle(); ?></strong></h4>
<?php if ($client->getId() == $currentAuthProviderId): ?>
<?php echo Html::a(Yii::t('UserModule.base', 'Current account'), '#', ['class' => 'btn btn-default pull-right', 'data-method' => 'POST', 'disabled' => 'disabled']); ?>
<?php elseif (in_array($client->getId(), $activeAuthClientIds)) : ?>
<?php echo Html::a(Yii::t('UserModule.base', 'Disconnect account'), ['connected-accounts', 'disconnect' => $client->getId()], ['class' => 'btn btn-danger pull-right', 'data-method' => 'POST']); ?>
<?php else: ?>
<?php echo Html::a(Yii::t('UserModule.base', 'Connect account'), Url::to(['/user/auth/external-auth', 'authclient' => $client->getId()]), ['class' => 'btn btn-success pull-right']); ?>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>

View File

@ -9,6 +9,9 @@ use \humhub\models\Setting;
<?php echo Yii::t('UserModule.views_account_editSettings', '<strong>User</strong> settings'); ?>
</div>
<div class="panel-body">
<?= humhub\modules\user\widgets\AccountSettingsMenu::widget(); ?>
<br />
<p />
<?php $form = ActiveForm::begin(['id' => 'basic-settings-form']); ?>
<?php echo $form->field($model, 'tags'); ?>
@ -19,10 +22,12 @@ use \humhub\models\Setting;
<?php if (Setting::Get('allowGuestAccess', 'authentication_internal')): ?>
<?php echo $form->field($model, 'visibility')->dropdownList([
<?php
echo $form->field($model, 'visibility')->dropdownList([
1 => Yii::t('UserModule.views_account_editSettings', 'Registered users only'),
2 => Yii::t('UserModule.views_account_editSettings', 'Visible for all (also unregistered users)'),
]); ?>
]);
?>
<?php endif; ?>

View File

@ -18,6 +18,13 @@ $this->pageTitle = Yii::t('UserModule.views_auth_login', 'Login');
<div class="panel-body">
<?php if (Yii::$app->session->hasFlash('error')): ?>
<div class="alert alert-danger" role="alert">
<?= Yii::$app->session->getFlash('error') ?>
</div>
<?php endif; ?>
<?php $form = ActiveForm::begin(['id' => 'account-login-form']); ?>
<p><?php echo Yii::t('UserModule.views_auth_login', "If you're already a member, please login with your username/email and password."); ?></p>
<?php echo $form->field($model, 'username')->textInput(['id' => 'login_username', 'placeholder' => Yii::t('UserModule.views_auth_login', 'username or email')]); ?>
@ -39,6 +46,7 @@ $this->pageTitle = Yii::t('UserModule.views_auth_login', 'Login');
</div>
<?php ActiveForm::end(); ?>
<?= humhub\modules\user\widgets\AuthChoice::widget([]) ?>
</div>
</div>
@ -56,9 +64,8 @@ $this->pageTitle = Yii::t('UserModule.views_auth_login', 'Login');
<p><?php echo Yii::t('UserModule.views_auth_login', "Don't have an account? Join the network by entering your e-mail address."); ?></p>
<?php $form = ActiveForm::begin(['id' => 'account-register-form']); ?>
<?php echo $form->field($registerModel, 'email')->textInput(['id' => 'register-email', 'placeholder' => Yii::t('UserModule.views_auth_login', 'email')]); ?>
<?php $form = ActiveForm::begin(['id' => 'invite-form']); ?>
<?php echo $form->field($invite, 'email')->textInput(['id' => 'register-email', 'placeholder' => Yii::t('UserModule.views_auth_login', 'email')]); ?>
<hr>
<?php echo CHtml::submitButton(Yii::t('UserModule.views_auth_login', 'Register'), array('class' => 'btn btn-primary')); ?>
@ -86,7 +93,7 @@ $this->pageTitle = Yii::t('UserModule.views_auth_login', 'Login');
<?php } ?>
// Shake panel after wrong validation
<?php if ($registerModel->hasErrors()) { ?>
<?php if ($invite->hasErrors()) { ?>
$('#register-form').removeClass('bounceInLeft');
$('#register-form').addClass('shake');
$('#login-form').removeClass('bounceIn');

View File

@ -16,11 +16,11 @@ use yii\helpers\Url;
<?php if ($canRegister) : ?>
<div class="text-center">
<ul id="tabs" class="nav nav-tabs tabs-center" data-tabs="tabs">
<li class="<?php echo (!isset($_POST['AccountRegister'])) ? "active" : ""; ?> tab-login"><a
<li class="<?php echo (!isset($_POST['Invite'])) ? "active" : ""; ?> tab-login"><a
href="#login"
data-toggle="tab"><?php echo Yii::t('SpaceModule.views_space_invite', 'Login'); ?></a>
</li>
<li class="<?php echo (isset($_POST['AccountRegister'])) ? "active" : ""; ?> tab-register"><a
<li class="<?php echo (isset($_POST['Invite'])) ? "active" : ""; ?> tab-register"><a
href="#register"
data-toggle="tab"><?php echo Yii::t('SpaceModule.views_space_invite', 'New user?'); ?></a>
</li>
@ -31,18 +31,12 @@ use yii\helpers\Url;
<div class="tab-content">
<div class="tab-pane <?php echo (!isset($_POST['AccountRegister'])) ? "active" : ""; ?>" id="login">
<div class="tab-pane <?php echo (!isset($_POST['Invite'])) ? "active" : ""; ?>" id="login">
<?php $form = ActiveForm::begin(); ?>
<p><?php echo Yii::t('UserModule.views_auth_login', "If you're already a member, please login with your username/email and password."); ?></p>
<?php echo $form->field($model, 'username')->textInput(['id' => 'login_username', 'placeholder' => Yii::t('UserModule.views_auth_login', 'username or email')]); ?>
<?php echo $form->field($model, 'password')->passwordInput(['id' => 'login_password', 'placeholder' => Yii::t('UserModule.views_auth_login', 'password')]); ?>
<?php echo $form->field($model, 'rememberMe')->checkbox(); ?>
<hr>
<div class="row">
@ -87,16 +81,17 @@ use yii\helpers\Url;
</div>
<?php ActiveForm::end(); ?>
<?= humhub\modules\user\widgets\AuthChoice::widget([]) ?>
</div>
<?php if ($canRegister) : ?>
<div class="tab-pane <?php echo (isset($_POST['AccountRegister'])) ? "active" : ""; ?>"
<div class="tab-pane <?php echo (isset($_POST['Invite'])) ? "active" : ""; ?>"
id="register">
<p><?php echo Yii::t('UserModule.views_auth_login', "Don't have an account? Join the network by entering your e-mail address."); ?></p>
<?php $form = ActiveForm::begin(); ?>
<?php echo $form->field($registerModel, 'email')->textInput(['id' => 'register-email', 'placeholder' => Yii::t('UserModule.views_auth_login', 'email')]); ?>
<?php echo $form->field($invite, 'email')->textInput(['id' => 'register-email', 'placeholder' => Yii::t('UserModule.views_auth_login', 'email')]); ?>
<hr>
<?php

View File

@ -75,8 +75,8 @@ class AccountMenu extends BaseMenu
'isActive' => (Yii::$app->controller->action->id == "emailing"),
));
// LDAP users cannot change their e-mail address
if (Yii::$app->user->getIdentity()->auth_mode != User::AUTH_MODE_LDAP) {
if (Yii::$app->user->canChangeEmail()) {
$this->addItem(array(
'label' => Yii::t('UserModule.widgets_AccountMenuWidget', 'E-Mail'),
'icon' => '<i class="fa fa-paper-plane"></i>',
@ -87,8 +87,7 @@ class AccountMenu extends BaseMenu
));
}
// LDAP users cannot changes password or delete account
if (Yii::$app->user->getIdentity()->auth_mode != User::AUTH_MODE_LDAP) {
if (Yii::$app->user->canChangePassword()) {
$this->addItem(array(
'label' => Yii::t('UserModule.widgets_AccountMenuWidget', 'Password'),
'icon' => '<i class="fa fa-key"></i>',
@ -97,6 +96,10 @@ class AccountMenu extends BaseMenu
'sortOrder' => 500,
'isActive' => (Yii::$app->controller->action->id == "change-password"),
));
}
if (Yii::$app->user->canDeleteAccount()) {
$this->addItem(array(
'label' => Yii::t('UserModule.widgets_AccountMenuWidget', 'Delete account'),
'icon' => '<i class="fa fa-trash-o"></i>',

View File

@ -0,0 +1,68 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\user\widgets;
use Yii;
use yii\helpers\Url;
use humhub\models\Setting;
/**
* Account Settings Tab Menu
*/
class AccountSettingsMenu extends \humhub\widgets\BaseMenu
{
/**
* @inheritdoc
*/
public $template = "@humhub/widgets/views/tabMenu";
/**
* @inheritdoc
*/
public function init()
{
$this->addItem(array(
'label' => Yii::t('UserModule.base', 'Basic Settings'),
'url' => Url::toRoute(['/user/account/edit-settings']),
'sortOrder' => 100,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'user' && Yii::$app->controller->id == 'account' && Yii::$app->controller->action->id == 'edit-settings'),
));
if (count($this->getSecondoaryAuthProviders()) != 0) {
$this->addItem(array(
'label' => Yii::t('UserModule.base', 'Connected Accounts'),
'url' => Url::toRoute(['/user/account/connected-accounts']),
'sortOrder' => 200,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'user' && Yii::$app->controller->id == 'account' && Yii::$app->controller->action->id == 'connected-accounts'),
));
}
parent::init();
}
/**
* Returns optional authclients
*
* @return \yii\authclient\ClientInterface[]
*/
protected function getSecondoaryAuthProviders()
{
$clients = [];
foreach (Yii::$app->get('authClientCollection')->getClients() as $client) {
if (!$client instanceof \humhub\modules\user\authclient\BaseFormAuth && !$client instanceof \humhub\modules\user\authclient\interfaces\PrimaryClient) {
$clients[] = $client;
}
}
return $clients;
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace humhub\modules\user\widgets;
use yii\base\Widget;
use Yii;
use yii\authclient\ClientInterface;
class AuthChoice extends \yii\authclient\widgets\AuthChoice
{
/**
* @inheritdoc
*/
public $popupMode = false;
/**
* @var ClientInterface[] auth providers list.
*/
private $_clients;
/**
* @param ClientInterface[] $clients auth providers
*/
public function setClients(array $clients)
{
$this->_clients = $clients;
}
/**
* @return ClientInterface[] auth providers
*/
public function getClients()
{
if ($this->_clients === null) {
$clients = [];
foreach ($this->defaultClients() as $client) {
// Don't show clients which need login form
if (!$client instanceof \humhub\modules\user\authclient\BaseFormAuth) {
$clients[] = $client;
}
}
$this->_clients = $clients;
}
return $this->_clients;
}
/**
* @inheritdoc
*/
protected function defaultBaseAuthUrl()
{
$baseAuthUrl = ['/user/auth/external-auth'];
$params = $_GET;
unset($params[$this->clientIdGetParamName]);
$baseAuthUrl = array_merge($baseAuthUrl, $params);
return $baseAuthUrl;
}
/**
* Renders the main content, which includes all external services links.
*/
protected function renderMainContent()
{
if (count($this->getClients()) != 0) {
echo \yii\helpers\Html::tag('br');
echo \yii\helpers\Html::tag('hr');
echo \yii\helpers\Html::tag('strong', Yii::t('UserModule.base', 'Or login by using:'));
echo \yii\helpers\Html::tag('p');
parent::renderMainContent();
}
}
}

View File

@ -19,8 +19,8 @@ class LoginPage extends BasePage
*/
public function login($username, $password)
{
$this->actor->fillField('input[name="AccountLogin[username]"]', $username);
$this->actor->fillField('input[name="AccountLogin[password]"]', $password);
$this->actor->fillField('input[name="Login[username]"]', $username);
$this->actor->fillField('input[name="Login[password]"]', $password);
$this->actor->click('login-button');
}