2016-10-31 20:59:46 -07:00
< ? php
namespace App\Console\Commands ;
2021-04-14 10:17:57 -07:00
use App\Models\Department ;
2020-11-30 17:11:44 -08:00
use Illuminate\Console\Command ;
use App\Models\Setting ;
use App\Models\Ldap ;
2016-10-31 20:59:46 -07:00
use App\Models\User ;
use App\Models\Location ;
2020-11-30 17:11:44 -08:00
use Log ;
2016-10-31 20:59:46 -07:00
class LdapSync extends Command
{
/**
* The name and signature of the console command .
*
* @ var string
*/
2020-11-30 17:11:44 -08:00
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--summary} {--json_summary}' ;
2016-10-31 20:59:46 -07:00
/**
* The console command description .
*
* @ var string
*/
2020-11-30 17:11:44 -08:00
protected $description = 'Command line LDAP sync' ;
2020-08-14 14:45:05 -07:00
/**
* Create a new command instance .
2020-11-30 17:11:44 -08:00
*
* @ return void
2016-10-31 20:59:46 -07:00
*/
2020-11-30 17:11:44 -08:00
public function __construct ()
2016-10-31 20:59:46 -07:00
{
parent :: __construct ();
}
/**
* Execute the console command .
*
* @ return mixed
*/
public function handle ()
{
2020-11-30 17:11:44 -08:00
ini_set ( 'max_execution_time' , env ( 'LDAP_TIME_LIM' , 600 )); //600 seconds = 10 minutes
ini_set ( 'memory_limit' , env ( 'LDAP_MEM_LIM' , '500M' ));
$ldap_result_username = Setting :: getSettings () -> ldap_username_field ;
$ldap_result_last_name = Setting :: getSettings () -> ldap_lname_field ;
$ldap_result_first_name = Setting :: getSettings () -> ldap_fname_field ;
2016-10-31 20:59:46 -07:00
2022-02-03 15:01:45 -08:00
$ldap_result_active_flag = Setting :: getSettings () -> ldap_active_flag ;
2020-11-30 17:11:44 -08:00
$ldap_result_emp_num = Setting :: getSettings () -> ldap_emp_num ;
$ldap_result_email = Setting :: getSettings () -> ldap_email ;
2021-04-05 19:26:04 -07:00
$ldap_result_phone = Setting :: getSettings () -> ldap_phone_field ;
$ldap_result_jobtitle = Setting :: getSettings () -> ldap_jobtitle ;
$ldap_result_country = Setting :: getSettings () -> ldap_country ;
2021-04-14 10:17:57 -07:00
$ldap_result_dept = Setting :: getSettings () -> ldap_dept ;
2020-08-14 14:45:05 -07:00
2020-11-30 17:11:44 -08:00
try {
$ldapconn = Ldap :: connectToLdap ();
Ldap :: bindAdminToLdap ( $ldapconn );
} catch ( \Exception $e ) {
if ( $this -> option ( 'json_summary' )) {
$json_summary = [ " error " => true , " error_message " => $e -> getMessage (), " summary " => [] ];
$this -> info ( json_encode ( $json_summary ));
}
LOG :: info ( $e );
return [];
2016-10-31 20:59:46 -07:00
}
2020-11-30 17:11:44 -08:00
$summary = array ();
2016-10-31 20:59:46 -07:00
2020-11-30 17:11:44 -08:00
try {
if ( $this -> option ( 'base_dn' ) != '' ) {
$search_base = $this -> option ( 'base_dn' );
LOG :: debug ( 'Importing users from specified base DN: \"' . $search_base . '\".' );
2020-10-21 15:13:36 -07:00
} else {
2020-11-30 17:11:44 -08:00
$search_base = null ;
}
$results = Ldap :: findLdapUsers ( $search_base );
} catch ( \Exception $e ) {
if ( $this -> option ( 'json_summary' )) {
$json_summary = [ " error " => true , " error_message " => $e -> getMessage (), " summary " => [] ];
$this -> info ( json_encode ( $json_summary ));
2017-12-14 12:57:43 -08:00
}
2020-11-30 17:11:44 -08:00
LOG :: info ( $e );
return [];
2017-12-14 12:57:43 -08:00
}
2017-01-11 23:37:14 -08:00
2020-11-30 17:11:44 -08:00
/* Determine which location to assign users to by default. */
2020-12-01 21:26:52 -08:00
$location = NULL ; // FIXME - this would be better called "$default_location", which is more explicit about its purpose
2020-11-30 17:11:44 -08:00
if ( $this -> option ( 'location' ) != '' ) {
$location = Location :: where ( 'name' , '=' , $this -> option ( 'location' )) -> first ();
LOG :: debug ( 'Location name ' . $this -> option ( 'location' ) . ' passed' );
LOG :: debug ( 'Importing to ' . $location -> name . ' (' . $location -> id . ')' );
} elseif ( $this -> option ( 'location_id' ) != '' ) {
$location = Location :: where ( 'id' , '=' , $this -> option ( 'location_id' )) -> first ();
LOG :: debug ( 'Location ID ' . $this -> option ( 'location_id' ) . ' passed' );
LOG :: debug ( 'Importing to ' . $location -> name . ' (' . $location -> id . ')' );
2020-10-21 15:13:36 -07:00
}
2018-02-13 17:06:42 -08:00
2020-11-30 17:11:44 -08:00
if ( ! isset ( $location )) {
LOG :: debug ( 'That location is invalid or a location was not provided, so no location will be assigned by default.' );
2016-10-31 20:59:46 -07:00
}
2020-11-30 17:11:44 -08:00
/* Process locations with explicitly defined OUs, if doing a full import. */
if ( $this -> option ( 'base_dn' ) == '' ) {
// Retrieve locations with a mapped OU, and sort them from the shallowest to deepest OU (see #3993)
$ldap_ou_locations = Location :: where ( 'ldap_ou' , '!=' , '' ) -> get () -> toArray ();
$ldap_ou_lengths = array ();
2020-12-01 21:26:52 -08:00
foreach ( $ldap_ou_locations as $ou_loc ) {
$ldap_ou_lengths [] = strlen ( $ou_loc [ " ldap_ou " ]);
2017-01-11 23:37:14 -08:00
}
2020-11-30 17:11:44 -08:00
array_multisort ( $ldap_ou_lengths , SORT_ASC , $ldap_ou_locations );
2018-01-23 18:15:36 -08:00
2020-11-30 17:11:44 -08:00
if ( sizeof ( $ldap_ou_locations ) > 0 ) {
LOG :: debug ( 'Some locations have special OUs set. Locations will be automatically set for users in those OUs.' );
2017-01-11 23:37:14 -08:00
}
2020-11-30 17:11:44 -08:00
// Inject location information fields
for ( $i = 0 ; $i < $results [ " count " ]; $i ++ ) {
$results [ $i ][ " ldap_location_override " ] = false ;
$results [ $i ][ " location_id " ] = 0 ;
2018-01-23 18:15:36 -08:00
}
2016-10-31 20:59:46 -07:00
2020-11-30 17:11:44 -08:00
// Grab subsets based on location-specific DNs, and overwrite location for these users.
foreach ( $ldap_ou_locations as $ldap_loc ) {
try {
$location_users = Ldap :: findLdapUsers ( $ldap_loc [ " ldap_ou " ]);
} catch ( \Exception $e ) { // FIXME: this is stolen from line 77 or so above
if ( $this -> option ( 'json_summary' )) {
$json_summary = [ " error " => true , " error_message " => trans ( 'admin/users/message.error.ldap_could_not_search' ) . " Location: " . $ldap_loc [ 'name' ] . " (ID: " . $ldap_loc [ 'id' ] . " ) cannot connect to \" " . $ldap_loc [ " ldap_ou " ] . " \" - " . $e -> getMessage (), " summary " => [] ];
$this -> info ( json_encode ( $json_summary ));
}
LOG :: info ( $e );
return [];
2016-10-31 20:59:46 -07:00
}
2020-11-30 17:11:44 -08:00
$usernames = array ();
for ( $i = 0 ; $i < $location_users [ " count " ]; $i ++ ) {
2019-05-06 06:40:53 -07:00
2020-11-30 17:11:44 -08:00
if ( array_key_exists ( $ldap_result_username , $location_users [ $i ])) {
$location_users [ $i ][ " ldap_location_override " ] = true ;
$location_users [ $i ][ " location_id " ] = $ldap_loc [ " id " ];
$usernames [] = $location_users [ $i ][ $ldap_result_username ][ 0 ];
}
}
// Delete located users from the general group.
foreach ( $results as $key => $generic_entry ) {
if (( is_array ( $generic_entry )) && ( array_key_exists ( $ldap_result_username , $generic_entry ))) {
if ( in_array ( $generic_entry [ $ldap_result_username ][ 0 ], $usernames )) {
unset ( $results [ $key ]);
}
}
2016-10-31 20:59:46 -07:00
}
2020-11-30 17:11:44 -08:00
$global_count = $results [ 'count' ];
$results = array_merge ( $location_users , $results );
$results [ 'count' ] = $global_count ;
2016-10-31 20:59:46 -07:00
}
2020-08-14 14:45:05 -07:00
}
2020-11-30 17:11:44 -08:00
/* Create user account entries in Snipe-IT */
$tmp_pass = substr ( str_shuffle ( " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ " ), 0 , 20 );
$pass = bcrypt ( $tmp_pass );
for ( $i = 0 ; $i < $results [ " count " ]; $i ++ ) {
$item = array ();
$item [ " username " ] = isset ( $results [ $i ][ $ldap_result_username ][ 0 ]) ? $results [ $i ][ $ldap_result_username ][ 0 ] : " " ;
$item [ " employee_number " ] = isset ( $results [ $i ][ $ldap_result_emp_num ][ 0 ]) ? $results [ $i ][ $ldap_result_emp_num ][ 0 ] : " " ;
$item [ " lastname " ] = isset ( $results [ $i ][ $ldap_result_last_name ][ 0 ]) ? $results [ $i ][ $ldap_result_last_name ][ 0 ] : " " ;
$item [ " firstname " ] = isset ( $results [ $i ][ $ldap_result_first_name ][ 0 ]) ? $results [ $i ][ $ldap_result_first_name ][ 0 ] : " " ;
$item [ " email " ] = isset ( $results [ $i ][ $ldap_result_email ][ 0 ]) ? $results [ $i ][ $ldap_result_email ][ 0 ] : " " ;
$item [ " ldap_location_override " ] = isset ( $results [ $i ][ " ldap_location_override " ]) ? $results [ $i ][ " ldap_location_override " ] : " " ;
$item [ " location_id " ] = isset ( $results [ $i ][ " location_id " ]) ? $results [ $i ][ " location_id " ] : " " ;
2021-04-05 19:26:04 -07:00
$item [ " telephone " ] = isset ( $results [ $i ][ $ldap_result_phone ][ 0 ]) ? $results [ $i ][ $ldap_result_phone ][ 0 ] : " " ;
$item [ " jobtitle " ] = isset ( $results [ $i ][ $ldap_result_jobtitle ][ 0 ]) ? $results [ $i ][ $ldap_result_jobtitle ][ 0 ] : " " ;
$item [ " country " ] = isset ( $results [ $i ][ $ldap_result_country ][ 0 ]) ? $results [ $i ][ $ldap_result_country ][ 0 ] : " " ;
2021-04-14 10:17:57 -07:00
$item [ " department " ] = isset ( $results [ $i ][ $ldap_result_dept ][ 0 ]) ? $results [ $i ][ $ldap_result_dept ][ 0 ] : " " ;
$department = Department :: firstOrCreate ([
'name' => $item [ " department " ],
]);
2020-11-30 17:11:44 -08:00
$user = User :: where ( 'username' , $item [ " username " ]) -> first ();
2022-02-02 18:07:34 -08:00
2020-11-30 17:11:44 -08:00
if ( $user ) {
// Updating an existing user.
$item [ " createorupdate " ] = 'updated' ;
} else {
// Creating a new user.
$user = new User ;
$user -> password = $pass ;
2022-02-03 15:01:45 -08:00
$user -> activated = 1 ; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
2020-11-30 17:11:44 -08:00
$item [ " createorupdate " ] = 'created' ;
}
$user -> first_name = $item [ " firstname " ];
$user -> last_name = $item [ " lastname " ];
$user -> username = $item [ " username " ];
$user -> email = $item [ " email " ];
$user -> employee_num = e ( $item [ " employee_number " ]);
2021-04-05 19:26:04 -07:00
$user -> phone = $item [ " telephone " ];
$user -> jobtitle = $item [ " jobtitle " ];
$user -> country = $item [ " country " ];
2021-04-14 10:17:57 -07:00
$user -> department_id = $department -> id ;
2020-11-30 17:11:44 -08:00
2022-02-03 19:04:56 -08:00
if ( @ $results [ $i ][ $ldap_result_active_flag ][ 0 ]) {
\Log :: error ( " ldap_result_active_flag: $ldap_result_active_flag , value: " .@ $results [ $i ][ $ldap_result_active_flag ][ 0 ]);
}
2022-02-03 15:01:45 -08:00
if ( ! empty ( $ldap_result_active_flag )) { // IF we have an 'active' flag set....
2022-02-03 19:04:56 -08:00
//\Log::error("WE HAVE AN ACTIVE FLAG! We are going to set activated TO: ".(@$results[$i][$ldap_result_active_flag][0] ? 1 : 0));
// $parsed_active_flag = filter_var(@$results[$i][$ldap_result_active_flag][0], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
// $user->activated = $parsed_active_flag ?? true; // ....then anything truthy will activate the user, period. Anything falsey will deactivate them.
// (and anything even weirder than that will process as 'true' I guess?)
$raw_value = @ $results [ $i ][ $ldap_result_active_flag ][ 0 ];
$filter_var = filter_var ( $raw_value , FILTER_VALIDATE_BOOLEAN , FILTER_NULL_ON_FAILURE );
$boolean_cast = ( bool ) $raw_value ;
if ( ! is_null ( $raw_value )) {
\Log :: error ( " We have an active flag! filter_var for ' $raw_value ' says: " . ( is_null ( $filter_var ) ? 'NULL' : ( $filter_var ? 'true' : 'false' )) . " but boolean cast says: " . ( $boolean_cast ? 'true' : 'false' ) . " And string compare is: " . ( $raw_value > 0 ? 'true' : 'false' ));
}
$user -> activated = $filter_var ? ? $boolean_cast ; // this seems clever but it does pretty much exactly what I want. (I think?) No, it doesn't.
2022-02-03 15:01:45 -08:00
} elseif ( array_key_exists ( 'useraccountcontrol' , $results [ $i ]) ) {
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
// ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in
2020-11-30 17:11:44 -08:00
/* The following is _probably_ the correct logic , but we can ' t use it because
some users may have been dependent upon the previous behavior , and this
could cause additional access to be available to users they don ' t want
to allow to log in .
$useraccountcontrol = $results [ $i ][ 'useraccountcontrol' ][ 0 ];
if (
// based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties
( $useraccountcontrol & 0x200 ) && // is a NORMAL_ACCOUNT
! ( $useraccountcontrol & 0x02 ) && // *and* _not_ ACCOUNTDISABLE
! ( $useraccountcontrol & 0x10 ) // *and* _not_ LOCKOUT
) {
$user -> activated = 1 ;
} else {
$user -> activated = 0 ;
} */
$enabled_accounts = [
'512' , // 0x200 NORMAL_ACCOUNT
'544' , // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
'66048' , // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
'66080' , // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
'262656' , // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
'262688' , // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
'328192' , // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'328224' , // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
2022-01-12 12:07:51 -08:00
'4194816' , // 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
2020-11-30 17:11:44 -08:00
'4260352' , // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
'1049088' , // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
];
$user -> activated = ( in_array ( $results [ $i ][ 'useraccountcontrol' ][ 0 ], $enabled_accounts ) ) ? 1 : 0 ;
2022-02-03 15:01:45 -08:00
} /* implied 'else' here - leave the $user -> activated flag alone . Newly - created accounts will be active .
already - existing accounts will be however the administrator has set them */
2020-11-30 17:11:44 -08:00
if ( $item [ 'ldap_location_override' ] == true ) {
$user -> location_id = $item [ 'location_id' ];
} elseif (( isset ( $location )) && ( ! empty ( $location ))) {
if (( is_array ( $location )) && ( array_key_exists ( 'id' , $location ))) {
$user -> location_id = $location [ 'id' ];
} elseif ( is_object ( $location )) {
$user -> location_id = $location -> id ;
}
}
$user -> ldap_import = 1 ;
$errors = '' ;
if ( $user -> save ()) {
2022-02-03 19:04:56 -08:00
//\Log::info("We have done a save, and it was succesful! Results: ".print_r($user,true));
2020-11-30 17:11:44 -08:00
$item [ " note " ] = $item [ " createorupdate " ];
$item [ " status " ] = 'success' ;
} else {
foreach ( $user -> getErrors () -> getMessages () as $key => $err ) {
$errors .= $err [ 0 ];
}
$item [ " note " ] = $errors ;
$item [ " status " ] = 'error' ;
}
array_push ( $summary , $item );
2022-02-02 18:07:34 -08:00
2016-10-31 20:59:46 -07:00
}
2020-11-30 17:11:44 -08:00
if ( $this -> option ( 'summary' )) {
for ( $x = 0 ; $x < count ( $summary ); $x ++ ) {
if ( $summary [ $x ][ 'status' ] == 'error' ) {
$this -> error ( 'ERROR: ' . $summary [ $x ][ 'firstname' ] . ' ' . $summary [ $x ][ 'lastname' ] . ' (username: ' . $summary [ $x ][ 'username' ] . ') was not imported: ' . $summary [ $x ][ 'note' ]);
} else {
$this -> info ( 'User ' . $summary [ $x ][ 'firstname' ] . ' ' . $summary [ $x ][ 'lastname' ] . ' (username: ' . $summary [ $x ][ 'username' ] . ') was ' . strtoupper ( $summary [ $x ][ 'createorupdate' ]) . '.' );
}
}
} else if ( $this -> option ( 'json_summary' )) {
$json_summary = [ " error " => false , " error_message " => " " , " summary " => $summary ]; // hardcoding the error to false and the error_message to blank seems a bit weird
2017-10-06 16:15:14 -07:00
$this -> info ( json_encode ( $json_summary ));
2020-11-30 17:11:44 -08:00
} else {
return $summary ;
2016-10-31 20:59:46 -07:00
}
}
}