Adldap2 / Adldap2-Laravel

LDAP Authentication & Management for Laravel
MIT License
911 stars 184 forks source link

When re-login will insert database again #459

Closed shaneho closed 6 years ago

shaneho commented 6 years ago

Description:

The first login is successful, but after I logout and re-login, the error message will show as below:

Illuminate\Database\QueryException with message 'SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "users_username_unique" DETAIL: Key (username)=(xxxxx) already exists.

its seem to re-insert data again.

Use php artisan tinker var_dump(Auth::attempt(['username' => 'xxxxx','password' => 'xxxxxx'])); Will show up the same error message.

Steps To Reproduce:

  1. Run the command: composer require adldap2/adldap2-laravel
  2. Run the command: composer update
  3. Insert the Facades : 'Adldap' => Adldap\Laravel\Facades\Adldap::class
  4. Publish the configuration file: php artisan vendor:publish --tag="adldap"
  5. Modified database/migrations/2014_10_12_000000_create_users_table.php // From: $table->string('email')->unique();

    // To: $table->string('username')->unique();

  6. Run the command: php artisan migrate
  7. Run the command: php artisan vendor:publish
  8. Modify the config/adldap.php and config/adldap_auth.php files

    **config/adldap.php**
    'connections' => [
    'default' => [
        'auto_connect' => env('ADLDAP_AUTO_CONNECT', true),
        'connection' => Adldap\Connections\Ldap::class,
        'schema' => Adldap\Schemas\ActiveDirectory::class,
        'connection_settings' => [
            'account_prefix' => env('ADLDAP_ACCOUNT_PREFIX', ''),
            'account_suffix' => env('ADLDAP_ACCOUNT_SUFFIX', ''),
            'domain_controllers' => explode(' ', env('ADLDAP_CONTROLLERS', 'xxxxxx')),
            'port' => env('ADLDAP_PORT', 389),
            'timeout' => env('ADLDAP_TIMEOUT', 60),
            'base_dn' => env('ADLDAP_BASEDN', 'xxxxxx'),
            'admin_account_prefix' => env('ADLDAP_ADMIN_ACCOUNT_PREFIX', ''),
            'admin_account_suffix' => env('ADLDAP_ADMIN_ACCOUNT_SUFFIX', ''),
            'admin_username' => env('ADLDAP_ADMIN_USERNAME', 'xxxxxxx'),
            'admin_password' => env('ADLDAP_ADMIN_PASSWORD', 'xxxxxxx'),
            'follow_referrals' => false,
            'use_ssl' => env('ADLDAP_USE_SSL', false),
            'use_tls' => env('ADLDAP_USE_TLS', false),
        ],
    ],
    ],
    **config/adldap_auth.php**
    
    'connection' => env('ADLDAP_CONNECTION', 'default'),
    'provider' => Adldap\Laravel\Auth\DatabaseUserProvider::class,
    'rules' => [
        // Denys deleted users from authenticating.
        Adldap\Laravel\Validation\Rules\DenyTrashed::class,
        // Allows only manually imported users to authenticate.
        // Adldap\Laravel\Validation\Rules\OnlyImported::class,
    ],
    'scopes' => [
        // Only allows users with a user principal name to authenticate.
        // Remove this if you're using OpenLDAP.
        Adldap\Laravel\Scopes\UpnScope::class,
    
        // Only allows users with a uid to authenticate.
        // Uncomment if you're using OpenLDAP.
        // Adldap\Laravel\Scopes\UidScope::class,
    ],
    'usernames' => [
        'ldap' => [
           **'discover' => 'samaccountname', // was userprincipalname**
            'authenticate' => 'distinguishedname',
        ],
        **'eloquent' => 'username', // was email**
        'windows' => [
            'discover' => 'samaccountname',
            'key' => 'AUTH_USER',
        ],
    ],
    'passwords' => [
        'sync' => env('ADLDAP_PASSWORD_SYNC', false),
        'column' => 'password',
    ],
    'login_fallback' => env('ADLDAP_LOGIN_FALLBACK', false),
    'sync_attributes' => [
        **'username' => 'samaccountname', // was 'email' => 'userprincipalname'**
        'name' => 'cn',
    ],
    'logging' => [
        'enabled' => true,
        'events' => [
            \Adldap\Laravel\Events\Importing::class => \Adldap\Laravel\Listeners\LogImport::class,
            \Adldap\Laravel\Events\Synchronized::class => \Adldap\Laravel\Listeners\LogSynchronized::class,
            \Adldap\Laravel\Events\Synchronizing::class => \Adldap\Laravel\Listeners\LogSynchronizing::class,
            \Adldap\Laravel\Events\Authenticated::class => \Adldap\Laravel\Listeners\LogAuthenticated::class,
            \Adldap\Laravel\Events\Authenticating::class => \Adldap\Laravel\Listeners\LogAuthentication::class,
            \Adldap\Laravel\Events\AuthenticationFailed::class => \Adldap\Laravel\Listeners\LogAuthenticationFailure::class,
            \Adldap\Laravel\Events\AuthenticationRejected::class => \Adldap\Laravel\Listeners\LogAuthenticationRejection::class,
            \Adldap\Laravel\Events\AuthenticationSuccessful::class => \Adldap\Laravel\Listeners\LogAuthenticationSuccess::class,
            \Adldap\Laravel\Events\DiscoveredWithCredentials::class => \Adldap\Laravel\Listeners\LogDiscovery::class,
            \Adldap\Laravel\Events\AuthenticatedWithWindows::class => \Adldap\Laravel\Listeners\LogWindowsAuth::class,
            \Adldap\Laravel\Events\AuthenticatedModelTrashed::class => \Adldap\Laravel\Listeners\LogTrashedModel::class,
        ],
    ],
  9. Run the command: php artisan make:auth
  10. Add the public method username() into app/Http/Controllers/Auth/LoginController.php
    public function username() {
        return 'username';
    }
  11. Modify the HTML input to username instead of email in resources/views/auth/login.blade.php
  12. The LoginController file:
    
    **app/Http/Controllers/Auth/LoginController.php**
    <?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller; use Illuminate\Foundation\Auth\AuthenticatesUsers;

