DirectoryTree / LdapRecord-Laravel

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

globalScope or import filter [Support] #166

Closed gregupton closed 4 years ago

gregupton commented 4 years ago

Hello Steve,

How would one go about creating a globalScope or filter to only import users from a specific AD group?

stevebauman commented 4 years ago

Hi @gregupton! Thanks so much for the sponsorship! ❀️

I'll absolutely walk you through the process.


First, create your own User LDAP model that extends the built-in LdapRecord Active Directory user.

You can do this by running the below command:

php artisan make:ldap-model User

Then, update the generated model (inside the app\Ldap\User.php file) and make it extend the LdapRecord Active Directory User model:

namespace App\Ldap;

use LdapRecord\Models\ActiveDirectory\User as BaseModel;

class User extends BaseModel
{
    //
}

Now we will create the scope that will be applied to the model.

Run the below command to create it:

php artisan make:ldap-scope ImportFilter

Then, in the generated scope (app/Ldap/Scopes/ImportFilter.php), this is where you will limit the users who can authenticate and be imported into your application.

Here is an example scope:

// ...
use LdapRecord\Models\ActiveDirectory\Group;

class ImportFilter implements Scope
{
    public function apply(Builder $query, Model $model)
    {
        $group = Group::findByAnrOrFail('My group name');

        $query->whereMemberOf($group);
    }
}

Note: I use the Group::findByAnrOrFail() method to generate an exception if the group cannot be found in the directory. This guarantees that the scope is applied and group membership is properly queried for.

Then, inside of your created app/Ldap/User.php model, override the static boot() method, and add the global scope to the model:

namespace App\Ldap;

use App\Ldap\Scopes\ImportFilter;
use LdapRecord\Models\ActiveDirectory\User as BaseModel;

class User extends BaseModel
{
    protected static function boot()
    {
        parent::boot();

        static:addGlobalScope(new ImportFilter);
    }
}

After that is done, update your authentication provider inside your config/auth.php file with your new LDAP model:

// ...

// config/auth.php

'providers' => [
    // ...

    'ldap' => [
        // ...
        'model' => \App\Ldap\User::class,
    ],
],

Now when you run ldap:import command, only members of the AD group you have specified will be imported. In addition, users attempting to sign into your application that have not yet been imported will have the same scope applied, guaranteeing that your application only contains members of the group you have specified.

gregupton commented 4 years ago

Thanks for the fast reply! I must be missing something... when I run the CLI I get "Found [] User(s)."

namespace App\Ldap\Scopes;

use LdapRecord\Models\Model;
use LdapRecord\Models\Scope;
use LdapRecord\Query\Model\Builder;
use LdapRecord\Models\ActiveDirectory\Group;

class ImportFilter implements Scope
{
    /**
     * Apply the scope to the given query.
     *
     * @param Builder $query
     * @param Model   $model
     *
     * @return void
     */
    public function apply(Builder $query, Model $model)
    {
        {
            $group = Group::findByAnrOrFail('cn=intranetusers,ou=groups,dc=contoso,dc=com');

            $query->whereMemberOf($group);
        }
    }
}

