From 131326fee4af78e0693775206d4776cef4a4502c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Sassmann?= Date: Thu, 27 May 2021 15:28:41 +0200 Subject: [PATCH] feat: new script ad_group_vsup_o365 * Created new script ad_group_vsup_o365 which respects vsup's expiration. --- gen/ad_group_vsup_o365 | 161 ++++++++++++++++++++++++++++++++++++++++ send/ad_group_vsup_o365 | 134 +++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100755 gen/ad_group_vsup_o365 create mode 100755 send/ad_group_vsup_o365 diff --git a/gen/ad_group_vsup_o365 b/gen/ad_group_vsup_o365 new file mode 100755 index 00000000..cecf9dc5 --- /dev/null +++ b/gen/ad_group_vsup_o365 @@ -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; +} diff --git a/send/ad_group_vsup_o365 b/send/ad_group_vsup_o365 new file mode 100755 index 00000000..2f41af72 --- /dev/null +++ b/send/ad_group_vsup_o365 @@ -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 + + } + +}