Importing LDAP Users

Introduction

LdapRecord-Laravel allows you to import users from your LDAP directories into your local database. This is done by executing the php artisan ldap:import command and is only available to LDAP authentication providers you configure with database synchronization.

As it is with signing users into your application, the Eloquent database model you specify in your config/auth.php file is used for the creation and retrieval of users in your database.

Attribute Synchronization

The sync_attributes you define inside of your config/auth.php file for your provider will be used for importing and synchronizing users.

Be sure to look at the documentation to get a further understanding on what is possible with this option.

Syncing Existing Records

The sync_existing array you define inside of your config/auth.php will be used to synchronize existing database records with your LDAP users.

Be sure to look at the documentation to get a further understanding on what is possible with this option.

Password Synchronization

The sync_passwords option you define inside of your config/auth.php file is used when importing and synchronizing users. However, there are some main takeaways you must be aware of:

  • Passwords cannot be retrieved from users who are being imported from your LDAP server.
    This would be a major security risk if this were possible. If a password is already set for the user being imported, it will be left untouched. This is to retain a possible synchronized password that was set upon login.
  • Passwords will always be set to a hashed 16 character string if not already present.
    If the user being imported does not have a password, their password will be set to a hashed 16 character random string using Str::random.
  • Passwords will not be set if you have defined false for password_column.

Running the command

To run the command you must insert the provider name that you have setup for LDAP database synchronization inside of your config/auth.php file. Let's walk through an example.

In our application we have a configured authentication provider named ldap:

'providers' => [
    // ...

    'ldap' => [
        // ...
        'database' => [
            // ...
        ],
    ],
],

We will then insert the providers name into our import command and execute it:

php artisan ldap:import ldap

You will then be asked after a successful search in your directory:

Found 2 user(s).

Would you like to display the user(s) to be imported / synchronized? (yes/no) [no]:
> y

A table will then be shown so you can confirm the import of the located users:

+-------------+-------------------+---------------------+
| Name        | Account Name      | UPN                 |
+-------------+-------------------+---------------------+
| John Doe    | johndoe           | johndoe@local.com   |
| Jane Doe    | janedoe           | janedoe@local.com   |
+-------------+-------------------+---------------------+

Then, you will be asked to import the users shown and the import will begin:

 Would you like these users to be imported / synchronized? (yes/no) [no]:
 > y

  2/2 [============================] 100%

Successfully imported / synchronized 2 user(s).

Scheduling the command

To run the import as a scheduled job, place the following in your app/Console/Kernel.php in the command scheduler:

protected function schedule(Schedule $schedule)
{
    // Import LDAP users hourly.
    $schedule->command('ldap:import ldap', [
        '--no-interaction',
        '--restore',
        '--delete',
        '--filter' => '(objectclass=user)',
    ])->hourly();
}