My User Model ( I had to comment out my relationship to the my survey model once I removed the Eloquent Model from inheritance.

namespace App;

use App\Ldap\Scopes\ImportFilter;
use Illuminate\Database\Eloquent\SoftDeletes;
use LdapRecord\Models\ActiveDirectory\User as BaseModel;

class User extends BaseModel
{
    use SoftDeletes;

    /**
     * 
     * Global Scope Filter
     * 
     */
    public static function boot()

    {
        static::boot();

        static::addGlobalScope(new ImportFilter);
    }

    protected $hidden = [
        'password',
    ];

    protected $guarded = [];

    protected $perPage = 50;

    //public function surveys()
    //{
    //    return $this->hasMany('App\Survey');
    //}

    public function scopeOrderByName($query)
    {
        $query->orderBy('last_name')->orderBy('first_name');
    }

    public function scopeFilter($query, array $filters)
    {
        $query->when($filters['search'] ?? null, function ($query, $search) {
            $query->where(function ($query) use ($search) {
                $query->where('first_name', 'like', '%'.$search.'%')
                    ->orWhere('last_name', 'like', '%'.$search.'%');
            });
        })->when($filters['trashed'] ?? null, function ($query, $trashed) {
            if ($trashed === 'with') {
                $query->withTrashed();
            } elseif ($trashed === 'only') {
                $query->onlyTrashed();
            }
        });
    }
}

My Auth driver:

    'providers' => [
        'ldap' => [
            'driver' => 'ldap',
            'model' => LdapRecord\Models\ActiveDirectory\User::class,
            'database' => [
                'model' => App\User::class,
                'sync_passwords' => true,
                'sync_attributes' => [
                    'name' => 'cn',
                    'first_name' => 'givenname',
                    'last_name' => 'sn',
                    'email' => 'mail',
                ],
            ],
        ],
    ],
stevebauman commented 4 years ago

Ah okay this is your issue here:

$group = Group::findByAnrOrFail('cn=intranetusers,ou=groups,dc=contoso,dc=com');

If you have the full distinguished name, use findOrFail method instead:

$group = Group::findOrFail('cn=intranetusers,ou=groups,dc=contoso,dc=com');

Can you give that a shot?

stevebauman commented 4 years ago

Oh also, it seems your applying the scope to your Eloquent database model -- not your LDAP model.

This is how your configured provider (inside your config/auth.php file) should look:

    'providers' => [
        'ldap' => [
            'driver' => 'ldap',
            'model' => App\Ldap\User::class, // <-- Your custom LdapRecord model
            'database' => [
                'model' => App\User::class, // <-- Your Laravel Eloquent database model
                'sync_passwords' => true,
                'sync_attributes' => [
                    'name' => 'cn',
                    'first_name' => 'givenname',
                    'last_name' => 'sn',
                    'email' => 'mail',
                ],
            ],
        ],
    ],
gregupton commented 4 years ago

So now that I've made those changes I am getting a memory error... this doesn't happen if I comment out the scope in the ldap user model.

$ php artisan ldap:import ldap
PHP Warning:  Module 'ldap' already loaded in Unknown on line 0
PHP Fatal error:  Allowed memory size of 2147483648 bytes exhausted (tried to allocate 262144 bytes) in /home/forge/my.contoso.com/app/Ldap/User.php on line 12
PHP Fatal error:  Allowed memory size of 2147483648 bytes exhausted (tried to allocate 262144 bytes) in Unknown on line 0

Line 12 is bash static::boot();

stevebauman commented 4 years ago

🀦 Dang, sorry my mistake, we're recursively calling the boot() method, causing it to be called indefinitely and running out of memory. Update your scope to this:

protected static function boot()
{
    parent::boot();

    static::addGlobalScope(new ImportFilter);
}

I've changed the boot() method to protected, and used parent::boot() instead of static::boot().

Let me know if successful!

gregupton commented 4 years ago

Now we're getting somewhere... I got about 13 of the 64 users. Logs show:

[2020-06-10 00:12:15] development.ERROR: Importing user [CN=Exchange Online-ApplicationAccount] failed. Declaration of Illuminate\Database\Eloquent\SoftDeletes::restore() should be compatible with LdapRecord\Models\ActiveDirectory\Entry::restore($newParentDn = NULL)  
stevebauman commented 4 years ago

Looks like you've applied the Eloquent SoftDeletes trait on your App\Ldap\User.php model -- remove it and you should be good!

stevebauman commented 4 years ago

Also, if you want to prevent system accounts (such as the Exchange Online-ApplicationAccount) from being imported into your Laravel application, you can add further LDAP filters to restrict the import even further.

For example, if your users are located inside (nested or otherwise) one of our LDAP Organizational Units, you can use the in() method to do so:

$group = Group::findByOrFail('cn=intranetusers,ou=groups,dc=contoso,dc=com');

$query
    ->in('ou=users,dc=contoso,dc=com')
    ->whereMemberOf($group);
gregupton commented 4 years ago

No softdeletes on that model...


namespace App\Ldap;

use App\Ldap\Scopes\ImportFilter;
use LdapRecord\Models\ActiveDirectory\User as BaseModel;

class User extends BaseModel
{
    protected static function boot()
    {
       parent::boot();

        static::addGlobalScope(new ImportFilter);
    }
}

There are no system accounts in the IntranetUsers group and unfortunately all of the users are spread across many OU's so using in() may become cumbersome.

stevebauman commented 4 years ago

Strange... can you post your config/auth.php file?

gregupton commented 4 years ago
return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session", "token"
    |
    */

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

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'ldap' => [
            'driver' => 'ldap',
            'model' => App\Ldap\User::class,
            'database' => [
                'model' => App\User::class,
                'sync_passwords' => true,
                'sync_attributes' => [
                    'name' => 'cn',
                    'first_name' => 'givenname',
                    'last_name' => 'sn',
                    'email' => 'mail',
                ],
            ],
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

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

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the amount of seconds before a password confirmation
    | times out and the user is prompted to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => 10800,

];
stevebauman commented 4 years ago

Hmmm... can you post your App\User.php model?

gregupton commented 4 years ago

Oddly, I'm not getting any error messages now on re-deployment. But only 13 of the 48 users are importing. I removed SoftDeletes from my App\User model last night.

Here is my current App\User model:


<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Auth\Authenticatable;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use LdapRecord\Laravel\Auth\LdapAuthenticatable;
use LdapRecord\Laravel\Auth\AuthenticatesWithLdap;

class User extends Model implements AuthenticatableContract, AuthorizableContract, LdapAuthenticatable
{
    use Notifiable, Authenticatable, Authorizable, AuthenticatesWithLdap;

    protected $hidden = [
        'password',
    ];

    protected $guarded = [];

    protected $perPage = 50;

    public function survey()
    {
        return $this->hasMany('App\Survey');
    }

    public function scopeOrderByName($query)
    {
        $query->orderBy('last_name')->orderBy('first_name');
    }

    public function scopeFilter($query, array $filters)
    {
        $query->when($filters['search'] ?? null, function ($query, $search) {
            $query->where(function ($query) use ($search) {
                $query->where('first_name', 'like', '%'.$search.'%')
                    ->orWhere('last_name', 'like', '%'.$search.'%');
            });
        })->when($filters['trashed'] ?? null, function ($query, $trashed) {
            if ($trashed === 'with') {
                $query->withTrashed();
            } elseif ($trashed === 'only') {
                $query->onlyTrashed();
            }
        });
    }
}

My Forge deployment script for my dev environment:

cd /home/forge/<project name>

git pull origin dev

composer install --no-interaction --prefer-dist --optimize-autoloader

npm install && npm run dev

php artisan migrate:fresh --force

php artisan ldap:import ldap --no-interaction

( flock -w 10 9 || exit 1
    echo 'Restarting FPM...'; sudo -S service php7.4-fpm reload ) 9>/tmp/fpmlock
stevebauman commented 4 years ago

Can you check the logs and see any errors on import?

gregupton commented 4 years ago

No errors now. Just missing users.

stevebauman commented 4 years ago

When running the import, does it show 13 / 48 successfully imported? Or are you identifying users who are missing yourself?

gregupton commented 4 years ago

It's only showing 13/13 but there are 48 user objects in the group.

$ php artisan ldap:import ldap
PHP Warning:  Module 'ldap' already loaded in Unknown on line 0
Found [] user(s).

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

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

 13/13 [β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“] 100%

Successfully imported / synchronized [13] user(s).
stevebauman commented 4 years ago

Ok I'm understanding now, thanks!

Are these users perhaps members of a group that is contained in your parent group?

Let's give this a shot -- update your ImportFilter to this:

$group = Group::findByDnOrFail('cn=intranetusers,ou=groups,dc=contoso,dc=com');

$query->whereMemberOf($group, $nested = true);

I've added the $nested flag in the above query. Let me know if this makes a difference πŸ‘

gregupton commented 4 years ago

Same result... I just emailed you the output of:

 Get-ADGroupMember -identity "IntranetUsers" -Recursive | Get-ADUser -Property DisplayName | Select Name,ObjectClass,DisplayName
stevebauman commented 4 years ago

Thanks Greg -- another thing to look as it the base_dn set inside of your config/ldap.php file -- is that set to the proper base of your domain?

gregupton commented 4 years ago

Yes, sir. Base DN appears to be correct in the .env file. I've exported all of the users and their attributes to a CSV file... Nothing is jumping out at me. Before the scope, we were importing 144 users. All of the ones that we're missing now were importing before.

Perhaps I need to consider moving these users to a specific OU and filter them based on that rather than the group membership.

stevebauman commented 4 years ago

Hmmm... Strange! I'm super interested as to why these users are not being discovered by the query.

Can you try this inside of your routes/web.php file and see if you get all the proper members?

We're just dumping the names of all the members of the group.

// routes/web.php

$group = \LdapRecord\Models\ActiveDirectory\Group::find(
    'cn=intranetusers,ou=groups,dc=contoso,dc=com'
);

dd($group->members()->get()->map->getFirstAttribute('cn'));

// ...
gregupton commented 4 years ago

BadMethodCallException Call to undefined method LdapRecord\Query\Model\ActiveDirectoryBuilder::findByDn()

stevebauman commented 4 years ago

Typo! My mistake -- see the updated code example.

gregupton commented 4 years ago

I've removed the names, but it returns the same 13...

LdapRecord\Query\Collection^ {#285
  #items: array:13 [
    0 => 
    1 => 
    2 => 
    3 => 
    4 => 
    5 => 
    6 => 
    7 => 
    8 => 
    9 => 
    10 =>
    11 => 
    12 => 
  ]
}
Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 1
stevebauman commented 4 years ago

