setup.md 19.8 KB
Newer Older
jiangbowen's avatar
jiangbowen committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
# Setup

- [Connection](#connection)
- [Provider](#provider)
- [Rules](#rules)
- [Scopes](#scopes)
- [Identifiers](#identifiers)
- [Passwords](#passwords)
- [Login Fallback](#login-fallback)
- [Synchronizing](#synchronizing)

## Connection

The `connection` option is the name of the LDAP connection to use for authentication that you've configured in your `ldap.php` file.

## Provider

Authentication providers allow you to choose how LDAP users are authenticated into your application.

There are two built in providers. Please view their documentation to see which one is right for you.

* [DatabaseUserProvider](#databaseuserprovider)
* [NoDatabaseUserProvider](#nodatabaseuserprovider)

### DatabaseUserProvider

The `DatabaseUserProvider` allows you to synchronize LDAP users to your applications database.

> **Note**: This provider requires that you add an `objectguid` database column to your `users` database table. [Read more here](#guid-column).

To use it, insert it in your `config/ldap_auth.php` in the `provider` option:

```php
'provider' => Adldap\Laravel\Auth\DatabaseUserProvider::class
```

Using this provider utilizes your configured Eloquent model in `config/auth.php`:

```php
'providers' => [
    'users' => [
        'driver' => 'ldap',
        'model' => App\User::class,
    ],
],
```

When you've authenticated successfully, use the method `Auth::user()` as you would normally to retrieve the currently authenticated user:

```php
// Instance of \App\User.
$user = Auth::user();

echo $user->email;
```

### NoDatabaseUserProvider

The `NoDatabaseUserProvider` allows you to authenticate LDAP users without synchronizing them.

This provider is great for smaller applications that don't require you to attach data to authenticating users and is great for simply authorizing access.

#### Important Note About Session Drivers

When using the `database` session driver with the `NoDatabaseUserProvider`, you **must**
change the `user_id` data type in the generated Laravel sessions migration (`database/migrations/2018_05_03_182019_create_sessions_table.php`)
to `varchar`. This is because the identifier for LDAP records is
a GUID - which contains letters and dashes (incompatible with
the `integer` type of databases).

#### Important Note About Default Views

Due to Laravel's generated blade views with the `auth:make` command, any
views that utilize Eloquent User model attributes will need to be
re-written for compatibility with this provider.

For example, in the generated `resources/views/layouts/app.blade.php`, you will
need to rewrite `Auth::user()->name` to `Auth::user()->getCommonName();`

This is because the authenticated user will not be a standard Eloquent
model, it will be a `Adldap\Models\User` instance.

You will receive exceptions otherwise.

---

To use it, insert it in your `config/ldap_auth.php` in the `provider` option:

```php
'provider' => Adldap\Laravel\Auth\NoDatabaseUserProvider::class
```

Inside your `config/auth.php` file, you can remove the `model` key in your provider array since it won't be used:

```php
'providers' => [
    'users' => [
        'driver' => 'ldap',
    ],
],
```

When you've authenticated successfully, use the method `Auth::user()` as you would
normally to retrieve the currently authenticated user:

```php
// Instance of \Adldap\Models\User.
$user = Auth::user();

echo $user->getCommonName();

echo $user->getAccountName();
```

## Rules

Authentication rules allow you to restrict which LDAP users are able to authenticate, much like [scopes](#scopes),
but with the ability to perform checks on the specific user authenticating, rather than a global scope.

### Creating a Rule

To create a rule, it must extend the class `Adldap\Laravel\Validation\Rules\Rule`.

Two properties will be available to you inside the rule. A `$user` property that
contains the LDAP user model, as well as their Eloquent `$model`

> **Note**: If you utilize the `NoDatabaseUserProvider` instead of the default
> `DatabaseUserProvider`, then only the `$user` property will be available.

We'll create a folder in our `app` directory containing our rule named `Rules`.

With this example rule, we only want to allow users to login if they are inside specific OU's:

```php
namespace App\Rules;

use Adldap\Laravel\Validation\Rules\Rule;
use Illuminate\Support\Str; // Laravel >= 5.7

class OnlyManagersAndAccounting extends Rule
{
    /**
     * Determines if the user is allowed to authenticate.
     *
     * @return bool
     */   
    public function isValid()
    {
        $ous = [
            'ou=Accounting,dc=acme,dc=org',
            'ou=Managers,dc=acme,dc=org',
        ];
    
        // Laravel <= 5.6
        return str_contains($this->user->getDn(), $ous);

        // Laravel >= 5.7
        return Str::contains($this->user->getDn(), $ous)
    }
}
```

### Implementing the Rule

To implement your new rule, you just need to insert it into your `config/ldap_auth.php` file:

```php
'rules' => [
    
    App\Rules\OnlyManagersAndAccounting::class,

],
```

Now when you try to login, the LDAP user you login with will need to be apart of either the `Accounting` or `Managers` Organizational Unit.

### Example Rules

#### Group Validation

To validate that an authenticating user is apart of one or more LDAP groups, we can perform this with a `Rule`:

```php
namespace App\Rules;

use Adldap\Models\User as LdapUser;
use Adldap\Laravel\Validation\Rules\Rule;

class IsAccountant extends Rule
{
    /**
     * Determines if the user is allowed to authenticate.
     *
     * Only allows users in the `Accounting` group to authenticate.
     *
     * @return bool
     */   
    public function isValid()
    {
        return $this->user->inGroup('Accounting');
    }
}
```

Once you've implemented the above rule, only LDAP users that are apart of the `Accounting` group, will be allowed to authenticate.

## Scopes

Scopes allow you to restrict which LDAP users are allowed to login to your application.

If you're familiar with Laravel's [Query Scopes](https://laravel.com/docs/5.7/eloquent#query-scopes),
then these will feel very similar.

### Creating a Scope

To create a scope, it must implement the interface `Adldap\Laravel\Scopes\ScopeInterface`.

For this example, we'll create a folder inside our `app` directory containing our scope named: `Scopes`.

Of course, you can place these scopes wherever you desire, but in this example, our final scope path will be:

```
../my-application/app/Scopes/AccountingScope.php
```

With this scope, we want to only allow members of an Active Directory group named: `Accounting`:

```php
namespace App\Scopes;

use Adldap\Query\Builder;
use Adldap\Laravel\Scopes\ScopeInterface;

class AccountingScope implements ScopeInterface
{
    /**
     * Apply the scope to a given LDAP query builder.
     *
     * @param Builder $query
     *
     * @return void
     */
    public function apply(Builder $query)
    {
        // The distinguished name of our LDAP group.
        $accounting = 'cn=Accounting,ou=Groups,dc=acme,dc=org';
        
        $query->whereMemberOf($accounting);
    }
}
```

### Implementing a Scope

Now that we've created our scope (`app/Scopes/AccountingScope.php`), we can insert it into our `config/ldap_auth.php` file:

```php
'scopes' => [

    // Only allows users with a user principal name to authenticate.

    Adldap\Laravel\Scopes\UpnScope::class,
    
    // Only allow members of 'Accounting' to login.
    
    App\Scopes\AccountingScope::class,

],
```

Once you've inserted your scope into the configuration file, you will now only be able
to authenticate with users that are a member of the `Accounting` group.

All other users will be denied authentication, even if their credentials are valid.

> **Note**: If you're caching your configuration files, make sure you
> run `php artisan config:clear` to be able to use your new scope.

## Identifiers

Inside your `config/ldap_auth.php` file there is a configuration option named `identifiers`:

```php
'identifiers' => [

    'ldap' => [
        'locate_users_by' => 'userprincipalname',
        'bind_users_by' => 'distinguishedname',
    ],
    
    'database' => [
        'guid_column' => 'objectguid',
        'username_column' => 'email',
    ],
    
    'windows' => [
        'locate_users_by' => 'samaccountname',
        'server_key' => 'AUTH_USER',
    ],

],
```

Let's go through each option with their meaning.

### LDAP

The LDAP array contains two elements each with a key and value.

The `locate_users_by` key contains the LDAP users attribute you would like your authenticating users to be located by.

> **Note**: If you're using the `NoDatabaseUserProvider` it is extremely important to know that this value is used as the key to retrieve the inputted username from the `Auth::attempt()` credentials array.
>
> For example, if you're executing an `Auth::attempt(['username' => 'jdoe..'])` and you have a `locate_users_by` value set to `userprincipalname` then the Adldap2-Laravel auth driver will try to retrieve your users username from the given credentials array with the key `userprincipalname`. This would generate an exception since this key does not exist in the above credentials array.

For example, executing the following:

```php
Auth::attempt(['email' => 'jdoe@corp.com', 'password' => 'password'])
```

Will perform an LDAP search for a user with the `userprincipalname` equal to `jdoe@corp.com`.

If you change `Auth::attempt()` `email` key, you will need to change the `database.username_column` key to match.

The `authenticate` key contains the LDAP users attribute you would like to perform LDAP authentication on.

For example, executing the following:

```php
Auth::attempt(['email' => 'jdoe@corp.com', 'password' => 'password'])
```

Will try to locate a user in your LDAP directory with a `userprincipalname` equal to `jdoe@corp.com`. Then, when an LDAP record of this user is located, their `disintinguishedname` will be retrieved from this record, an be passed into an `Adldap\Auth\Guard::attempt()` (ex `Guard::attempt('cn=John Doe,ou=Users,dc=corp,dc=com', 'password')`).

> **Note**: It's **extremely** important to know that your configured `account_suffix` and `account_prefix` (located in your `config/ldap.php` file) will be appended or pre-pended *onto* this passed in username.

You can ignore the `windows` configuration array, unless you're planning on using the included [middleware](auth/middleware.md) for single sign on authentication.

### Database

### GUID Column

The GUID column is a new configuration option added in v6.0 that allows you to set the
database column that will store users Object GUID (Globally Unique Identifier).

The addition of this database column allows you to make username changes in your
LDAP directory, and have them properly synchronize in your Laravel application.

This is usually the scenario when someone changes their marital status, or changes their name.

If you're upgrading from a previous Adldap2-Laravel version, simply create a new migration and add the `nullable` column to your `users` database table.

Ex. `php artisan make:migration add_objectguid_column`

```php
Schema::table('users', function (Blueprint $table) {
    $table->string('objectguid')->nullable()->after('id');
});
```

Otherwise, if you're starting from scratch, simply add the column to your `create_users_table` migration:

```php
Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('objectguid')->nullable(); // Added here.
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
});
```

If you have user records already inside your database with a `null` `objectguid` value, then
**it will be set automatically** if a user authenticates with the same username that
is contained in your configured in your `username_column` option.

For example, lets say we have a user in our database with the following information:
```
+----+------------+---------------+
| id | objectguid | email         |
+----+------------+---------------+
| 1  | NULL       | jdoe@acme.org |
+----+------------+---------------+
```

When a user successfully authenticates with the username of `jdoe@acme.org`, then his `objectguid` column will automatically be set:
```
+----+--------------------------------------+---------------+
| id | objectguid                           | email         |
+----+--------------------------------------+---------------+
| 1  | cc07cacc-5d9d-fa40-a9fb-3a4d50a172b0 | jdoe@acme.org |
+----+--------------------------------------+---------------+
```

The next time this user authenticates, the `objectguid` will queried for **first**, then `email`.
This is done using a simple `or where` statement, so two queries are not executed for one login.

> **Note**: If the users identifier changes (their email / username) prior to their
> `objectguid` from being synchronized to your local database and they login to 
> your application, a new user record will be created.
>
> This is due to not being able to locate a local user record with the users new username.
>
> It is recommended to keep your application in sync via scheduling the `adldap:import`
> command so that all users have a synchronized `objectguid`.

### Username Column

The `username_column` contains a value that should match the username column you have set up in your `users` database table.

For example, if you're using a `username` field instead of `email` in your application, you will need to change this option to `username`.

> **Note**: If you're using the `DatabaseUserProvider` it is extremely important to know that this value is used as the key to retrieve the inputted username from the `Auth::attempt()` credentials array.
>
> For example, if you're executing an `Auth::attempt(['username' => 'jdoe..'])` and you have an `username_column` value set to `email` then the Adldap2-Laravel auth driver will try to retrieve your users username from the given credentials array with the key `email`. This would generate an exception since this key does not exist in the above credentials array.

> **Note**: Keep in mind you will also need to update your `create_users_table` migration to
> use a username field instead of email, **as well as** your LoginController.

For example, if you'd like to login users by their `samaccountname`:

```php
'usernames' => [

    'ldap' => [
        'locate_users_by' => 'samaccountname', // Changed from `userprincipalname`
        'bind_users_by' => 'distinguishedname',
    ],
    
    'database' => [
        'guid_column' => 'objectguid',
        'username_column' => 'username', // Changed from `email`
    ],

],
```

**Be sure** to update the `sync_attributes` option to synchronize the users `username` as well as this is not done automatically. You will receive a SQL exception otherwise.

```php
'sync_attributes' => [
    'username' => 'samaccountname',
    'name' => 'cn',
],
```

### Logging In

Login a user regularly using `Auth::attempt($credentials);`.

Once a user is authenticated, retrieve them as you would regularly:

> **Note**: The below code is just an example. You should not need to modify
> the `login()` method on the default `LoginController`, unless
> you require unique functionality.

```php
public function login(Request $request)
{
    if (Auth::attempt($request->only(['email', 'password']))) {
        
        // Returns \App\User model configured in `config/auth.php`.
        $user = Auth::user();
        
        return redirect()->to('home')
            ->withMessage('Logged in!');
    }
    
    return redirect()->to('login')
        ->withMessage('Hmm... Your username or password is incorrect');
}
```

## Passwords

### Sync

The password sync option allows you to automatically synchronize users LDAP passwords to your local database. These passwords are hashed natively by Laravel using the `Hash::make()` [method](https://laravel.com/docs/5.7/hashing#basic-usage).

Enabling this option would also allow users to login to their accounts using the password last used when an LDAP connection was present.

If this option is disabled, the local database account is applied a random 16 character hashed password upon first login, and will lose access to this account upon loss of LDAP connectivity.

This option must be true or false and is only applicable
to the `DatabaseUserProvider`.

### Column

The column option allows you to change the database column that contains the users password.

Change this if your database column is different than `password` and you have enabled the above sync option.

## Login Fallback

The login fallback option allows you to login as a local database user using the default Eloquent authentication
driver if LDAP authentication fails. This option is handy in environments where:

- You may have some directory users and other users registering through
  the website itself (user does not exist in your LDAP directory).
- Your LDAP server goes down and may be unavailable

> **Note**: If you would like users to be able to login if your LDAP server is unavailable, you must
> also enable the above [Password Sync](#sync) option. Otherwise, users will fail authentication
> because their password has not been synchronized, and therefore will be incorrect.
>
> Users must have logged in once prior to your LDAP server going down, as their account will not yet exist in the database.

To enable it, simply set the option to true in your `config/ldap_auth.php` configuration file:

```php
'login_fallback' => env('LDAP_LOGIN_FALLBACK', true), // Set to true.
```

## Synchronizing

Inside your `config/ldap_auth.php` file there is a configuration option named `sync_attributes`. This
is an array of attributes where the key is the eloquent `User` model attribute, and the
value is the active directory users attribute:

```php
'sync_attributes' => [
    'email' => 'userprincipalname',
    'name' => 'cn',
],
```

By default, the `User` models `email` and `name` attributes are synchronized to
the LDAP users `userprincipalname` and `cn` attributes.

This means, upon login, the users `email` and `name` attribute on Laravel `User` Model will be set to the
LDAP users `userprincipalname` and common name (`cn`) attribute, **then saved**.

Feel free to add more attributes here, however be sure that your `users` database table contains
the key you've entered, otherwise you will receive a SQL exception upon authentication, due
to the column not existing on your users database table.

### Attribute Handlers

If you're looking to synchronize an attribute from an Adldap2 model that contains an array or an
object, or sync attributes yourself, you can use an attribute handler class
to sync your model attributes manually. For example:

> **Note**: The class must contain a `handle()` method. Otherwise you will receive an exception.

> **Tip**: Attribute handlers are constructed using the `app()` helper. This means you can type-hint any application
> dependencies you may need in the handlers constructor.

```php
'sync_attributes' => [
    
    App\Handlers\LdapAttributeHandler::class,

],
```

The `LdapAttributeHandler`:

```php
namespace App\Handlers;

use App\User as EloquentUser;
use Adldap\Models\User as LdapUser;

class LdapAttributeHandler
{
    /**
     * Synchronizes ldap attributes to the specified model.
     *
     * @param LdapUser     $ldapUser
     * @param EloquentUser $eloquentUser
     *
     * @return void
     */
    public function handle(LdapUser $ldapUser, EloquentUser $eloquentUser)
    {
        $eloquentUser->name = $ldapUser->getCommonName();
    }
}
```

## Logging

The `logging` array contains a list of the events to be logged when certain [events](auth/events.md) occur using Adldap2-Laravel.

Each element in the array consists of the key (the occurring event) and the value (the listener that performs the logging of said event).

You can remove any or all of these if you'd prefer nothing to be logged for the event. No passwords are logged with any of the events.