use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Adldap\Laravel\Facades\Adldap;

use App\User;

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 = '/home';

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

public function username(){
    return 'username';
}

public function showLoginForm(){
    return view('auth.login');
}

public function login(Request $request){
    if(Auth::attempt($request->only(['username', 'password']))) {
        $user = Auth::user();
        return 'login success';
    }
    return 'log fail';
}

}


13. Try login. Will successful.
14. logout
15. Then login again, will show up error message

Could you figure me out what the step I was wrong?
Thank you very much!
stevebauman commented 6 years ago

Hi @shaneho,

This is actually due to using the Postgre database - as its queries are case sensitive rather than MySQL where they are case insensitive.

You will have to normalize your account names before authenticating them with a strtolower() before passing them into the Auth::attempt() command.

You will also likely need to extend the built in adldap:import command with your own to be able to normalize usernames prior to import so they sync properly and override the getUserCredentials method:

namespace App\Console\Commands;

use Adldap\Laravel\Commands\Console\Import;

class LdapImport extends Import
{
    protected function getUserCredentials()
    {
        $resolver = Resolver::getFacadeRoot();

        $username = $user->getFirstAttribute($resolver->getLdapDiscoveryAttribute());

        return [
            $resolver->getEloquentUsernameAttribute() => strtolower($username),
        ];
    }
}
shaneho commented 6 years ago

Hi @stevebauman,

Thanks for the reply and help! This is works now! :)

And I have another question of Single Sign On (SSO) Middleware, Assume that I have two Laravel projects A and B, and I want to use project A as SSO server, If the users want to access project B, they must to login from project A first, otherwise, will redirect to project A login view.

My question is : is it possible to post something from project A to project B, to tell project B the user is login or not?

I'm already add WindowsAuthenticate in app/Http/Kernel.php

protected $middlewareGroups = [
    'web' => [
        Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        Middleware\VerifyCsrfToken::class,
        \Adldap\Laravel\Middleware\WindowsAuthenticate::class, // Inserted here.
    ],
];

Thank you very much!