DirectoryTree / LdapRecord-Laravel

Multi-domain LDAP Authentication & Management for Laravel.
https://ldaprecord.com/docs/laravel/v3
MIT License
496 stars 52 forks source link

[Support] Call to a member function groups() on null #476

Closed MartinTerp closed 1 year ago

MartinTerp commented 1 year ago

Hi

My usecase is that i want to use ldap, if the user is not found in local. At the moment i have solved this by using fallback:

auth.php:

      'users' => [
            'driver' => 'ldap',
            'model' => App\Ldap\User::class,
            'rules' => [],
            'database' => [
                'model' => App\Models\User::class,
                'sync_passwords' => false,
            ],
        ],

App\Models\User.php:

...
use LdapRecord\Laravel\Auth\LdapAuthenticatable;
use LdapRecord\Laravel\Auth\AuthenticatesWithLdap;

class User extends Authenticatable implements LdapAuthenticatable
{
    use HasApiTokens, HasFactory, Notifiable, HasRoles, Notifiable, AuthenticatesWithLdap;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

}

App\Ldap\User.php:

namespace App\Ldap;

use LdapRecord\Models\Model;
use LdapRecord\Models\Concerns\CanAuthenticate;
use Illuminate\Contracts\Auth\Authenticatable;

use LdapRecord\Models\OpenLDAP\Group;
use LdapRecord\Models\OpenLDAP\User as BaseModel;

class User extends Model implements Authenticatable
{
    use CanAuthenticate;

    public static $objectClasses = [];
    protected $guidKey = "uid";

}

I then want to get the groups on the authenticated used, in a listener:

namespace App\Listeners;

use Illuminate\Auth\Events\Authenticated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

use Auth;

class SyncLdapUserRoles
{

    public function __construct()
    {
        //
    }

    public function handle(Authenticated $event)
    {

        $user = Auth::user();
        $user->ldap->groups();

    }
}

But im getting : Error: Call to a member function groups() on null in file. So somewhere the ldap object is not getting attached.

If i remove my listner, i can login just fine so i guess the "ldap" part works.

But im not sure what im missing?

Bonus question: Is it possible to make so "ldaprecord" dosnt create any user in the mysql database?

yusufabdulwahid commented 1 year ago

to access the groups() function, try add HasLdapUser trait in your App\Models\User.php

use LdapRecord\Laravel\Auth\LdapAuthenticatable;
use LdapRecord\Laravel\Auth\AuthenticatesWithLdap;
use LdapRecord\Laravel\Auth\HasLdapUser;

class User extends Authenticatable implements LdapAuthenticatable
{
    use HasApiTokens, HasFactory, Notifiable, HasRoles, Notifiable, AuthenticatesWithLdap;
    use HasLdapUser;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

}

if you just want login using ldap without save any user to mysql database, just remove the 'database' from the users provider in your config\auth.php file.

from this

      'users' => [
            'driver' => 'ldap',
            'model' => App\Ldap\User::class,
            'rules' => [],
            'database' => [
                'model' => App\Models\User::class,
                'sync_passwords' => false,
            ],
        ],

to this

     'users' => [
            'driver' => 'ldap',
            'model' => App\Ldap\User::class,
            'rules' => [],
        ],

be carefull when accessing the Auth::user(); try ddd(Auth;;user()); to see what you get after login with ldap

when you want to access groups() after login if saving users to mysql database try this Auth::user()->ldap->groups();

if not saving users to mysql database try this Auth::user()->groups();

and honesly you don't need to create model 'App\Ldap\User' just use the model that provided by ldap 'LdapRecord\Models\ActiveDirectory\User'

MartinTerp commented 1 year ago

Hi @yusufabdulwahid

Thanks for your in-depth answer, much appreciated.

If i remove the database part, i wont be able to use the local users in laravel db? If the ldap connection is down, i still want to use the "normal" users in laravel.

I have now added the stuff that you mentioned:

use LdapRecord\Laravel\Auth\LdapAuthenticatable;
use LdapRecord\Laravel\Auth\AuthenticatesWithLdap;
use LdapRecord\Laravel\Auth\HasLdapUser;