Okay we're getting closer...

Try again with:

$group = \LdapRecord\Models\ActiveDirectory\Group::find(
    'cn=intranetusers,ou=groups,dc=contoso,dc=com'
);

dd($group->members()->recursive()->get()->map->getFirstAttribute('cn'));

I've added the recursive() method call to the above example.

stevebauman commented 4 years ago

If the same members are returned with the above example, is this group possibly a "Primary Group" of the other users that are missing? I ask, as primary groups are stored differently than regular group memberships.

gregupton commented 4 years ago

Same output.

I looked at an account that is being found and one that is not, and both have the option to set the group as a primary group, which leads me to believe that it is not the case.

The group scope is global and the group type is security.

Also, just for the sake of my sanity, I created a new group... added all 48 users to it. Same output.

stevebauman commented 4 years ago

Very strange... can you post your LDAP configuration with sensitive details removed?

gregupton commented 4 years ago

My Config\Ldap.php


<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Default LDAP Connection Name
    |--------------------------------------------------------------------------
    |
    | Here you may specify which of the LDAP connections below you wish
    | to use as your default connection for all LDAP operations. Of
    | course you may add as many connections you'd like below.
    |
    */

    'default' => env('LDAP_CONNECTION', 'default'),

    /*
    |--------------------------------------------------------------------------
    | LDAP Connections
    |--------------------------------------------------------------------------
    |
    | Below you may configure each LDAP connection your application requires
    | access to. Be sure to include a valid base DN - otherwise you may
    | not receive any results when performing LDAP search operations.
    |
    */

    'connections' => [

        'default' => [
            'hosts' => [env('LDAP_HOST', '127.0.0.1')],
            'username' => env('LDAP_USERNAME', 'cn=user,dc=local,dc=om'),
            'password' => env('LDAP_PASSWORD', 'secret'),
            'port' => env('LDAP_PORT', 389),
            'base_dn' => env('LDAP_BASE_DN', 'dc=local,dc=com'),
            'timeout' => env('LDAP_TIMEOUT', 5),
            'use_ssl' => env('LDAP_SSL', false),
            'use_tls' => env('LDAP_TLS', false),
        ],

    ],

    /*
    |--------------------------------------------------------------------------
    | LDAP Logging
    |--------------------------------------------------------------------------
    |
    | When LDAP logging is enabled, all LDAP search and authentication
    | operations are logged using the default application logging
    | driver. This can assist in debugging issues and more.
    |
    */

    'logging' => env('LDAP_LOGGING', true),

    /*
    |--------------------------------------------------------------------------
    | LDAP Cache
    |--------------------------------------------------------------------------
    |
    | LDAP caching enables the ability of caching search results using the
    | query builder. This is great for running expensive operations that
    | may take many seconds to complete, such as a pagination request.
    |
    */

    'cache' => [
        'enabled' => env('LDAP_CACHE', false),
        'driver' => env('CACHE_DRIVER', 'file'),
    ],

];

