Skip to content

Commit

Permalink
Merge pull request #601 from Vojtech-Sassmann/vsupAdExpired
Browse files Browse the repository at this point in the history
feat: new script ad_group_vsup_o365
  • Loading branch information
zlamalp authored May 28, 2021
2 parents abd5926 + 131326f commit bb81c0f
Show file tree
Hide file tree
Showing 2 changed files with 295 additions and 0 deletions.
161 changes: 161 additions & 0 deletions gen/ad_group_vsup_o365
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;
}
134 changes: 134 additions & 0 deletions send/ad_group_vsup_o365
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

}

}

0 comments on commit bb81c0f

Please sign in to comment.