The above scheduled import command will:

  • Run without interaction and import new users as well as synchronize already imported users
  • Restore user models who have been re-activated in your LDAP directory (if you're using Eloquent Soft Deletes)
  • Soft-Delete user models who have been deactived in your LDAP directory (if you're using Eloquent Soft Deletes)
  • Only import users that have an objectclass equal to user

It's recommended to use model query scopes instead of the --filter option on your configured authentication LdapRecord model so LDAP users signing into your application are applied the same search filter.

Programmatically Executing

You can call the ldap:import command using Laravel's Artisan facade to programmatically execute the import inside of your application wherever you'd like:

Artisan::call('ldap:import', ['provider' => 'ldap', '--no-interaction']);

To use other arguments and options, include them as array values:

Artisan::call('ldap:import', [
    'provider' => 'ldap',
    'user' => 'sbauman',
    '--no-interaction',
    '--restore' => true,
    '--delete' => true,
    '--delete-missing' => true,
    '--filter' => '(cn=John Doe)',
    '--attributes' => 'cn,mail,samaccountname',
]);

Command Arguments

Provider

To execute the import command, you must supply an authentication provider name. This will retrieve the users from your configured LdapRecord model, and import them using your configured Eloquent model.

For example, if you have kept the default users authentication provider name in your config/auth.php file, then you would execute:

php artisan ldap:import users

User

To import or synchronize a single user, insert one of their attributes (such as mail, samaccountname, cn) and LdapRecord will try to locate the user for you using Ambiguous Name Resolution. If your LDAP server does not support ANR, an equivalent query will be created automatically.

This argument is completely optional.

Important: Do not use the --delete-missing option with this argument. Otherwise, other LDAP users that have been imported will be soft-deleted (if configured & enabled on your Eloquent model).

php artisan ldap:import ldap jdoe@email.com

Found user 'John Doe'.

Would you like to display the user(s) to be imported / synchronized? (yes/no) [no]:
> y

Command Options

Filter

The --filter (or -f) option allows you to apply a raw filter to further narrow down the users who are imported:

Important: If your filter contains commas, or other types of "escape" level LDAP search filter characters, you must escape the value with a backslash (\) before passing it into the search string. More on this below.

php artisan ldap:import ldap --filter "(cn=John Doe)"

Escaping

In some cases, you may need to pass commas or other escape level characters into the search filter.

To do so, add a backslash (\) before the character to escape it properly:

php artisan ldap:import ldap --filter "(cn=Doe\, John)"

If this is not done, you will receive a Bad search filter exception during import.

Attributes

The --attributes (or -a) option allows you to specify the attributes that should be returned from your LDAP server.

This option is great for reducing memory usage for large imports, since all attributes will be returned from your LDAP server otherwise.

Important: To use this option, you must comma separate each attribute in the command and include the attributes you have configured in your authentication provider.

php artisan ldap:import ldap --attributes "cn,mail,sn,givenname,samaccountname"

Delete

This option is only available on Active Directory models.

The --delete (or -d) option allows you to soft-delete deactivated LDAP users. No users will be deleted if your User Eloquent model does not have soft-deletes enabled.

php artisan ldap:import ldap --delete

Delete Missing

This option is available for all LDAP directories.

The --delete-missing option allows you to soft-delete all LDAP users that were missing from the import. This is useful when a user has been deleted in your LDAP server, and therefore should be soft-deleted inside of your application, since they will not be returned in search results.

This option has been designed to have the utmost safety of user data in mind. Here are some paramount things to understand with this option:

No users will be deleted if soft-deletes are not enabled on your User eloquent model.

Deletion will not occur. You must setup Soft Deletes on your User eloquent model.

If no users have been successfully imported, no users will be soft-deleted.

If an executed import does not successfully import any users, no users will be soft-deleted.

Only users that belong to the domain you are importing will be soft-deleted.

This means, all other users will be left untouched, such as local database users that were not imported from an LDAP server, as well as users that were imported from another domain.

Soft-deleted users are reported in the log.

When users are soft-deleted, a log entry will be created for each one:

User with [id = 2] has been soft-deleted due to being missing from LDAP import.
User with [id = 5] has been soft-deleted due to being missing from LDAP import.

The DeletedMissing Event

A DeletedMissing event is fired in the event of any users being soft-deleted.

You may listen for this event and access the IDs of the deleted users, as well as the Eloquent model that was used to perform the deletion, and the LdapRecord model that was used to perform the import.

Here is an example listener that accesses this event and its properties:

// app/Listeners/UsersDeletedFromImport.php

namespace App\Listeners;

use LdapRecord\Laravel\Events\DeletedMissing;

class UsersDeletedFromImport
{
    public function handle(DeletedMissing $event)
    {
        // \Illuminate\Support\Collection
        $event->ids;

        // \LdapRecord\Models\ActiveDirectory\User
        $event->ldap;

        // \App\User
        $event->eloquent;
    }
}

Restore

This option is only available on Active Directory models.

The --restore (or -r) option allows you to restore soft-deleted re-activated LDAP users.

php artisan ldap:import ldap --restore

Typically, the --restore and --delete options would be used together to allow full synchronization of user disablements and restoration.

No Logging

The --no-log option allows you to disable logging during the command.

php artisan ldap:import ldap --no-log

By default this is enabled, regardless if logging is disabled in your config/ldap.php file.

No Interaction

To run the import command via a schedule, use the --no-interaction flag:

php artisan ldap:import ldap --no-interaction

Users will be imported automatically with no prompts.

You can also call the command from the Laravel Scheduler, or other commands:

// Importing one user
$schedule->command('ldap:import ldap sbauman', ['--no-interaction'])
            ->everyMinute();

// Importing all users
$schedule->command('ldap:import ldap', ['--no-interaction'])
            ->everyMinute();

// Importing users with a filter
$dn = 'CN=Accounting,OU=SecurityGroups,DC=local,DC=com';

$filter = sprintf('(memberof:1.2.840.113556.1.4.1941:=%s)', $dn);

$schedule->command('ldap:import ldap', ['--no-interaction', '--filter' => $filter])
    ->everyMinute();

Additional Tips

  • Users who already exist inside your database will be updated with your configured providers sync_attributes.
  • Users will never be force deleted from the import command. You will need to delete users manually through your Eloquent model
  • If you have a password mutator (setter) on your User Eloquent model, it will not override it. This allows you to hash the random 16 character passwords in your own way.
  • Imported (new) users will be reported in your log files:
[2020-01-29 14:51:51] local.INFO: Imported user johndoe
  • Users that fail to be imported are also reported in your log files, alongside the message of the exception that caused the failure:
[2020-01-29 14:51:51] local.ERROR: Unable to import user janedoe. SQLSTATE[23000]: Integrity constraint violation: 1048
← Previous Topic

Multi-Domain - Laravel UI

Next Topic →

Restricting Sign In