my .env settings

LDAP_LOGGING=true
LDAP_CONNECTION=default
LDAP_HOST=<PDC IP>
LDAP_USERNAME=ldap@contoso.com
LDAP_PASSWORD=********
LDAP_PORT=389
LDAP_BASE_DN="dc=contoso,dc=com"
LDAP_TIMEOUT=5
LDAP_SSL=false
LDAP_TLS=false
stevebauman commented 4 years ago

Everything looking good there... Can you try this in your routes/web.php file and post the results?

$group = \LdapRecord\Models\ActiveDirectory\Group::find(
    'cn=intranetusers,ou=groups,dc=contoso,dc=com'
);

dd($group->member);
gregupton commented 4 years ago

OK, that found all of the users....

stevebauman commented 4 years ago

Ok, so the users are immediate members, but for some reason, the query for them is returning a limited number of them.

This brings me to the base_dn again... But to be sure, can you post the results of:

$group = \LdapRecord\Models\ActiveDirectory\Group::find(
    'cn=intranetusers,ou=groups,dc=contoso,dc=com'
);

dd($group->members()->getQuery()->getUnescapedQuery());
gregupton commented 4 years ago
"(objectclass=*)"
stevebauman commented 4 years ago

Okay, now try:

$group = \LdapRecord\Models\ActiveDirectory\Group::find(
    'cn=intranetusers,ou=groups,dc=contoso,dc=com'
);

dd($group->members()->getRelationQuery()->getUnescapedQuery());
gregupton commented 4 years ago
"(objectclass=*)"
gregupton commented 4 years ago

For now I've moved the users to a new OU and I'm using

$query->in('ou=users,ou=managed,dc=contoso,dc=com');

I can still and would like to troubleshoot this in my dev environment as time permits.

stevebauman commented 4 years ago

For now I've moved the users to a new OU and I'm using

When you do this, are all members returned properly via $group->members()->get()?

gregupton commented 4 years ago

No, it only returns 13 users.

stevebauman commented 4 years ago

Weird... this:

$group = \LdapRecord\Models\ActiveDirectory\Group::find(
    'cn=intranetusers,ou=groups,dc=contoso,dc=com'
);

dd($group->members()->getRelationQuery()->getUnescapedQuery());

Should return:

"(memberof=cn=intranetusers,ou=groups,dc=contoso,dc=com)"

Would you like to hop on a Zoom call tonight or tomorrow and we can debug this together?

No worries if not, I can continue going step by step with you on here.

Let me know your thoughts!

gregupton commented 4 years ago

What time tomorrow?

From: Steve Bauman notifications@github.com Reply-To: DirectoryTree/LdapRecord-Laravel reply@reply.github.com Date: Wednesday, June 10, 2020 at 5:54 PM To: DirectoryTree/LdapRecord-Laravel LdapRecord-Laravel@noreply.github.com Cc: Gregory Upton greg@itHero.tech, Mention mention@noreply.github.com Subject: Re: [DirectoryTree/LdapRecord-Laravel] globalScope or import filter [Support] (#166)

Weird... this:

$group = \LdapRecord\Models\ActiveDirectory\Group::find(

'cn=intranetusers,ou=groups,dc=contoso,dc=com'

);

dd($group->members()->getRelationQuery()->getUnescapedQuery());

Should return:

"(memberof=cn=intranetusers,ou=groups,dc=contoso,dc=com)"

Would you like to hop on a Zoom call tonight or tomorrow and we can debug this together?

No worries if not, I can continue going step by step with you on here.

Let me know your thoughts!

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/DirectoryTree/LdapRecord-Laravel/issues/166#issuecomment-642287440, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ANA4YRRPGYQFIC36FYRRBNDRV76HJANCNFSM4NZ3MTLA.

stevebauman commented 4 years ago

I can do noon at 12 PM EST, or anytime after 4:30 PM EST?

gregupton commented 4 years ago

Let’s do noon.

From: Steve Bauman notifications@github.com Reply-To: DirectoryTree/LdapRecord-Laravel reply@reply.github.com Date: Wednesday, June 10, 2020 at 6:15 PM To: DirectoryTree/LdapRecord-Laravel LdapRecord-Laravel@noreply.github.com Cc: Gregory Upton greg@itHero.tech, Mention mention@noreply.github.com Subject: Re: [DirectoryTree/LdapRecord-Laravel] globalScope or import filter [Support] (#166)

I can do noon at 12 PM EST, or anytime after 4:30 PM EST?

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/DirectoryTree/LdapRecord-Laravel/issues/166#issuecomment-642295642, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ANA4YRRBBN46AFEFEZ4P77LRWAAXHANCNFSM4NZ3MTLA.

stevebauman commented 4 years ago

Great, I’ll send you a zoom link to your email tomorrow morning for noon.

stevebauman commented 4 years ago

Hey Greg - thanks for connecting with me earlier today to get this issue fixed!

I've done a bit of digging on this -- and the issue may be permissions with the account you are binding to the AD directory with:

https://serverfault.com/questions/582728/linux-ldap-query-to-ad-missing-group-members

Somehow certain AD objects were not readable by the generic account I was using (the LDAP query account did not have the read group membership permission) - adding that right to my generic LDAP query account fixed the issue permanently.

What you're using now will still work, but wanted to post this in case you need to troubleshoot this in the future and go back to what was being attempted previously. πŸ‘

gregupton commented 4 years ago

So after our Zoom meeting I wasn't able to log into the app with the workaround, it was syncing all of the users, but the passwords weren't working.

Interestingly enough, I logged into our tools server as the generic account and was able to query all members of the group via powershell.

However, you're on the right track with permissions, I ran the delegation wizard and gave the user account permission to "read all user information" and reverted the code to the original example and it works flawlessly.

Might be worth making a note in the project docs.

stevebauman commented 4 years ago

Awesome! I'm really glad it's working properly now. I'll definitely add this into the docs πŸ‘

I appreciate your time getting to the bottom of this issue.