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 App\Models\Ldap ;
2016-10-31 20:59:46 -07:00
use App\Models\Location ;
2021-06-10 13:15:52 -07:00
use App\Models\Setting ;
use App\Models\User ;
use Illuminate\Console\Command ;
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
2020-11-30 17:11:44 -08:00
$ldap_result_active_flag = Setting :: getSettings () -> ldap_active_flag_field ;
$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 ;
2021-06-10 13:15:52 -07:00
$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' )) {
2021-06-10 13:15:52 -07:00
$json_summary = [ 'error' => true , 'error_message' => $e -> getMessage (), 'summary' => []];
2020-11-30 17:11:44 -08:00
$this -> info ( json_encode ( $json_summary ));
}
LOG :: info ( $e );
2021-06-10 13:15:52 -07:00
2020-11-30 17:11:44 -08:00
return [];
2016-10-31 20:59:46 -07:00
}
2021-06-10 13:15:52 -07:00
$summary = [];
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' )) {
2021-06-10 13:15:52 -07:00
$json_summary = [ 'error' => true , 'error_message' => $e -> getMessage (), 'summary' => []];
2020-11-30 17:11:44 -08:00
$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 );
2021-06-10 13:15:52 -07:00
2020-11-30 17:11:44 -08:00
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. */
2021-06-10 13:15:52 -07: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
2021-06-10 13:15:52 -07:00
if ( $this -> option ( 'location' ) != '' ) {
2020-11-30 17:11:44 -08:00
$location = Location :: where ( 'name' , '=' , $this -> option ( 'location' )) -> first ();
LOG :: debug ( 'Location name ' . $this -> option ( 'location' ) . ' passed' );
LOG :: debug ( 'Importing to ' . $location -> name . ' (' . $location -> id . ')' );
2021-06-10 13:15:52 -07:00
} elseif ( $this -> option ( 'location_id' ) != '' ) {
2020-11-30 17:11:44 -08:00
$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
2021-06-10 13:15:52 -07:00
if ( ! isset ( $location )) {
2020-11-30 17:11:44 -08:00
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. */
2021-06-10 13:15:52 -07:00
if ( $this -> option ( 'base_dn' ) == '' ) {
2020-11-30 17:11:44 -08:00
// 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 ();
2021-06-10 13:15:52 -07:00
$ldap_ou_lengths = [];
2020-11-30 17:11:44 -08:00
2020-12-01 21:26:52 -08:00
foreach ( $ldap_ou_locations as $ou_loc ) {
2021-06-10 13:15:52 -07:00
$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
2021-06-10 13:15:52 -07:00
if ( count ( $ldap_ou_locations ) > 0 ) {
2020-11-30 17:11:44 -08:00
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
2021-06-10 13:15:52 -07:00
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 {
2021-06-10 13:15:52 -07:00
$location_users = Ldap :: findLdapUsers ( $ldap_loc [ 'ldap_ou' ]);
2020-11-30 17:11:44 -08:00
} catch ( \Exception $e ) { // FIXME: this is stolen from line 77 or so above
if ( $this -> option ( 'json_summary' )) {
2021-06-10 13:15:52 -07:00
$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' => []];
2020-11-30 17:11:44 -08:00
$this -> info ( json_encode ( $json_summary ));
}
LOG :: info ( $e );
2021-06-10 13:15:52 -07:00
2020-11-30 17:11:44 -08:00
return [];
2016-10-31 20:59:46 -07:00
}
2021-06-10 13:15:52 -07:00
$usernames = [];
for ( $i = 0 ; $i < $location_users [ 'count' ]; $i ++ ) {
2020-11-30 17:11:44 -08:00
if ( array_key_exists ( $ldap_result_username , $location_users [ $i ])) {
2021-06-10 13:15:52 -07:00
$location_users [ $i ][ 'ldap_location_override' ] = true ;
$location_users [ $i ][ 'location_id' ] = $ldap_loc [ 'id' ];
2020-11-30 17:11:44 -08:00
$usernames [] = $location_users [ $i ][ $ldap_result_username ][ 0 ];
}
}
// Delete located users from the general group.
foreach ( $results as $key => $generic_entry ) {
2021-06-10 13:15:52 -07:00
if (( is_array ( $generic_entry )) && ( array_key_exists ( $ldap_result_username , $generic_entry ))) {
2020-11-30 17:11:44 -08:00
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 */
2021-06-10 13:15:52 -07:00
$tmp_pass = substr ( str_shuffle ( '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ), 0 , 20 );
2020-11-30 17:11:44 -08:00
$pass = bcrypt ( $tmp_pass );
2021-06-10 13:15:52 -07:00
for ( $i = 0 ; $i < $results [ 'count' ]; $i ++ ) {
if ( empty ( $ldap_result_active_flag ) || $results [ $i ][ $ldap_result_active_flag ][ 0 ] == 'TRUE' ) {
$item = [];
$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' ] : '' ;
$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 ] : '' ;
$item [ 'department' ] = isset ( $results [ $i ][ $ldap_result_dept ][ 0 ]) ? $results [ $i ][ $ldap_result_dept ][ 0 ] : '' ;
2021-04-14 10:17:57 -07:00
$department = Department :: firstOrCreate ([
2021-06-10 13:15:52 -07:00
'name' => $item [ 'department' ],
2021-04-14 10:17:57 -07:00
]);
2021-06-10 13:15:52 -07:00
$user = User :: where ( 'username' , $item [ 'username' ]) -> first ();
2020-11-30 17:11:44 -08:00
if ( $user ) {
// Updating an existing user.
2021-06-10 13:15:52 -07:00
$item [ 'createorupdate' ] = 'updated' ;
2020-11-30 17:11:44 -08:00
} else {
// Creating a new user.
$user = new User ;
$user -> password = $pass ;
$user -> activated = 0 ;
2021-06-10 13:15:52 -07:00
$item [ 'createorupdate' ] = 'created' ;
2020-11-30 17:11:44 -08:00
}
2021-06-10 13:15:52 -07:00
$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' ]);
$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
// Sync activated state for Active Directory.
2021-06-10 13:15:52 -07:00
if ( array_key_exists ( 'useraccountcontrol' , $results [ $i ])) {
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 ;
} */
2021-06-10 13:15:52 -07:00
$enabled_accounts = [
2020-11-30 17:11:44 -08:00
'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
2021-06-10 13:15:52 -07:00
'4260352' , // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
'1049088' , // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
2020-11-30 17:11:44 -08:00
];
2021-06-10 13:15:52 -07:00
$user -> activated = ( in_array ( $results [ $i ][ 'useraccountcontrol' ][ 0 ], $enabled_accounts )) ? 1 : 0 ;
2020-11-30 17:11:44 -08:00
}
// If we're not using AD, and there isn't an activated flag set, activate all users
elseif ( empty ( $ldap_result_active_flag )) {
2021-06-10 13:15:52 -07:00
$user -> activated = 1 ;
2020-11-30 17:11:44 -08:00
}
if ( $item [ 'ldap_location_override' ] == true ) {
$user -> location_id = $item [ 'location_id' ];
2021-06-10 13:15:52 -07:00
} elseif (( isset ( $location )) && ( ! empty ( $location ))) {
2020-11-30 17:11:44 -08:00
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 ()) {
2021-06-10 13:15:52 -07:00
$item [ 'note' ] = $item [ 'createorupdate' ];
$item [ 'status' ] = 'success' ;
2020-11-30 17:11:44 -08:00
} else {
foreach ( $user -> getErrors () -> getMessages () as $key => $err ) {
$errors .= $err [ 0 ];
}
2021-06-10 13:15:52 -07:00
$item [ 'note' ] = $errors ;
$item [ 'status' ] = 'error' ;
2020-11-30 17:11:44 -08:00
}
array_push ( $summary , $item );
}
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 ++ ) {
2021-06-10 13:15:52 -07:00
if ( $summary [ $x ][ 'status' ] == 'error' ) {
2020-11-30 17:11:44 -08:00
$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' ]) . '.' );
}
}
2021-06-10 13:15:52 -07:00
} elseif ( $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
}
}
}