class User extends Authenticatable implements LdapAuthenticatable
{
    use HasApiTokens, HasFactory, Notifiable, HasRoles, Notifiable, AuthenticatesWithLdap, HasLdapUser;

But when i try and access Auth::user()->groups():

$user = Auth::user()->groups();
Call to undefined method LdapRecord\Query\Model\Builder::groups()
//and $user = Auth::user()->ldap->groups(); gives:
Call to a member function groups() on null

And if i keep the database part:

$user = Auth::user()->ldap->groups();
Call to a member function groups() on null
//and  $user = Auth::user()->groups() gives:
Call to undefined method App\Models\User::groups()
yusufabdulwahid commented 1 year ago

try change the 'model' class in config\auth.php from

     'users' => [
            'driver' => 'ldap',
            'model' => App\Ldap\User::class,
            'rules' => [],
        ],

to

     'users' => [
            'driver' => 'ldap',
            'model' => LdapRecord\Models\ActiveDirectory\User::class,
            'rules' => [],
        ],
MartinTerp commented 1 year ago

That works, but i need to change objectClasses and globalscope, so i need my own model(?)

yusufabdulwahid commented 1 year ago

then try do like this to your own model App\Ldap\User.php

<?php

namespace App\Ldap;

use LdapRecord\Models\Model;
use LdapRecord\Models\Concerns\CanAuthenticate;
use Illuminate\Contracts\Auth\Authenticatable;
use LdapRecord\Models\ActiveDirectory\Concerns\HasPrimaryGroup;
use LdapRecord\Models\ActiveDirectory\Group;

class User extends Model implements Authenticatable
{
    use CanAuthenticate;
    use HasPrimaryGroup;

    public static $objectClasses = [
        'top',
        'person',
        'organizationalperson',
        'user',
    ];

    public function groups()
    {
        return $this->hasMany(Group::class, 'member')->with($this->primaryGroup());
    }
    public function primaryGroup()
    {
        return $this->hasOnePrimaryGroup(Group::class, 'primarygroupid');
    }
}
yusufabdulwahid commented 1 year ago

then try do like this to your own model App\Ldap\User.php

<?php

namespace App\Ldap;

use LdapRecord\Models\Model;
use LdapRecord\Models\Concerns\CanAuthenticate;
use Illuminate\Contracts\Auth\Authenticatable;
use LdapRecord\Models\ActiveDirectory\Concerns\HasPrimaryGroup;
use LdapRecord\Models\ActiveDirectory\Group;

class User extends Model implements Authenticatable
{
    use CanAuthenticate;
    use HasPrimaryGroup;

    public static $objectClasses = [
        'top',
        'person',
        'organizationalperson',
        'user',
    ];

    public function groups()
    {
        return $this->hasMany(Group::class, 'member')->with($this->primaryGroup());
    }
    public function primaryGroup()
    {
        return $this->hasOnePrimaryGroup(Group::class, 'primarygroupid');
    }
}

add two functions and one traits in model App\Ldap\User.php, then turn back your model in config\auth.php to App\Ldap\User::class

stevebauman commented 1 year ago

Hi @MartinTerp,

If the ldap connection is down, i still want to use the "normal" users in laravel.

You cannot configure plain LDAP authentication and fall back to eloquent authentication. You must use LdapRecord database authentication to be able to fallback to eloquent authentication.

Is it possible to make so "ldaprecord" dosnt create any user in the mysql database?

You must configure an authentication rule that prevents LDAP users from logging in whom do not current exist inside of your application's database. Here is the documentation link for instruction:

https://ldaprecord.com/docs/laravel/v2/auth/restricting-login/#using-only-manually-imported-users

You must also configure the sync_existing for LDAP users to be able to login as existing database users. Here is the documentation for that:

https://ldaprecord.com/docs/laravel/v2/auth/database/configuration/#sync-existing-records

If you follow the database authentication guide from start to finish, it will take you through step by step:

https://ldaprecord.com/docs/laravel/v2/auth/database

To figure out what type of authentication will work best for your application, I would encourage you to read the differences between the two options (plain vs database) here:

https://ldaprecord.com/docs/laravel/v2/auth

Hope this helps!