-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #601 from Vojtech-Sassmann/vsupAdExpired
feat: new script ad_group_vsup_o365
- Loading branch information
Showing
2 changed files
with
295 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#!/usr/bin/perl | ||
use feature "switch"; | ||
use strict; | ||
use warnings; | ||
use perunServicesInit; | ||
use perunServicesUtils; | ||
|
||
local $::SERVICE_NAME = "ad_group_vsup_o365"; | ||
local $::PROTOCOL_VERSION = "3.0.0"; | ||
my $SCRIPT_VERSION = "3.0.0"; | ||
|
||
perunServicesInit::init; | ||
my $DIRECTORY = perunServicesInit::getDirectory; | ||
my $fileName = "$DIRECTORY/$::SERVICE_NAME".".ldif"; | ||
my $baseDnFileName = "$DIRECTORY/baseDN"; | ||
|
||
my $data = perunServicesInit::getHashedHierarchicalData; | ||
|
||
#Constants | ||
our $A_LOGIN; *A_LOGIN = \'urn:perun:user_facility:attribute-def:virt:login'; | ||
our $A_IS_SERVICE; *A_IS_SERVICE = \'urn:perun:user:attribute-def:core:serviceUser'; | ||
our $A_F_BASE_DN; *A_F_BASE_DN = \'urn:perun:facility:attribute-def:def:adBaseDN'; | ||
our $A_F_SERV_BASE_DN; *A_F_SERV_BASE_DN = \'urn:perun:facility:attribute-def:def:adServiceBaseDN'; | ||
our $A_F_GROUP_BASE_DN; *A_F_GROUP_BASE_DN = \'urn:perun:facility:attribute-def:def:adGroupBaseDN'; | ||
our $A_R_GROUP_NAME; *A_R_GROUP_NAME = \'urn:perun:resource:attribute-def:def:adGroupName'; | ||
our $A_EXPIRATION_KOS; *A_EXPIRATION_KOS = \'urn:perun:user:attribute-def:def:expirationKos'; | ||
our $A_EXPIRATION_DC2; *A_EXPIRATION_DC2 = \'urn:perun:user:attribute-def:def:expirationDc2'; | ||
our $A_EXPIRATION_MANUAL; *A_EXPIRATION_MANUAL = \'urn:perun:user:attribute-def:def:expirationManual'; | ||
|
||
# CHECK ON FACILITY ATTRIBUTES4 | ||
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_GROUP_BASE_DN ))) { | ||
exit 1; | ||
} | ||
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_BASE_DN ))) { | ||
exit 1; | ||
} | ||
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_SERV_BASE_DN ))) { | ||
exit 1; | ||
} | ||
|
||
my $baseGroupDN = $data->getFacilityAttributeValue( attrName => $A_F_GROUP_BASE_DN ); | ||
my $baseDN = $data->getFacilityAttributeValue( attrName => $A_F_BASE_DN ); | ||
my $baseServiceDN = $data->getFacilityAttributeValue( attrName => $A_F_SERV_BASE_DN ); | ||
|
||
# | ||
# PRINT BASE_DN FILE | ||
# | ||
open FILE,">:encoding(UTF-8)","$baseDnFileName" or die "Cannot open $baseDnFileName: $! \n"; | ||
print FILE $baseGroupDN; | ||
close(FILE); | ||
|
||
my $groups = {}; | ||
my $usersByResource = {}; | ||
|
||
# FOR EACH RESOURCE | ||
foreach my $resourceId ($data->getResourceIds()) { | ||
|
||
my $group = $data->getResourceAttributeValue( resource => $resourceId, attrName => $A_R_GROUP_NAME ); | ||
$groups->{$group} = 1; | ||
|
||
# FOR EACH MEMBER ON RESOURCE | ||
foreach my $memberId ($data->getMemberIdsForResource( resource => $resourceId )) { | ||
|
||
my $login = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_LOGIN ); | ||
|
||
unless ($login) { | ||
# skip users without login = CN | ||
next; | ||
} | ||
|
||
my $expirationKOS = $data->getUserAttributeValue(member => $memberId, attrName => $A_EXPIRATION_KOS); | ||
my $expirationDC2 = $data->getUserAttributeValue(member => $memberId, attrName => $A_EXPIRATION_DC2); | ||
my $expirationManual = $data->getUserAttributeValue(member => $memberId, attrName => $A_EXPIRATION_MANUAL); | ||
|
||
my $isActive = isActive($expirationKOS, $expirationDC2, $expirationManual); | ||
|
||
# skip "vsup expired" members | ||
next unless ($isActive); | ||
|
||
my $serviceUser = $data->getUserAttributeValue( member => $memberId, attrName => $A_IS_SERVICE ); | ||
|
||
if (defined $serviceUser and ($serviceUser == 1)) { | ||
# store which service users (their DN) are on this resource | ||
$usersByResource->{$group}->{"CN=" . $login . "," . $baseServiceDN} = 1 | ||
} else { | ||
# store which normal users (their DN) are on this resource | ||
$usersByResource->{$group}->{"CN=" . $login . "," . $baseDN} = 1 | ||
} | ||
|
||
|
||
} | ||
|
||
} | ||
|
||
# | ||
# Print group data LDIF | ||
# | ||
open FILE,">:encoding(UTF-8)","$fileName" or die "Cannot open $fileName: $! \n"; | ||
|
||
for my $group (sort keys %$groups) { | ||
|
||
print FILE "dn: CN=" . $group . "," . $baseGroupDN . "\n"; | ||
print FILE "cn: " . $group . "\n"; | ||
print FILE "objectClass: group\n"; | ||
print FILE "objectClass: top\n"; | ||
|
||
my @groupMembers = sort keys %{$usersByResource->{$group}}; | ||
for my $member (@groupMembers) { | ||
print FILE "member: " . $member . "\n"; | ||
} | ||
|
||
# there must be empty line after each entry | ||
print FILE "\n"; | ||
|
||
} | ||
|
||
close FILE; | ||
|
||
perunServicesInit::finalize; | ||
|
||
|
||
# | ||
# returns 1 if the the latest of given expiration is either in the future, or in the 14 days grace period | ||
# otherwise, returns 0 | ||
# | ||
sub isActive() { | ||
|
||
# read input | ||
my $expirationKos = shift; | ||
my $expirationDc2 = shift; | ||
my $expirationMan = shift; | ||
# parse to time or undef | ||
my $expirationKosTime = ($expirationKos) ? Time::Piece->strptime($expirationKos,"%Y-%m-%d") : undef; | ||
my $expirationDc2Time = ($expirationDc2) ? Time::Piece->strptime($expirationDc2,"%Y-%m-%d") : undef; | ||
my $expirationManTime = ($expirationMan) ? Time::Piece->strptime($expirationMan,"%Y-%m-%d") : undef; | ||
|
||
my @expirations = (); | ||
if (defined $expirationKosTime) { push(@expirations, $expirationKosTime->epoch); } | ||
if (defined $expirationDc2Time) { push(@expirations, $expirationDc2Time->epoch); } | ||
if (defined $expirationManTime) { push(@expirations, $expirationManTime->epoch); } | ||
|
||
# sort all expirations | ||
my @sorted_expirations = sort { $a <=> $b } @expirations; | ||
|
||
my $latest_expiration = $sorted_expirations[$#sorted_expirations]; | ||
my $currentDate = Time::Piece->strptime(localtime->ymd,"%Y-%m-%d"); | ||
|
||
if (!defined $expirationKos and !defined $expirationDc2 and !defined $expirationMan) { | ||
# if no expiration set in source data - take as "never" | ||
return 1; | ||
} | ||
|
||
# Add time 23:59:59 to the date, since we want accounts to be active on the last day | ||
$latest_expiration = $latest_expiration + 86399; | ||
|
||
if (($latest_expiration + (14*24*60*60)) > $currentDate->epoch) { | ||
return 1; | ||
} | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
#!/usr/bin/perl | ||
use strict; | ||
use warnings; | ||
no if $] >= 5.017011, warnings => 'experimental::smartmatch'; | ||
use Net::LDAPS; | ||
use Net::LDAP::Entry; | ||
use Net::LDAP::Message; | ||
use Net::LDAP::LDIF; | ||
|
||
# Import shared AD library | ||
use ADConnector; | ||
use ScriptLock; | ||
|
||
sub process_update; | ||
|
||
# Constants and counters | ||
my $RESULT_ERRORS = "errors"; | ||
my $RESULT_CHANGED = "changed"; | ||
my $counter_group_members_updated = 0; | ||
my $counter_group_members_updated_with_errors = 0; | ||
my $counter_group_members_not_updated = 0; | ||
|
||
# define service | ||
my $service_name = "ad_group_vsup_o365"; | ||
|
||
# GEN folder location | ||
my $facility_name = $ARGV[0]; | ||
chomp($facility_name); | ||
my $service_files_base_dir="../gen/spool"; | ||
my $service_files_dir="$service_files_base_dir/$facility_name/$service_name"; | ||
|
||
# BASE DN | ||
open my $file, '<', "$service_files_dir/baseDN"; | ||
my $base_dn = <$file>; | ||
chomp($base_dn); | ||
close $file; | ||
|
||
# propagation destination | ||
my $namespace = $ARGV[1]; | ||
chomp($namespace); | ||
|
||
# create service lock | ||
my $lock = ScriptLock->new($facility_name . "_" . $service_name . "_" . $namespace); | ||
($lock->lock() == 1) or die "Unable to get lock, service propagation was already running."; | ||
|
||
# init configuration | ||
my @conf = init_config($namespace); | ||
my $ldap_location = resolve_pdc($conf[0]); | ||
my $ldap = ldap_connect($ldap_location); | ||
my $filter = '(objectClass=group)'; | ||
|
||
# connect | ||
ldap_bind($ldap, $conf[1], $conf[2]); | ||
|
||
# load all data | ||
my @perun_entries = load_perun($service_files_dir . "/" . $service_name . ".ldif"); | ||
my %perun_entries_map = (); | ||
|
||
foreach my $perun_entry (@perun_entries) { | ||
my $cn = $perun_entry->get_value('cn'); | ||
$perun_entries_map{ $cn } = $perun_entry; | ||
} | ||
|
||
# process data | ||
process_update(); | ||
|
||
# disconnect | ||
ldap_unbind($ldap); | ||
|
||
# log results | ||
ldap_log($service_name, "Group updated (members): " . $counter_group_members_updated . " entries."); | ||
ldap_log($service_name, "Group updated with errors (members): " . $counter_group_members_updated_with_errors . " entries."); | ||
ldap_log($service_name, "Group failed to update (members): " . $counter_group_members_not_updated. " entries."); | ||
|
||
# print results for TaskResults in GUI | ||
print "Group updated (members): " . $counter_group_members_updated . " entries.\n"; | ||
print "Group updated with errors (members): " . $counter_group_members_updated_with_errors . " entries.\n"; | ||
print "Group failed to update (members): " . $counter_group_members_not_updated. " entries.\n"; | ||
|
||
$lock->unlock(); | ||
|
||
if ($counter_group_members_updated_with_errors > 0) { die "Failed to process: " . $counter_group_members_updated_with_errors . " entries.\nSee log at: ~/perun-engine/send/logs/$service_name.log";} | ||
|
||
# END of main script | ||
|
||
|
||
# | ||
# Update group members in AD | ||
# | ||
sub process_update() { | ||
|
||
foreach my $perun_entry (@perun_entries) { | ||
|
||
my @per_val = $perun_entry->get_value('member'); | ||
|
||
# load members of a group from AD based on DN in Perun => Group must exists in AD | ||
my @ad_val = load_group_members($ldap, $perun_entry->dn(), $filter); | ||
|
||
if ($? != 0) { | ||
ldap_log($service_name, "Unable to load Perun group members from AD: " . $perun_entry->dn()); | ||
next; | ||
} | ||
|
||
# sort to compare | ||
my @sorted_ad_val = sort(@ad_val); | ||
my @sorted_per_val = sort(@per_val); | ||
|
||
# compare using smart-match (perl 5.10.1+) | ||
unless(@sorted_ad_val ~~ @sorted_per_val) { | ||
|
||
my %ad_val_map = map { $_ => 1 } @sorted_ad_val; | ||
my %per_val_map = map { $_ => 1 } @sorted_per_val; | ||
|
||
# members of group are not equals | ||
# we must get reference to real group from AD in order to call "replace" | ||
my $response_ad = $ldap->search( base => $perun_entry->dn(), filter => $filter, scope => 'base' ); | ||
unless ($response_ad->is_error()) { | ||
# SUCCESS | ||
my $ad_entry = $response_ad->entry(0); | ||
my $result = update_group_membership($ldap, $service_name, $ad_entry, \%ad_val_map, \%per_val_map); | ||
$counter_group_members_updated++ if ($result eq $RESULT_CHANGED); | ||
$counter_group_members_updated_with_errors++ if ($result eq $RESULT_ERRORS); | ||
} else { | ||
# FAIL (to get group from AD) | ||
$counter_group_members_not_updated++; | ||
ldap_log($service_name, "Group members NOT updated: " . $perun_entry->dn() . " | " . $response_ad->error()); | ||
} | ||
} | ||
|
||
# group is unchanged | ||
|
||
} | ||
|
||
} |