phplaravel-5vendoradldap

Adldap2-Laravel, where to put my logic


I need to use Adldap2-Laravel. My laravel app is based in Laravel-boilerplate 5.

The only login() method that I see that the app is using is this one: vendor\laravel\framework\src\Illuminate\Foundation\Auth\AuthenticatesUsers.php I know it's a vendor, but for testing purposes I have edited the public function login(Request $request) method like this way:

public function login(Request $request){
    if (\Adldap::auth()->attempt(str_replace('@example.com', '', $request->email), $request->password)) { //THIS IS THE ONLY LINE THAT IA HAVE ADDED, THE REST OF THE CODE IS THE ORIGINAL ONE
       $this->validateLogin($request);
        if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }
    }
    else {
        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }
}

It all works fine, but how could I do it without touching this vendor file? Where should I look to implement that if-else with that Adlap verification.


Solution

  • You could use the default login page form.

    All the steps to do that are explains there: https://github.com/jotaelesalinas/laravel-simple-ldap-auth

    I'll explain everything till the first step in case you missed something.

    1. install Adldap2-Laravel

    cd yourLaravalProjectFolder
    composer require adldap2/adldap2-laravel
    

    2. Register Adldap's service providers and façade in config/app.php

    'providers' => [
        ...
        Adldap\Laravel\AdldapServiceProvider::class,
        Adldap\Laravel\AdldapAuthServiceProvider::class,
    ],
    
    'aliases' => [
        ...
        'Adldap' => Adldap\Laravel\Facades\Adldap::class,
    ],
    

    Publish Adldap:

    php artisan vendor:publish --tag="adldap"
    

    3. Change the driver of the user provider in config/auth.php

    'providers' => [
        'users' => [
            'driver' => 'adldap', // was 'eloquent'
            'model'  => App\User::class,
        ],
    ],
    

    4. Configure the Adldap2 connection in config/adldap.php
    tried to add a new connection and leave default untouched, but it didn't work. Adldap2 kept trying to connect as administrator using the default setup, so I had to modify default directly:

    'connections' => [
        'default' => [
            'auto_connect' => false,
            'connection' => Adldap\Connections\Ldap::class,
            'schema' => Adldap\Schemas\OpenLDAP::class, // was Adldap\Schemas\ActiveDirectory::class
            'connection_settings' => [
                'account_prefix' => env('ADLDAP_ACCOUNT_PREFIX', ''),
                'account_suffix' => env('ADLDAP_ACCOUNT_SUFFIX', ''),
                'domain_controllers' => explode(' ', env('ADLDAP_CONTROLLERS', 'corp-dc1.corp.acme.org corp-dc2.corp.acme.org')),
                'port' => env('ADLDAP_PORT', 389),
                'timeout' => env('ADLDAP_TIMEOUT', 5),
                'base_dn' => env('ADLDAP_BASEDN', 'dc=corp,dc=acme,dc=org'),
                'admin_account_suffix' => env('ADLDAP_ADMIN_ACCOUNT_SUFFIX', ''),
                'admin_username' => env('ADLDAP_ADMIN_USERNAME', ''),
                'admin_password' => env('ADLDAP_ADMIN_PASSWORD', ''),
                'follow_referrals' => true,
                'use_ssl' => false,
                'use_tls' => false,
            ],
        ],
    ],
    

    5. Change the usernames and attributes to synchronize in config/adldap_auth.php:

    This configuration specifies which fields are copied from the LDAP server into the local database for each logged in user.

    Some examples of extra attributes to synchronize could be "role" to control access to certain areas or "session_expiration_in_minutes" to force logout after some time. I am sure you can think of many other uses.

    The number of fields available in the testing LDAP server is limited, so we will add 'phone' as an example.

    'usernames' => [
        'ldap' => env('ADLDAP_USER_ATTRIBUTE', 'userprincipalname'), // was just 'userprincipalname'
        'eloquent' => 'username', // was 'email'
    ],
    
    'sync_attributes' => [
        // 'field_in_local_db' => 'attribute_in_ldap_server',
        'username' => 'uid', // was 'email' => 'userprincipalname',
        'name' => 'cn',
        'phone' => 'telephonenumber',
    ],
    

    6. Configure your LDAP and database connections in .env

    FYI, configuration that is secret, i.e. API tokens or database passwords, should be stored in this file, which Laravel includes by default in .gitignore.

    ADLDAP_CONNECTION=default
    ADLDAP_CONTROLLERS=ldap.forumsys.com 
    ADLDAP_BASEDN=dc=example,dc=com
    ADLDAP_USER_ATTRIBUTE=uid
    ADLDAP_USER_FORMAT=uid=%s,dc=example,dc=com
    
    DB_CONNECTION=sqlite  # was 'mysql'
    DB_HOST=127.0.0.1     # remove this line
    DB_PORT=3306          # remove this line
    DB_DATABASE=homestead # remove this line
    DB_USERNAME=homestead # remove this line
    DB_PASSWORD=secret    # remove this line
    

    7. Change database/migrations/2014_10_12_000000_create_users_table.php

    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('username')->unique(); // was 'email'
            $table->string('password');
            $table->string('name'); // to be read from LDAP
            $table->string('phone'); // extra field to read from LDAP
            $table->rememberToken();
            $table->timestamps();
        });
    }
    

    8. Delete the file database/migrations/2014_10_12_100000_create_password_resets_table.php

    9. Change app/User.php

    protected $fillable = [
        // replace 'email' with 'username' and add 'phone'
        'name', 'username', 'password', 'phone',
    ];
    

    10 Run the migration to create the users table and Auth scaffolding

    Before migrating, make sure that your database is configured and working properly.

    touch database/database.sqlite
    php artisan migrate
    php artisan make:auth
    

    This last command installs many controllers and views that we are not going to need, so let's remove them.

    11. Delete these files and folder

     - app/Http/Controllers/Auth/ForgotPasswordController.php
     - app/Http/Controllers/Auth/RegisterController.php
     - app/Http/Controllers/Auth/ResetPasswordController.php
     - resources/views/auth/register.blade.php
     - resources/views/auth/passwords --> remove folder and all files inside
    

    12. Remove this line from resources/views/layouts/app.blade.php

    <li><a href="{{ route('register') }}">Register</a></li>
    

    13. Remove this line from resources/views/welcome.blade.php

    <a href="{{ url('/register') }}">Register</a>
    

    14. Change 'email' for 'username' in resources/views/auth/login.blade.php

    <div class="form-group{{ $errors->has('username') ? ' has-error' : '' }}">
        <label for="username" class="col-md-4 control-label">Username</label>
        <div class="col-md-6">
            <input id="username" type="text" class="form-control" name="username" value="{{ old('username') }}" required autofocus>
            @if ($errors->has('username'))
                <span class="help-block">
                    <strong>{{ $errors->first('username') }}</strong>
                </span>
            @endif
        </div>
    </div>
    

    15. Add these methods to LoginController in app/Http/Controllers/Auth/LoginController.php

    Don't forget the use instructions.

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;
    use Adldap\Laravel\Facades\Adldap;
    
    class LoginController extends Controller {
        ...
    
        public function username() {
            return config('adldap_auth.usernames.eloquent');
        }
    
        protected function validateLogin(Request $request) {
            $this->validate($request, [
                $this->username() => 'required|string|regex:/^\w+$/',
                'password' => 'required|string',
            ]);
        }
    
        protected function attemptLogin(Request $request) {
            $credentials = $request->only($this->username(), 'password');
            $username = $credentials[$this->username()];
            $password = $credentials['password'];
    
            $user_format = env('ADLDAP_USER_FORMAT', 'cn=%s,'.env('ADLDAP_BASEDN', ''));
            $userdn = sprintf($user_format, $username);
    
            // you might need this, as reported in
            // [#14](https://github.com/jotaelesalinas/laravel-simple-ldap-auth/issues/14):
            // Adldap::auth()->bind($userdn, $password);
    
            if(Adldap::auth()->attempt($userdn, $password, $bindAsUser = true)) {
                // the user exists in the LDAP server, with the provided password
    
                $user = \App\User::where($this->username(), $username) -> first();
                if (!$user) {
                    // the user doesn't exist in the local database, so we have to create one
    
                    $user = new \App\User();
                    $user->username = $username;
                    $user->password = '';
    
                    // you can skip this if there are no extra attributes to read from the LDAP server
                    // or you can move it below this if(!$user) block if you want to keep the user always
                    // in sync with the LDAP server 
                    $sync_attrs = $this->retrieveSyncAttributes($username);
                    foreach ($sync_attrs as $field => $value) {
                        $user->$field = $value !== null ? $value : '';
                    }
                }
    
                // by logging the user we create the session, so there is no need to login again (in the configured time).
                // pass false as second parameter if you want to force the session to expire when the user closes the browser.
                // have a look at the section 'session lifetime' in `config/session.php` for more options.
                $this->guard()->login($user, true);
                return true;
            }
    
            // the user doesn't exist in the LDAP server or the password is wrong
            // log error
            return false;
        }
    
        protected function retrieveSyncAttributes($username) {
            $ldapuser = Adldap::search()->where(env('ADLDAP_USER_ATTRIBUTE'), '=', $username)->first();
            if ( !$ldapuser ) {
                // log error
                return false;
            }
            // if you want to see the list of available attributes in your specific LDAP server:
            // var_dump($ldapuser->attributes); exit;
    
            // needed if any attribute is not directly accessible via a method call.
            // attributes in \Adldap\Models\User are protected, so we will need
            // to retrieve them using reflection.
            $ldapuser_attrs = null;
    
            $attrs = [];
    
            foreach (config('adldap_auth.sync_attributes') as $local_attr => $ldap_attr) {
                if ( $local_attr == 'username' ) {
                    continue;
                }
    
                $method = 'get' . $ldap_attr;
                if (method_exists($ldapuser, $method)) {
                    $attrs[$local_attr] = $ldapuser->$method();
                    continue;
                }
    
                if ($ldapuser_attrs === null) {
                    $ldapuser_attrs = self::accessProtected($ldapuser, 'attributes');
                }
    
                if (!isset($ldapuser_attrs[$ldap_attr])) {
                    // an exception could be thrown
                    $attrs[$local_attr] = null;
                    continue;
                }
    
                if (!is_array($ldapuser_attrs[$ldap_attr])) {
                    $attrs[$local_attr] = $ldapuser_attrs[$ldap_attr];
                }
    
                if (count($ldapuser_attrs[$ldap_attr]) == 0) {
                    // an exception could be thrown
                    $attrs[$local_attr] = null;
                    continue;
                }
    
                // now it returns the first item, but it could return
                // a comma-separated string or any other thing that suits you better
                $attrs[$local_attr] = $ldapuser_attrs[$ldap_attr][0];
                //$attrs[$local_attr] = implode(',', $ldapuser_attrs[$ldap_attr]);
            }
    
            return $attrs;
        }
    
        protected static function accessProtected ($obj, $prop) {
            $reflection = new \ReflectionClass($obj);
            $property = $reflection->getProperty($prop);
            $property->setAccessible(true);
            return $property->getValue($obj);
        }
    
    }
    

    Run the website

    We're done!

    Don't forget to set the web server port to 8000 in your local testing .env file:

    APP_URL=http://localhost:8000
    

    Let's run the website and try to log in.

    php artisan serve
    

    Visit http://localhost:8000 in your favourite browser.

    Try to visit http://localhost:8000/home before logging in.

    Hope it will help you.