-
Notifications
You must be signed in to change notification settings - Fork 0
LdapData helper #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
LdapData helper #55
Changes from all commits
05fad48
d94cbda
c972cdb
2d1ce8d
09e15b8
f14c1d0
8693d58
eeee373
de0f2dd
20e86af
fddcdbc
7d153bf
955fcd4
0463ce5
efc9a44
4fc272f
1cbb371
d90bff4
697272c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| /* | ||
| |---------------------------------------------------------------------- | ||
| | LDAP Credentials | ||
| |---------------------------------------------------------------------- | ||
| | | ||
| | The credentials used to connect to the LDAP server, which must be set | ||
| | in the .env file. | ||
| | | ||
| | Note that LDAP_USER must be a fully-qualified DN, so we append the | ||
| | default Cornell base DN for authorized users. | ||
| | | ||
| */ | ||
| 'user' => env('LDAP_USER') ? env('LDAP_USER').',ou=Directory Administrators,o=Cornell University,c=US' : '', | ||
| 'pass' => env('LDAP_PASS') ?? '', | ||
|
|
||
| /* | ||
| |---------------------------------------------------------------------- | ||
| | LDAP Server | ||
| |---------------------------------------------------------------------- | ||
| | | ||
| | The LDAP server to connect to and base_dn, defaulting to the Cornell | ||
| | LDAP server and base DN. | ||
| | | ||
| */ | ||
| 'server' => env('LDAP_SERVER', 'ldaps://query.directory.cornell.edu'), | ||
| 'base_dn' => env('LDAP_BASE_DN', 'ou=People,o=Cornell University,c=US'), | ||
|
|
||
| /* | ||
| |---------------------------------------------------------------------- | ||
| | LDAP Cache | ||
| |---------------------------------------------------------------------- | ||
| | | ||
| | The number of seconds to cache LDAP queries. This is useful for | ||
| | performance, but be careful not to cache too long, as LDAP data | ||
| | can change frequently. | ||
| | | ||
| */ | ||
| 'cache_seconds' => env('LDAP_CACHE_SECONDS', 300), | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| <?php | ||
|
|
||
| namespace CornellCustomDev\LaravelStarterKit\Ldap; | ||
|
|
||
| /** | ||
| * An immutable data object representing the LDAP data returned for a user. | ||
| */ | ||
| class LdapData | ||
| { | ||
| public function __construct( | ||
| public string $uid, | ||
| public string $principalName, | ||
| public string $emplid, | ||
| public ?string $firstName, | ||
| public ?string $lastName, | ||
| public ?string $displayName, | ||
| public ?string $email, | ||
| public ?string $campusPhone, | ||
| public ?string $deptName, | ||
| public ?string $workingTitle, | ||
| public ?string $primaryAffiliation, | ||
| public ?array $affiliations, | ||
| public ?array $previousNetids, | ||
| public ?array $previousEmplids, | ||
| public array $ldapData = [], | ||
| public array $returnedData = [] | ||
| ) {} | ||
|
|
||
| /** | ||
| * Create a new LdapData object from an array of LDAP data. | ||
| * | ||
| * See https://confluence.cornell.edu/display/IDM/Attributes | ||
| * | ||
| * @param array $data The LDAP data array, typically from a search result. | ||
| * @param bool $withLdapData Whether to include the LDAP data in the returned object. | ||
| */ | ||
| public static function make(array $data, bool $withLdapData = true): ?LdapData | ||
| { | ||
| // Use preferred first name if it is not null, otherwise use givenName. | ||
| $firstName = ($data['cornelleduprefgivenname'] ?? null) ?: ($data['givenName'] ?? null); | ||
|
|
||
| // Use preferred last name if it is not null, otherwise use sn. | ||
| $lastName = ($data['cornelleduprefsn'] ?? null) ?: ($data['sn'] ?? null); | ||
|
|
||
| // Affiliations can be a string, an array, or not set | ||
| $affiliationCollection = collect(($data['cornelleduaffiliation'] ?? null) ?: null); | ||
| $affiliations = $affiliationCollection->toArray(); | ||
|
|
||
| // Use the defined primary affiliation or the first affiliation in the collection. | ||
| $primaryAffiliation = ($data['cornelleduprimaryaffiliation'] ?? null) ?: $affiliationCollection->shift() ?? ''; | ||
|
|
||
| // Secondary affiliation is the first affiliation that is not the primary affiliation. | ||
| $secondaryAffiliation = $affiliationCollection->reject($primaryAffiliation)->first() ?? ''; | ||
|
|
||
| // Process previousNetids: always convert the comma-separated string to an array. | ||
| $previousNetIds = collect(explode(',', $data['cornelledupreviousnetids'] ?? '')) | ||
| ->map(fn ($id) => trim($id))->filter()->all(); | ||
|
|
||
| // Same for previousEmplids | ||
| $previousEmplids = collect(explode(',', $data['cornelledupreviousemplids'] ?? '')) | ||
| ->map(fn ($id) => trim($id))->filter()->all(); | ||
|
|
||
| // User may have exercised FERPA right to suppress name. | ||
| if (empty($firstName) && empty($lastName)) { | ||
| $firstName = 'Cornell'; | ||
| $lastName = $primaryAffiliation === 'student' ? 'Student' : 'User'; | ||
| } | ||
|
|
||
| // Format an array to match the old LDAP::data function | ||
| $ldapData = [ | ||
| 'emplid' => $data['cornelleduemplid'] ?? '', | ||
| 'firstname' => $firstName ?? '', | ||
| 'lastname' => $lastName ?? '', | ||
| 'name' => trim("$firstName $lastName"), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think full name must include middle name or at least it should be an option for both options. Also name formatting is usually application task: some applications need to display last name fist, some - first name first
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The array that is being populated is only there to match the This |
||
| 'email' => $data['mail'] ?? '', | ||
| 'campusphone' => $data['cornelleducampusphone'] ?? '', | ||
| 'netid' => $data['uid'], | ||
| 'deptname' => $data['cornelledudeptname1'] ?? '', | ||
| 'primaryaffiliation' => $primaryAffiliation, | ||
| 'secondaryaffiliation' => $secondaryAffiliation, | ||
| 'wrkngtitle' => $data['cornelleduwrkngtitle1'] ?? '', | ||
| 'affiliations' => $affiliations, | ||
| 'previousnetids' => $data['cornelledupreviousnetids'] ?? null, | ||
| 'previousemplids' => $data['cornelledupreviousemplids'] ?? null, | ||
| ]; | ||
|
Comment on lines
+84
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need to return all these data from LDAP? Most of the time we just need to get a name and netid. I think the BindID must have special permissions to return some of these data (like emplid) and usually we do not have and do not need those permissions.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to return all the data from LDAP: No and most queries won't, which is fine and the code requires only 'uid' to be populated in the results. (And even that can be made optional.) The attribute list in here was meant as a starter list, merging what is in DailyCheck, IT Gov, and EMN phone, which happened to be three place I was working at the time I was putting this together. Looking at the LDAP names in Confluence, I see that indeed |
||
|
|
||
| return new LdapData( | ||
| uid: $data['uid'], | ||
| principalName: $data['edupersonprincipalname'] | ||
| ?? $data['uid'].'@cornell.edu', | ||
| emplid: $data['cornelleduemplid'] ?? '', | ||
|
|
||
| firstName: $firstName, | ||
| lastName: $lastName, | ||
|
|
||
| // Use preferred display name if it is not null, otherwise fall back on first_name + last_name. | ||
| displayName: $data['displayname'] | ||
| ?? trim($firstName.' '.$lastName), | ||
|
|
||
| // Only set 'email' if it is not empty. | ||
| email: ($data['mail'] ?? null) ?: null, | ||
|
|
||
| campusPhone: $data['cornelleducampusphone'] ?? null, | ||
| deptName: $data['cornelledudeptname1'] ?? null, | ||
| workingTitle: $data['cornelleduwrkngtitle1'] ?? null, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if an application needs department name and working title, then these data should be coming from Workday not from LDAP
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you are bringing up a good question about the source of the data. I can see that the attribute documentation indicates the those fields come from "HR" (i.e., Workday), but I don't know much else. Do we have reason to be concerned about some of these pass-through fields? Looking at the sources for the attributes, lots of them come from Workday and Peoplesoft. |
||
| primaryAffiliation: $primaryAffiliation ?: null, | ||
| affiliations: $affiliations, | ||
| previousNetids: $previousNetIds ?: null, | ||
| previousEmplids: $previousEmplids ?: null, | ||
| ldapData: $withLdapData ? $ldapData : [], | ||
| returnedData: $withLdapData ? $data : [], | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Provides an id that is unique within the IdP, i.e., NetID or CWID | ||
| */ | ||
| public function id(): string | ||
| { | ||
| return $this->uid; | ||
| } | ||
|
|
||
| /* | ||
| * Returns the eduPersonPrincipalName | ||
| */ | ||
| public function principalName(): string | ||
| { | ||
| return $this->principalName; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the primary email (netid@cornell.edu) if available, otherwise the alias email. | ||
| */ | ||
| public function email(): string | ||
| { | ||
| return $this->email ?: $this->principalName; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how can alias email exists without primary email?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh! The doc comment is backwards 🤦🏻♂️ I think this confusion (mine, yours) highlights that we should be very clear about the function names. Maybe:
|
||
|
|
||
| /** | ||
| * Returns the display name if available, otherwise the common name, fallback is "givenName sn". | ||
| */ | ||
| public function name(): string | ||
| { | ||
| return $this->displayName; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <?php | ||
|
|
||
| namespace CornellCustomDev\LaravelStarterKit\Ldap; | ||
|
|
||
| use ErrorException; | ||
|
|
||
| /** | ||
| * An exception thrown by LdapService. | ||
| */ | ||
| class LdapDataException extends ErrorException | ||
| { | ||
| // This class is intentionally empty. | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <?php | ||
|
|
||
| namespace CornellCustomDev\LaravelStarterKit\Ldap; | ||
|
|
||
| use CornellCustomDev\LaravelStarterKit\StarterKitServiceProvider; | ||
| use Illuminate\Support\ServiceProvider; | ||
|
|
||
| class LdapDataServiceProvider extends ServiceProvider | ||
| { | ||
| const INSTALL_CONFIG_TAG = 'starterkit-ldap-config'; | ||
|
|
||
| public function register(): void | ||
| { | ||
| $this->mergeConfigFrom( | ||
| path: __DIR__.'/../../config/ldap.php', | ||
| key: 'ldap', | ||
| ); | ||
| } | ||
|
|
||
| public function boot(): void | ||
| { | ||
| if ($this->app->runningInConsole()) { | ||
| $this->publishes([ | ||
| __DIR__.'/../../config/ldap.php' => config_path('ldap.php'), | ||
| ], StarterKitServiceProvider::PACKAGE_NAME.':'.self::INSTALL_CONFIG_TAG); | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.