Skip to content
This repository was archived by the owner on Jul 24, 2023. It is now read-only.

Custom UserProvider extended NoDatabaseUserProvider #575

Closed
TwanoO67 opened this issue Aug 20, 2018 · 5 comments
Closed

Custom UserProvider extended NoDatabaseUserProvider #575

TwanoO67 opened this issue Aug 20, 2018 · 5 comments

Comments

@TwanoO67
Copy link

  • Laravel Version: 5.6
  • Adldap2-Laravel Version: ^4.0
  • PHP Version: 7.1
  • LDAP Type: ActiveDirectory

Description:

Hello! :)

I'm trying to create my own provider that extends NoDatabaseUserProvider.

But I can't because of this line:

if ($provider == NoDatabaseUserProvider::class) {

which is testing the class selected by string comparison.
So my provider even if it extend NoDatabaseUserProvider, is treated as DatabaseUserProvider.

Is it possible to replace that comparison by something using "instanceof" ?
Like in this class:

if ($provider instanceof NoDatabaseUserProvider) {

That would be very usefull :)

Regards,
Antoine

@stevebauman
Copy link
Member

Hi @TwanoO67, thanks for bringing this up! Going to label this as a bug. I definitely want developers to be able to extend the providers.

Going to fix this tonight! 😄

stevebauman added a commit that referenced this issue Sep 11, 2018
@stevebauman
Copy link
Member

Fixed in v4.0.8.

Thanks!

@hartleypool
Copy link

Hi @stevebauman,

I believe, I have the same but opposite problem as @TwanoO67 above.
I'm trying to override DatabaseServiceProvider:: validateCredentials() by extending it.

However there is a check here AdldapAuthServiceProvider for if ($provider == DatabaseUserProvider::class) { which doesn't evaluate to true if extending DatabaseUserProvider.

Perhaps there is another recommended approach to extending that user provider?

Background:
Using Doctrine instead of Eloquent, and need to map the Adldap model values to specific Doctrine User entities, but otherwise want to take advantage of all the code your got for importing and syncing users.

@TwanoO67
Copy link
Author

Hi @hartleypool,

I think that using instanceof everywhere instead of comparing class should fix your issue.

Furthermore,I dont know if it can help you with your problem, but there is some option in the original adldap package to specify the User model you want. So you don't need to override the user provider to change model class.
I hope that it will help :)

@hartleypool
Copy link

@TwanoO67 Thanks. Yes, I had already changed the config provider.users.model to point to my Doctrine User model, but the difference between the Eloquent and Doctrine user model meant that it doesn't work 'out of the box' due to how the DatabaseUserProvider makes pretty Eloquent-specific calls.

It turned out to be fairly straightforward, so in case someone else comes across this. I'll document what worked below for creating a provider that overrides/extends DatabaseUserProvider:

In config/auth.php:
Set the model to point to the non-Eloquent User model (in my case, Doctrine):

'providers' => [
        'users' => [
            'driver' => 'adldap',
            'model' => App\Model\ORM\User::class,
        ],

Make custom Provider :
php artisan make:provider DoctrineDatabaseUserProvider

In config/adldap_auth.php:
Set provider that Adldap uses to the new provider:
'provider' => App\Providers\DoctrineDatabaseUserProvider::class,

Edit new DoctrineDatabaseUserProvider.php
For my case I needed to override the following methods:

  • validateCredentials() to change how a Doctrine User was called
  • retrieveById() to use Doctrine EntityManager to find a user by ID
  • updateRememberToken() to use Doctrine EntityManager to update the token
<?php

namespace App\Providers;

use Adldap\Models\User;
use Adldap\Laravel\Auth\Provider;
use Adldap\Laravel\Auth\DatabaseUserProvider;
use Adldap\Laravel\Facades\Resolver;
use Adldap\Laravel\Commands\Import;
use Adldap\Laravel\Commands\SyncPassword;
use Adldap\Laravel\Events\Imported;
use Adldap\Laravel\Events\AuthenticationRejected;
use Adldap\Laravel\Events\AuthenticationSuccessful;
use Adldap\Laravel\Events\DiscoveredWithCredentials;
use Adldap\Laravel\Events\AuthenticatedWithCredentials;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Config;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Hash;
use DateTime;
use EntityManager;

/**
 * Provider replaces Adldap\Laravel\Auth\DatabaseUserProvider by supporting 
 * Doctrine User Model in place of expected Eloquent User Model.
 * 
 */
class DoctrineDatabaseUserProvider extends DatabaseUserProvider
{

    /**
     * The Doctrine user model.
     *
     * @var string
     */
    protected $model;

    /**
     * The currently authenticated LDAP user.
     *
     * @var User|null
     */
    protected $user;

    /**
     * Constructor.
     *
     */
    public function __construct()
    {
        $this->model = array_get(config('auth'), 'providers.users.model');
    }

    /**
     * {@inheritdoc}
     */
    public function validateCredentials(Authenticatable $model, array $credentials)
    {
        if ($this->user instanceof User) {
            // If an LDAP user was discovered, we can go
            // ahead and try to authenticate them.
            if (Resolver::authenticate($this->user, $credentials)) {
                Event::fire(new AuthenticatedWithCredentials($this->user, $model));

                // Here we will perform authorization on the LDAP user. If all
                // validation rules pass, we will allow the authentication
                // attempt. Otherwise, it is automatically rejected.
                if ($this->passesValidation($this->user, $model)) {
                    // Here we can now synchronize / set the users password since
                    // they have successfully passed authentication
                    // and our validation rules.
                    Bus::dispatch(new SyncPassword($model, $credentials));

                    // map to local user and save
                    $this->model = $this->saveLocalUser();

                    if ($this->localUserCreated($model->getCreatedOn())) {
                        // If the model was recently created, they
                        // have been imported successfully.
                        Event::fire(new Imported($this->user, $this->model));
                    }

                    Event::fire(new AuthenticationSuccessful($this->user, $model));

                    return true;
                }

                Event::fire(new AuthenticationRejected($this->user, $model));
            }

            // LDAP Authentication failed.
            return false;
        }

        if ($this->isFallingBack() && $model->exists) {
            // If the user exists in our local database already and fallback is
            // enabled, we'll perform standard eloquent authentication.
            return $this->fallback->validateCredentials($model, $credentials);
        }

        return false;
    }

    /**
     * {@inheritdoc}
     * 
     */
    public function retrieveById($identifier)
    {
        return EntityManager::getRepository(\App\Model\ORM\User::class)->findOneBy(['id' => (int) $identifier ]);
    }
     
    /**
     * Update the "remember me" token for the given user in storage.
     *
     * @param Authenticatable $user
     * @param string          $token
     *
     * @return void
     */
    public function updateRememberToken(Authenticatable $user, $token)
    {
        $user->setRememberToken($token);
        EntityManager::persist($user);
        EntityManager::flush();         
    }

    /**
     * Checks if an LDAP user has already received a local account 
     * and if not creates the new local application User record.
     * 
     * @return App\Model\ORM\User
     */
    protected function saveLocalUser() {
        $user = EntityManager::getRepository(\App\Model\ORM\User::class)->findOneBy(['username' => $this->user->getAttribute('userprincipalname')]);
        if (empty($user)) {
            $user = new \App\Model\ORM\User();
            $user = $user->createLdapUser($this->user);
        }
        // change random password on each login
        $user->setPassword(Hash::make(bin2hex(random_bytes(20))));
        EntityManager::persist($user);
        EntityManager::flush();   

        return $user;
    }

    /**
     * Returns true if the local User record was just created 
     * in the last 30 seconds.
     * 
     * @param DateTime $createdDate
     * @return boolean
     */
    protected function localUserCreated (DateTime $createdDate) {
        $interval = abs(time() - $createdDate->getTimestamp());
        return ($interval < 10);
    }
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants