DirectoryTree / LdapRecord-Laravel

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

Call to undefined method App\Model\Data\Models\User::getGuidKey() [Bug] #199

Closed sammyaxe closed 4 years ago

sammyaxe commented 4 years ago

I have installed LdapRecord and laravel-ui auth scaffolding, everything works fine except password reset feature. I'm not sure if this is actual bug or config issues but I get Call to undefined method App\Model\Data\Models\User::getGuidKey() When I submit email in password reset function.

Any clues on how to solve this would be very helpful.

stevebauman commented 4 years ago

Hi @digiiitalmb,

Iโ€™ll definitely help you get up and running.

It sounds like you didnโ€™t apply the required trait and interface to your Eloquent User model. This is explained in the documentation:

https://ldaprecord.com/docs/laravel/auth/quickstart/#database-user-model-setup

However, before you do this, make sure you publish the migration to add the required database columns to your users table.

Let me know if you encounter any issues after performing the above steps ๐Ÿ‘!

sammyaxe commented 4 years ago

Thanks for such a quick reply!

I do have that set up, but I think I'm running into different issue though...

If I do this I see record populating the User table with guid and password columns which is fine, but for some odd reason then I'm no longer able to auth on laravel and get redirected back.

This is config that does save user record into the User table, but does redirect me back to login page without doing anything else.

        'users' => [
            'driver' => 'eloquent',
            'model' => App\Model\Data\Models\User::class,
        ],
        'ldap' => [
            'driver' => 'ldap',
            'model' => LdapRecord\Models\ActiveDirectory\User::class,
            'rules' => [],
            'database' => [
                'model' => App\Model\Data\Models\User::class,
                'sync_passwords' => false,
                'sync_attributes' => [
                    'Email' => 'mail',
                ],
            ],
        ],
    ], 

If I take out database bit, then I'm able to login just fine.

'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Model\Data\Models\User::class,
        ],
        'ldap' => [
            'driver' => 'ldap',
            'model' => LdapRecord\Models\ActiveDirectory\User::class,
            'rules' => [],
        ],
    ], 

My set up however is a bit not standard and what would normally be called users table is called 'User', not sure if that may have something todo with it.

As to password reset it's a bit unclear what should be done here, should I change the provider to be ldap instead of users?

        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

Regards to the required trait...

This is how my User model looks like.


namespace App\Model\Data\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

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

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

    protected $table = 'dbo.User';
    public $timestamps = false;

    public $incrementing = false;
    protected $keyType = 'string';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'FirstName', 'LastName', 'Email', 'LoginEnabled', 'password', 'guid', 'domain','remember_token',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'LoginEnabled' => 'boolean',
    ]; 

    public function company()
    {
        return $this->hasOne('App\Model\Data\Models\Company', 'Id', 'CompanyId'); 
    } 

}

Also not sure if this separate problem, but I don't seem to be able to log in with other user than the admin user that is configured in .ENV even using plain Auth.

Sorry for dropping so much on you, but getting a bit desperate here.

stevebauman commented 4 years ago

No worries @digiiitalmb!

Can you post your LoginController code, as well as what your default guard is set to in your config/auth.php file?

sammyaxe commented 4 years ago

Here is LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; 

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    protected function credentials(Request $request)
    {
        return [
            'mail' => $request->get('email'),
            'password' => $request->get('password'),
        ];
    }

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
}

Here are relevant bits from config/auth.php

'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'ldap',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ], 
'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],
sammyaxe commented 4 years ago

Also I'm able to access any user on ActiveDirectory directly, so I'm pretty sure the connection with Azure Active Directory itself is correct

use LdapRecord\Models\ActiveDirectory\User as LUser;

class PagesController extends BaseFrontSubsystemController
{

    public function __construct()
    {

        parent::__construct();

    }

    public function homepage()
    {

        $user = LUser::find('CN=XXX XXXXX,OU=AADDC Users,DC=xxxxxxxxx,DC=com');
        dd($user);
stevebauman commented 4 years ago

Wow thanks for the sponsorship @digiiitalmb!! :heart: ๐ŸŽ‰ Apologies for the late reply -- I just got back from vacations so I was resting.

I see you have extra user columns for your dbo.Users database table (I'm assuming you're using SQL Server?) such as LoginEnabled. Do you have any custom additional middleware that could be conflicting with the authentication after a successful login with a synchronized database user?

Also not sure if this separate problem, but I don't seem to be able to log in with other user than the admin user that is configured in .ENV even using plain Auth.

Could you post your LDAP configuration with sensitive details omitted? I'm wondering if your base_dn could also be incorrect.

Another thing I would try is enabling LDAP_LOGGING=true in your .env file an then attempt logging in again. Once you do attempt again, take a look in your applications logs (storage/logs directory) and post the results here ๐Ÿ‘

sammyaxe commented 4 years ago

I have removed all additional middlewares that could potentially cause issue here.

Also not sure if this separate problem, but I don't seem to be able to log in with other user than the admin user that is configured in .ENV even using plain Auth.

This issue is now redundant, apparently if I reset users email in Azure AD then it does authenticate LDAP server just fine, I just assumed I can use my regular password that I had set up with my from before.

My issue right now is the redirect back to login page after successful authentication followed with immediate log out. Login works fine if I use plain login without DB sync.

From the log file

User [xxx] has been successfully located for authentication.
Object with name [xxx] is being synchronized.
Object with name [xxx] has been successfully synchronized.
User [xxx] is authenticating.
User [xxx] has successfully passed LDAP authentication.

If I take out this bit:

'database' => [
                'model' => App\Model\Data\Models\User::class,
                'sync_passwords' => false,
                'sync_attributes' => [
                    'FirstName' => 'cn',
                    'LastName' => 'cn',
                    'Email' => 'mail',
                ],
            ],

With password reset now works but only seem to be updated at Laravel and not at LDAP, is this what sync_passwords true would do?

If possible I would love to hop on hangouts or zoom with you tomorrow if you have time. I have emailed you.

stevebauman commented 4 years ago

With password reset now works but only seem to be updated at Laravel and not at LDAP, is this what sync_passwords true would do?

The sync passwords option stores the users LDAP password inside of your local applications database upon login (hashed, of course).

It does not synchronize the users local database password with their LDAP account password, but I can assist you with the implementation.

If possible I would love to hop on hangouts or zoom with you tomorrow if you have time. I have emailed you.

Sounds great! Iโ€™d love to. Itโ€™d definitely be much easier. Iโ€™m in EST time zone โ€” would 12 PM lunch time work for you?

sammyaxe commented 4 years ago

That is great, I'm EST as well, a bit earlier in the morning would be better, but if you can't do earlier 12 PM will do just fine.

Meanwhile any clues what could cause the redirect only when database options are enabled? Can you confirm you got my email? Could have gone into junk

stevebauman commented 4 years ago

Meanwhile any clues what could cause the redirect only when database options are enabled?

Hang on โ€” I think I may know the issue. This is likely caused by the string key type on your database model and the session driver you have configured.

Can you change either the key type on your User database model, or change the session driver you have configured to file (Iโ€™m assuming it may be set to database)?

Once changed, try logging in again and let me know the results!

sammyaxe commented 4 years ago

Sure, let me try that, I have tried redis for session driver, but now I have it set up to DB

sammyaxe commented 4 years ago

I'm not able to change user key type to Int as we have to use GUIDs for user IDs. I have changed session driver to file, cleared config cache, but I don't think it made any difference.

stevebauman commented 4 years ago

Hmm, are you caching your configuration files by chance? I believe Iโ€™ve encountered this before. Try running php artisan config:clear and then logging in again.

My issue was with the session ID payload being too large to store in the database id column. It caused an exception that gets caught and then a redirect back to the login page occurs.

This could be different of course, but want to make sure that isnโ€™t the cause.

sammyaxe commented 4 years ago

payload column is text field so should fit the session just fine, also sessions do work if configure it as plain login without database.

I did run php artisan config:clear still no luck

stevebauman commented 4 years ago

payload column is text field so should fit the session just fine

What about the ID column?

Also, when you attempt to login using either the database or file drivers with the LdapRecord database options configured, do any files / DB rows get created?

sammyaxe commented 4 years ago

Yes user records get created in the DB if they do not exist, that part works fine.

In terms of ID column for session table atm it's varchar up to 255 chars, and the records inserted in ID column by laravel are only 40 chars, but I think we can rule out this one as now I run file driver for sessions and it's still the same.

As soon as I comment out, I can login without issue and the session persists.

'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Model\Data\Models\User::class,
        ],
        'ldap' => [
            'driver' => 'ldap',
            'model' => LdapRecord\Models\ActiveDirectory\User::class,
            'rules' => [],
            /*'database' => [
                'model' => App\Model\Data\Models\User::class,
                'sync_passwords' => true,
                'sync_attributes' => [
                    'FirstName' => 'cn',
                    'LastName' => 'cn',
                    'Email' => 'mail',
                ],
            ],*/
        ], 
    ], 
stevebauman commented 4 years ago

In terms of ID column for session table atm it's varchar up to 255 chars, and the records inserted in ID column by laravel are only 40 chars, but I think we can rule out this one as now I run file driver for sessions and it's still the same.

For sure โ€” but when using either of those session drivers, do the actual session files or database rows in the sessions table get created?

sammyaxe commented 4 years ago

I actually just solved the main issue! It was todo with our users table Id being capital rather than lowercase. Changed Id to id and it works.

Good clue was to look into session record during plain login which actually populated user_id during plain login, but not during the attempt itself.

I still need your help regards password reset and couple other general questions.

stevebauman commented 4 years ago

Excellent @digiiitalmb! Great to hear ๐ŸŽ‰ . I'll see you in 5 minutes ๐Ÿ‘

sammyaxe commented 4 years ago

I'm in the meeting, let me know if invite works

stevebauman commented 4 years ago
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Str;
use LdapRecord\LdapRecordException;
use LdapRecord\Models\ActiveDirectory\User;

class ResetPasswordController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Password Reset Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling password reset requests
    | and uses a simple trait to include this behavior. You're free to
    | explore this trait and override any methods you wish to tweak.
    |
    */

    use ResetsPasswords;

    /**
     * Where to redirect users after resetting their password.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    /**
     * Reset the given user's password.
     *
     * @param  \Illuminate\Contracts\Auth\CanResetPassword  $user
     * @param  string  $password
     * @return void
     */
    protected function resetPassword($user, $password)
    {
        $ldapUser = User::on($user->domain)->findByGuid($user->guid);

        $ldapUser->password = $password;

        try {
            $ldapUser->save();
        } catch (LdapRecordException $ex) {
            dd($ex);
        }

        $user->setRememberToken(Str::random(60));

        $user->save();

        event(new PasswordReset($user));

        $this->guard()->login($user);
    }
}
sammyaxe commented 4 years ago

Thanks for your time today, the best support I have ever received on Github open source projecect!

For everyone else reading this, Steve is a great guy, don't hesitate sponsoring.

stevebauman commented 4 years ago

Thanks so much for your kind words @digiiitalmb! That really means a lot to me! ๐Ÿ‘ โค๏ธ

Email me any time you need assistance or have any questions :smile: