2016-10-31 20:59:46 -07:00
< ? php
2020-08-14 14:45:05 -07:00
declare ( strict_types = 1 );
2016-10-31 20:59:46 -07:00
namespace App\Console\Commands ;
2020-08-14 14:45:05 -07:00
use Log ;
use Exception ;
2016-10-31 20:59:46 -07:00
use App\Models\User ;
2020-08-14 14:45:05 -07:00
use App\Services\LdapAd ;
2016-10-31 20:59:46 -07:00
use App\Models\Location ;
2020-08-14 14:45:05 -07:00
use Illuminate\Console\Command ;
use Adldap\Models\User as AdldapUser ;
/**
* LDAP / AD sync command .
*
* @ author Wes Hulette < jwhulette @ gmail . com >
*
* @ since 5.0 . 0
*/
2016-10-31 20:59:46 -07:00
class LdapSync extends Command
{
/**
* The name and signature of the console command .
*
* @ var string
*/
2020-08-14 14:45:05 -07:00
protected $signature = ' snipeit : ldap - sync
{ -- location = : A location name }
{ -- location_id = : A location id }
{ -- base_dn = : A diffrent base DN to use }
{ -- summary : Print summary }
{ -- json_summary : Print summary in json format }
{ -- dryrun : Run the sync process but don\ 't update the database}' ;
2016-10-31 20:59:46 -07:00
/**
* The console command description .
*
* @ var string
*/
2020-08-14 14:45:05 -07:00
protected $description = 'Command line LDAP/AD sync' ;
2016-10-31 20:59:46 -07:00
/**
2020-08-14 14:45:05 -07:00
* An LdapAd instance .
*
* @ var \App\Models\LdapAd
*/
private $ldap ;
/**
* LDAP settings collection .
*
* @ var \Illuminate\Support\Collection
*/
private $settings = null ;
/**
* A default location collection .
*
* @ var \Illuminate\Support\Collection
*/
private $defaultLocation = null ;
/**
* Mapped locations collection .
2016-10-31 20:59:46 -07:00
*
2020-08-14 14:45:05 -07:00
* @ var \Illuminate\Support\Collection
*/
private $mappedLocations = null ;
/**
* The summary collection .
*
* @ var \Illuminate\Support\Collection
*/
private $summary ;
/**
* Is dry - run ?
*
* @ var bool
*/
private $dryrun = false ;
/**
* Show users to be imported .
*
* @ var array
*/
private $userlist = [];
/**
* Create a new command instance .
2016-10-31 20:59:46 -07:00
*/
2020-08-14 14:45:05 -07:00
public function __construct ( LdapAd $ldap )
2016-10-31 20:59:46 -07:00
{
parent :: __construct ();
2020-08-14 14:45:05 -07:00
$this -> ldap = $ldap ;
$this -> settings = $this -> ldap -> ldapSettings ;
$this -> summary = collect ();
2016-10-31 20:59:46 -07:00
}
/**
* Execute the console command .
*
* @ return mixed
*/
public function handle ()
{
2020-08-14 14:45:05 -07:00
ini_set ( 'max_execution_time' , '600' ); //600 seconds = 10 minutes
ini_set ( 'memory_limit' , '500M' );
2020-08-24 21:35:00 -07:00
$old_error_reporting = error_reporting (); // grab old error_reporting .ini setting, for later re-enablement
error_reporting ( $old_error_reporting & ~ E_DEPRECATED ); // disable deprecation warnings, for LDAP in PHP 7.4 (and greater)
2016-10-31 20:59:46 -07:00
2020-08-14 14:45:05 -07:00
if ( $this -> option ( 'dryrun' )) {
$this -> dryrun = true ;
}
$this -> checkIfLdapIsEnabled ();
$this -> checkLdapConnetion ();
$this -> setBaseDn ();
$this -> getUserDefaultLocation ();
/*
* Use the default location if set , this is needed for the LDAP users sync page
*/
if ( ! $this -> option ( 'base_dn' ) && null == $this -> defaultLocation ) {
$this -> getMappedLocations ();
}
$this -> processLdapUsers ();
// Print table of users
if ( $this -> dryrun ) {
$this -> info ( 'The following users will be synced!' );
$headers = [ 'First Name' , 'Last Name' , 'Username' , 'Email' , 'Employee #' , 'Location Id' , 'Status' ];
$this -> table ( $headers , $this -> summary -> toArray ());
}
2016-10-31 20:59:46 -07:00
2020-08-24 21:35:00 -07:00
error_reporting ( $old_error_reporting ); // re-enable deprecation warnings.
2020-08-14 14:45:05 -07:00
return $this -> getSummary ();
}
/**
* Generate the LDAP sync summary .
*
* @ author Wes Hulette < jwhulette @ gmail . com >
*
* @ since 5.0 . 0
*
* @ return string
*/
private function getSummary () : string
{
if ( $this -> option ( 'summary' ) && null === $this -> dryrun ) {
$this -> summary -> each ( function ( $item ) {
$this -> info ( 'USER: ' . $item [ 'note' ]);
if ( 'ERROR' === $item [ 'status' ]) {
$this -> error ( 'ERROR: ' . $item [ 'note' ]);
}
});
} elseif ( $this -> option ( 'json_summary' )) {
$json_summary = [
'error' => false ,
'error_message' => '' ,
'summary' => $this -> summary -> toArray (),
];
$this -> info ( json_encode ( $json_summary ));
2016-10-31 20:59:46 -07:00
}
2020-08-14 14:45:05 -07:00
return '' ;
}
2016-10-31 20:59:46 -07:00
2020-08-14 14:45:05 -07:00
/**
* Create a new user or update an existing user .
*
* @ author Wes Hulette < jwhulette @ gmail . com >
*
* @ since 5.0 . 0
*
* @ param \Adldap\Models\User $snipeUser
*/
private function updateCreateUser ( AdldapUser $snipeUser ) : void
{
$user = $this -> ldap -> processUser ( $snipeUser , $this -> defaultLocation , $this -> mappedLocations );
$summary = [
'firstname' => $user -> first_name ,
'lastname' => $user -> last_name ,
'username' => $user -> username ,
'employee_number' => $user -> employee_num ,
'email' => $user -> email ,
'location_id' => $user -> location_id ,
];
// Only update the database if is not a dry run
if ( ! $this -> dryrun ) {
2020-10-21 15:13:36 -07:00
if ( $user -> isDirty ()) { //if nothing on the user changed, don't bother trying to save anything nor put anything in the summary
if ( $user -> save ()) {
$summary [ 'note' ] = ( $user -> wasRecentlyCreated ? 'CREATED' : 'UPDATED' );
$summary [ 'status' ] = 'SUCCESS' ;
} else {
$errors = '' ;
foreach ( $user -> getErrors () -> getMessages () as $error ) {
$errors .= implode ( " , " , $error );
}
$summary [ 'note' ] = $snipeUser -> getDN () . ' was not imported. REASON: ' . $errors ;
$summary [ 'status' ] = 'ERROR' ;
2020-08-14 14:45:05 -07:00
}
2020-10-21 15:13:36 -07:00
} else {
$summary = null ;
2017-12-14 12:57:43 -08:00
}
}
2017-01-11 23:37:14 -08:00
2020-10-06 18:31:06 -07:00
// $summary['note'] = ($user->getOriginal('username') ? 'UPDATED' : 'CREATED'); // this seems, kinda, like, superfluous, relative to the $summary['note'] thing above, yeah?
2020-10-21 15:13:36 -07:00
if ( $summary ) { //if the $user wasn't dirty, $summary was set to null so that we will skip the following push()
$this -> summary -> push ( $summary );
}
2020-08-14 14:45:05 -07:00
}
2018-02-13 17:06:42 -08:00
2020-08-14 14:45:05 -07:00
/**
* Process the users to update / create .
*
* @ author Wes Hulette < jwhulette @ gmail . com >
*
* @ since 5.0 . 0
*
* @ param int $page The page to get the result set
*/
private function processLdapUsers ( int $page = 0 ) : void
{
try {
$ldapUsers = $this -> ldap -> getLdapUsers ( $page );
} catch ( Exception $e ) {
$this -> outputError ( $e );
exit ( $e -> getMessage ());
2016-10-31 20:59:46 -07:00
}
2020-08-14 14:45:05 -07:00
if ( 0 == $ldapUsers -> count ()) {
$msg = 'ERROR: No users found!' ;
Log :: error ( $msg );
if ( $this -> dryrun ) {
$this -> error ( $msg );
2017-01-11 23:37:14 -08:00
}
2020-08-14 14:45:05 -07:00
exit ( $msg );
}
2017-01-11 23:37:14 -08:00
2020-08-14 14:45:05 -07:00
// Process each individual users
foreach ( $ldapUsers as $user ) {
$this -> updateCreateUser ( $user );
}
2018-01-23 18:15:36 -08:00
2020-08-14 14:45:05 -07:00
if ( $ldapUsers -> getCurrentPage () < $ldapUsers -> getPages () - 1 ) {
$this -> processLdapUsers ( $ldapUsers -> getCurrentPage () + 1 );
}
}
2018-01-23 18:15:36 -08:00
2020-08-14 14:45:05 -07:00
/**
* Get the mapped locations if a base_dn is provided .
*
* @ author Wes Hulette < jwhulette @ gmail . com >
*
* @ since 5.0 . 0
*/
private function getMappedLocations ()
{
$ldapOuLocation = Location :: where ( 'ldap_ou' , '!=' , '' ) -> select ([ 'id' , 'ldap_ou' ]) -> get ();
$locations = $ldapOuLocation -> sortBy ( function ( $ou , $key ) {
return strlen ( $ou -> ldap_ou );
});
if ( $locations -> count () > 0 ) {
$msg = 'Some locations have special OUs set. Locations will be automatically set for users in those OUs.' ;
LOG :: debug ( $msg );
if ( $this -> dryrun ) {
$this -> info ( $msg );
2017-01-11 23:37:14 -08:00
}
2020-10-06 18:31:06 -07:00
$this -> mappedLocations = $locations -> pluck ( 'ldap_ou' , 'id' ); // TODO: this seems ok-ish, but the key-> value is going location_id -> OU name, and the primary action here is the opposite of that - going from OU's to location ID's.
2020-08-14 14:45:05 -07:00
}
}
2018-01-23 18:15:36 -08:00
2020-08-14 14:45:05 -07:00
/**
* Set the base dn if supplied .
*
* @ author Wes Hulette < jwhulette @ gmail . com >
*
* @ since 5.0 . 0
*/
private function setBaseDn () : void
{
if ( $this -> option ( 'base_dn' )) {
$this -> ldap -> baseDn = $this -> option ( 'base_dn' );
$msg = sprintf ( 'Importing users from specified base DN: "%s"' , $this -> ldap -> baseDn );
LOG :: debug ( $msg );
if ( $this -> dryrun ) {
$this -> info ( $msg );
2018-01-23 18:15:36 -08:00
}
2017-01-11 23:37:14 -08:00
}
2020-08-14 14:45:05 -07:00
}
2016-10-31 20:59:46 -07:00
2020-08-14 14:45:05 -07:00
/**
* Get a default location id for imported users .
*
* @ author Wes Hulette < jwhulette @ gmail . com >
*
* @ since 5.0 . 0
*/
private function getUserDefaultLocation () : void
{
$location = $this -> option ( 'location_id' ) ? ? $this -> option ( 'location' );
if ( $location ) {
$userLocation = Location :: where ( 'name' , '=' , $location )
-> orWhere ( 'id' , '=' , intval ( $location ))
-> select ([ 'name' , 'id' ])
-> first ();
if ( $userLocation ) {
$msg = 'Importing users with default location: ' . $userLocation -> name . ' (' . $userLocation -> id . ')' ;
LOG :: debug ( $msg );
if ( $this -> dryrun ) {
$this -> info ( $msg );
2016-10-31 20:59:46 -07:00
}
2019-05-06 06:40:53 -07:00
2020-08-14 14:45:05 -07:00
$this -> defaultLocation = collect ([
$userLocation -> id => $userLocation -> name ,
]);
} else {
$msg = 'The supplied location is invalid!' ;
LOG :: error ( $msg );
if ( $this -> dryrun ) {
$this -> error ( $msg );
2016-10-31 20:59:46 -07:00
}
2020-08-14 14:45:05 -07:00
exit ( 0 );
2016-10-31 20:59:46 -07:00
}
2020-08-14 14:45:05 -07:00
}
}
/**
* Check if LDAP intergration is enabled .
*
* @ author Wes Hulette < jwhulette @ gmail . com >
*
* @ since 5.0 . 0
*/
private function checkIfLdapIsEnabled () : void
{
if ( false === $this -> settings [ 'ldap_enabled' ]) {
$msg = 'LDAP intergration is not enabled. Exiting sync process.' ;
$this -> info ( $msg );
Log :: info ( $msg );
exit ( 0 );
}
}
2016-10-31 20:59:46 -07:00
2020-08-14 14:45:05 -07:00
/**
* Check to make sure we can access the server .
*
* @ author Wes Hulette < jwhulette @ gmail . com >
*
* @ since 5.0 . 0
*/
private function checkLdapConnetion () : void
{
try {
$this -> ldap -> testLdapAdUserConnection ();
$this -> ldap -> testLdapAdBindConnection ();
} catch ( Exception $e ) {
$this -> outputError ( $e );
exit ( 0 );
2016-10-31 20:59:46 -07:00
}
2020-08-14 14:45:05 -07:00
}
2016-10-31 20:59:46 -07:00
2020-08-14 14:45:05 -07:00
/**
* Output the json summary to the screen if enabled .
*
* @ param Exception $error
*/
private function outputError ( $error ) : void
{
if ( $this -> option ( 'json_summary' )) {
$json_summary = [
'error' => true ,
'error_message' => $error -> getMessage (),
'summary' => [],
];
2017-10-06 16:15:14 -07:00
$this -> info ( json_encode ( $json_summary ));
2016-10-31 20:59:46 -07:00
}
2020-08-14 14:45:05 -07:00
$this -> error ( $error -> getMessage ());
LOG :: error ( $error );
2016-10-31 20:59:46 -07:00
}
}