1
votes

I am newbie with Yii2 and I need to create a login system using ldap. There is not much information about it so I hope somebody can help me.

I installed edvlerblog/yii2-adldap-module and I already succeed with the authentication implementation.

My problem is that I want, after login, have the identity of the user the same way as it is working with the advanced template and be able to use the Yii::$app->user functionality.

The official example creates User which implements IdentityInterface but through ActiveRecord:

http://www.yiiframework.com/doc-2.0/yii-web-identityinterface.html

I also found many examples referring to Yii version 1. This is a good one:

https://www.exchangecore.com/blog/yii-active-directory-useridentity-login-authentication/

But still not able to make it work... Probably is a concept problem or a syntax issue, but anyway I would really appreciate some help here.

models/LoginForm.php authentication method:

public function validatePasswordLdap($attribute, $params)
{
    if (!$this->hasErrors()) {
        $user = $this->getUserLdap();
        if (!$user || !Yii::$app->ldap->authenticate($this->username,$this->password)) {
            $this->addError($attribute, 'Incorrect username or passwords.');
        }
    }
}

models/LoginForm.php login method:

public function loginLdap()
{
    if ($this->validate()) {
        return Yii::$app->user->login($this->getUserLdap(), $this->rememberMe ? 3600 * 24 * 30 : 0);
    } else {
        return false;
    }
}

and the last one, the getUser method. UserLdap is implenting IdentityInterface but I don't know how to do it correctly:

public function getUserLdap()
{
    if ($this->_user === false) {           
        $this->_user = UserLdap::findIdentity($this->username);
    }

    return $this->_user;
}
3

3 Answers

2
votes

I understood the logic of that finally:

  • When you login in the view the LoginForm.php, takes the username and password and validates using validatePasswordLdap. You have to specify in the rules which function is validating the password.

  • Then if validate() is OK, it calls FindIdentity with the username introduced in the login. This function has to return an IdentityInterface object so first you need to create the User object, set the parameters (email, telephone, name,..) and return it.

  • To user the login/logout functionality and the isGuest throughout the site, you just have to do as loginLdap function below and pass this user object to the Yii::$app->user->login method.

The code looks like this:

LoginForm.php

public function rules()
{
    return [
        // username and password are both required
        [['username', 'password'], 'required'],
        // rememberMe must be a boolean value
        ['rememberMe', 'boolean'],
       // password is validated by validatePassword()
        ['password', 'validatePasswordLdap'],
    ];
}


public function validatePasswordLdap($attribute, $params)
{
    if (!$this->hasErrors()) {
        $user = $this->getUserLdap();
        if (!$user || !Yii::$app->ldap->authenticate($this->username,$this->password)) {
            $this->addError($attribute, 'Incorrect username or passwords.');
        }
    }
}

public function loginLdap()
{
    if ($this->validate()) {
        return Yii::$app->user->login($this->getUserLdap(), $this->rememberMe ? 3600 * 24 * 30 : 0);
    } else {
        return false;
    }
}

User.php

public static function findIdentity($id)
{

    $new_identity = new User();

    if ($user_ldap_info = Yii::$app->ldap->user()->infoCollection($id, array("*"))){
        $new_identity->setId($user_ldap_info->samaccountname);
        $new_identity->setEmail($user_ldap_info->mail);
        $new_identity->setUsername($user_ldap_info->displayName);   
    }

    return $new_identity;
}

public function setEmail($email)
{
    $this->email = $email;
}

public function setUsername($username)
{
    $this->username = $username;
}

public function setId($id)
{
    $this->id = $id;
}

Inside LoginForm.php

public function getUserLdap()
{
    if ($this->_user === false) {           
        $this->_user = User::findIdentity($this->username);
    }

    return $this->_user;
}

EDIT: due to vendor ADLDAP update I had to change the findIdentity to this:

public static function findIdentity($id)
{
    $new_identity = new User ();

    if ( $user_ldap_info = Yii::$app->ldap->users()->find($id) ) {
        $new_identity->setId ( $user_ldap_info->samaccountname [0] );
        $new_identity->setEmail ( $user_ldap_info->mail[0] );
        $new_identity->setUsername ( $user_ldap_info->givenname [0] );
    }

    return $new_identity;
}
0
votes

I was hoping to find that Daniel Stolf published a working example of what he described.

While searching, I wondered if this has been accomplished with latest Larvel 5.1 and found this:

https://libraries.io/github/sroutier/laravel-5.1-enterprise-starter-kit

Optional LDAP/AD authentication using sroutier/eloquent-ldap, with options to:

Automatically creates local account for LDAP/AD users on first login.
Automatically assign to matching local roles based on LDAP/AD group membership.
Refresh role assignment on login.

This is exactly what I was looking for.. I would've preferred to use Yii2 framework, but think I'll be shifting to Laravel due to lack of such features-in-one-extension for Yii2.

This is unless Konrad kindly rewrites this guide for Yii2: http://blog.realhe.ro/windows/yii-role-mapping-based-on-active-directory

0
votes

First a disclaimer beforehand: the posted code snippets are all basic examples. You'll need to handle trivial Exceptions and further logic by yourself.


I will post a fast hack to achieve a simple AD authentication with the Yii2 advance template, which will take a username/password and authenticate this combination against a MS Active Directory domain controller. In addition, it checks a given group membership from the user, for which reason only users with this group can log in.

We assume that:

  • The user (particular the username) have to exist in the current user table (check out the Authorization Guide to build the rbac structure). We assume further that the username from our database is the same username you want to authenticate against the AD.
  • You have set up correctly an Adldap2 wrapper for Yii2 (like alexeevdv/yii2-adldap) or load Adladp2 as a vendor module. Benefit from the wrapper: You can configure the adldap2 class in your components section of the application configuration and then you can use the wrapper as a Yii2 component.
  • Your PHP-Environment use the LDAP-Extension (e.g. run something like apt-get install php5-ldap on debian distributions).
  • You have some admin credentials to connect to the domain controller with the permission to query the AD in the way you want.

So, lets start with the basic set-up.

Set up the wrapper config (e.g. config/main-local.php).

'components' => [
    'ldap' => [
        'class' => 'alexeevdv\adldap\Adldap',
        'options' => [
            'account_suffix' => '@stackoverflow.com',
            'domain_controllers' => ['dc1.stackoverflow.com', 'dc2.stackoverflow.com'],
            'base_dn' => 'dc=stackoverflow,dc=com',
            'admin_username' => 'someusername',
            'admin_password' => 'somepassword',
            'use_ssl' => true,
            'port' => '636'
        ],
    ],
],

I want to switch easily between ldap and local authentication. Configure some local params to have globally accessible application parameters (e.g. config/params-local.php).

return [
    'adminEmail' => '[email protected]',
    'authOverLdap' => true,
    'ldapGroup' => 'someldapgroup',
];

Edit your LoginForm.php, especially the validatePassword function (e.g. common/models/LoginForm.php).

/**
 * Validates the password.
 * This method serves as the inline validation for password.
 * If the authOverLdap attribute is set in the params config,
 * user and password will be authenticated over ldap
 *
 * @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();
        // to switch between the auth-methods
        $authOverLdap = \Yii::$app->params['authOverLdap'];
        if ($authOverLdap) {
            if (!$user || !$user->validateCredentialsOverLdap($user->username, $this->password)) {
                $this->addError($attribute, 'Some error text.');
            }
        } else {
            if (!$user || !$user->validatePassword($this->password)) {
                $this->addError($attribute, 'Some error text.');
            }
        }
    }
}

Add the validateCredentialsOverLdap function in the user model which handles the LDAP auth (e.g. /common/models/User.php).

/**
 * Validates a user/password combination over ldap
 *
 * @param string $username username to validate over ldap
 * @param string $password password to validate over ldap
 * @return boolean if the provided credentials are correct and the user is a member of **ldapGroup**
 */
public function validateCredentialsOverLdap($username, $password)
{
    $authSuccess = false;
    // checking the supplied credentials against the ldap (e.g. Active Directory)
    // first step: the user must have a valid account
    // second step: the user must be in a special group
    $authOk = \Yii::$app->ldap->authenticate($username, $password);
    if ($authOk) {
        $adUser = \Yii::$app->ldap->users()->find($username);
        // the user must be in special group (set in Yii params)
        if($adUser->inGroup(\Yii::$app->params['ldapGroup'])) {
            $authSuccess = true;
        }
    }

    return $authSuccess;
}

Disclaimer:

  • Don't copy&paste these snippets! You have to know what are you doing!
  • This shall show you an example and give you a hint to achieve AD authentication with the wrapper and the Yii2 Framework.
  • I run this code in a walled garden intranet!
  • Use ever, ever the secure layers like SSL to communicate over LDAP! You dont know who is sniffing the traffic in your problably safe network. You dealing with User credentials. This can be a massive fuck up, especially in Single Sign On Environments! Don't be silly.