#!/usr/bin/perl
use feature "switch";
use strict;
use warnings;
use perunServicesInit;
use perunServicesUtils;
use Time::Piece;

sub calculateExpiration;

local $::SERVICE_NAME = "ad_user_vsup";
local $::PROTOCOL_VERSION = "3.0.1";
my $SCRIPT_VERSION = "3.0.8";

perunServicesInit::init;
my $DIRECTORY = perunServicesInit::getDirectory;
my $fileName = "$DIRECTORY/$::SERVICE_NAME".".ldif";
my $baseDnFileName = "$DIRECTORY/baseDN";
my $topDnFileName = "$DIRECTORY/topDN";

my $data = perunServicesInit::getHashedHierarchicalData;

#Constants
our $A_F_BASE_DN;  *A_F_BASE_DN = \'urn:perun:facility:attribute-def:def:adBaseDN';
our $A_F_DOMAIN;  *A_F_DOMAIN = \'urn:perun:facility:attribute-def:def:adDomain';
our $A_F_UAC;  *A_F_UAC = \'urn:perun:facility:attribute-def:def:adUAC';
our $A_FIRST_NAME;  *A_FIRST_NAME = \'urn:perun:user:attribute-def:core:firstName';
our $A_LAST_NAME;  *A_LAST_NAME = \'urn:perun:user:attribute-def:core:lastName';
our $A_ARTISTIC_FIRST_NAME; *A_ARTISTIC_FIRST_NAME = \'urn:perun:user:attribute-def:def:artisticFirstName';
our $A_ARTISTIC_LAST_NAME; *A_ARTISTIC_LAST_NAME = \'urn:perun:user:attribute-def:def:artisticLastName';
our $A_LOGIN; *A_LOGIN = \'urn:perun:user_facility:attribute-def:virt:login';
our $A_BLACKLISTED;  *A_BLACKLISTED = \'urn:perun:user_facility:attribute-def:virt:blacklisted';
our $A_IS_SERVICE; *A_IS_SERVICE = \'urn:perun:user:attribute-def:core:serviceUser';
our $A_UCO; *A_UCO= \'urn:perun:user:attribute-def:def:ucoVsup';
our $A_TITLE_BEFORE;  *A_TITLE_BEFORE = \'urn:perun:user:attribute-def:core:titleBefore';
our $A_TITLE_AFTER;  *A_TITLE_AFTER = \'urn:perun:user:attribute-def:core:titleAfter';
our $A_PHONE;  *A_PHONE = \'urn:perun:user:attribute-def:def:phoneDc2';
our $A_CARD_BARCODES;  *A_CARD_BARCODES = \'urn:perun:user:attribute-def:def:cardBarCodes';
our $A_CARD_CHIP_NUMBERS;  *A_CARD_CHIP_NUMBERS = \'urn:perun:user:attribute-def:def:cardCodes';
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';
our $A_VSUP_PREF_MAIL;  *A_VSUP_PREF_MAIL = \'urn:perun:user:attribute-def:def:vsupPreferredMail';
our $A_VSUP_SSH_KEYS;  *A_VSUP_SSH_KEYS = \'urn:perun:user:attribute-def:def:sshPublicKey';
our $A_VSUP_EXCHANGE_MAIL_ALIASES;  *A_VSUP_EXCHANGE_MAIL_ALIASES = \'urn:perun:user:attribute-def:def:vsupExchangeMailAliases';
our $A_VSUP_EXCHANGE_MAIL;  *A_VSUP_EXCHANGE_MAIL = \'urn:perun:user:attribute-def:def:vsupExchangeMail';

# CHECK ON FACILITY ATTRIBUTES
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_BASE_DN ))) {
	exit 1;
}
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_DOMAIN ))) {
	exit 1;
}
if (!defined($data->getFacilityAttributeValue( attrName => $A_F_UAC ))) {
	exit 1;
}

my $baseDN = $data->getFacilityAttributeValue( attrName => $A_F_BASE_DN );
my $domain = $data->getFacilityAttributeValue( attrName => $A_F_DOMAIN );
my $uac = $data->getFacilityAttributeValue( attrName => $A_F_UAC );

# GATHER USERS
my $users;  # $users->{$login}->{ATTR} = $attrValue;

#
# PRINT BASE_DN FILE
#
open FILE,">:encoding(UTF-8)","$baseDnFileName" or die "Cannot open $baseDnFileName: $! \n";
print FILE $baseDN;
close(FILE);

#
# PRINT TOP_DN FILE
#
open FILE,">:encoding(UTF-8)","$topDnFileName" or die "Cannot open $topDnFileName: $! \n";
$baseDN =~ m/(DC=(.*))/;
print FILE $1;
close(FILE);

#
# AGGREGATE DATA
#
# FOR EACH USER
foreach my $memberId ($data->getMemberIdsForFacility()) {

	my $blacklisted = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_BLACKLISTED );

	if (defined $blacklisted and ($blacklisted == 1)) {
		# skip blacklisted users !security ban!
		next;
	}

	my $serviceUser = $data->getUserAttributeValue( member => $memberId, attrName => $A_IS_SERVICE );

	# Print only standard user accounts
	unless (defined $serviceUser and ($serviceUser == 1)) {

		my $login = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_LOGIN );
		my $artisticFirstName = $data->getUserAttributeValue( member => $memberId, attrName => $A_ARTISTIC_FIRST_NAME );
		my $firstName = $data->getUserAttributeValue( member => $memberId, attrName => $A_FIRST_NAME );
		my $artisticLastName = $data->getUserAttributeValue( member => $memberId, attrName => $A_ARTISTIC_LAST_NAME );
		my $lastName = $data->getUserAttributeValue( member => $memberId, attrName => $A_LAST_NAME );

		$users->{$login}->{"DN"} = "CN=".$login.",".$baseDN;
		# store standard attrs
		$users->{$login}->{$A_FIRST_NAME} = $artisticFirstName || $firstName;
		$users->{$login}->{$A_LAST_NAME} = $artisticLastName || $lastName;
		$users->{$login}->{$A_UCO} = $data->getUserAttributeValue( member => $memberId, attrName => $A_UCO );
		$users->{$login}->{$A_TITLE_BEFORE} = $data->getUserAttributeValue( member => $memberId, attrName => $A_TITLE_BEFORE );
		$users->{$login}->{$A_TITLE_AFTER} = $data->getUserAttributeValue( member => $memberId, attrName => $A_TITLE_AFTER );
		$users->{$login}->{$A_PHONE} = $data->getUserAttributeValue( member => $memberId, attrName => $A_PHONE );
		$users->{$login}->{$A_CARD_BARCODES} = $data->getUserAttributeValue( member => $memberId, attrName => $A_CARD_BARCODES );
		$users->{$login}->{$A_CARD_CHIP_NUMBERS} = $data->getUserAttributeValue( member => $memberId, attrName => $A_CARD_CHIP_NUMBERS );
		$users->{$login}->{$A_EXPIRATION_KOS} = $data->getUserAttributeValue( member => $memberId, attrName => $A_EXPIRATION_KOS );
		$users->{$login}->{$A_EXPIRATION_DC2} = $data->getUserAttributeValue( member => $memberId, attrName => $A_EXPIRATION_DC2 );
		$users->{$login}->{$A_EXPIRATION_MANUAL} = $data->getUserAttributeValue( member => $memberId, attrName => $A_EXPIRATION_MANUAL );
		$users->{$login}->{$A_VSUP_PREF_MAIL} = $data->getUserAttributeValue( member => $memberId, attrName => $A_VSUP_PREF_MAIL );
		$users->{$login}->{$A_VSUP_SSH_KEYS} = $data->getUserAttributeValue( member => $memberId, attrName => $A_VSUP_SSH_KEYS );
		$users->{$login}->{$A_VSUP_EXCHANGE_MAIL_ALIASES} = $data->getUserAttributeValue( member => $memberId, attrName => $A_VSUP_EXCHANGE_MAIL_ALIASES );
		$users->{$login}->{$A_VSUP_EXCHANGE_MAIL} = $data->getUserAttributeValue( member => $memberId, attrName => $A_VSUP_EXCHANGE_MAIL );

	}

}

#
# PRINT user data LDIF
#
open FILE,">$fileName" or die "Cannot open $fileName: $! \n";
binmode FILE, ":utf8";

# FOR EACH USER ON FACILITY
my @logins = sort keys %{$users};
for my $login (@logins) {

	# print attributes, which are never empty
	print FILE "dn: " . $users->{$login}->{"DN"} . "\n";

	print FILE "cn: " . $login . "\n";
	print FILE "samAccountName: " . $login . "\n";
	print FILE "userPrincipalName: " . $login . "\@umprum.cz" . "\n";
	# enable accounts (if not) using service propagation
	print FILE "userAccountControl: " . $uac . "\n";

	my $expiration = calculateExpiration($users->{$login}->{$A_EXPIRATION_KOS}, $users->{$login}->{$A_EXPIRATION_DC2}, $users->{$login}->{$A_EXPIRATION_MANUAL});
	print FILE "accountExpires: " . $expiration . "\n";

	# skip attributes which are empty and LDAP can't handle it (FIRST_NAME,...)
	my $sn = $users->{$login}->{$A_LAST_NAME};
	my $givenName = $users->{$login}->{$A_FIRST_NAME};
	my $uco = $users->{$login}->{$A_UCO};
	my $titleBefore = $users->{$login}->{$A_TITLE_BEFORE};
	my $titleAfter = $users->{$login}->{$A_TITLE_AFTER};
	my $phone = $users->{$login}->{$A_PHONE};
	my $barcodes = $users->{$login}->{$A_CARD_BARCODES};
	my $chipNumbers = $users->{$login}->{$A_CARD_CHIP_NUMBERS};
	my $vsupPrefMail = $users->{$login}->{$A_VSUP_PREF_MAIL};
	my $sshKeys = $users->{$login}->{$A_VSUP_SSH_KEYS};
	my $exchangeMailAliases = $users->{$login}->{$A_VSUP_EXCHANGE_MAIL_ALIASES};
	my $exchangeMail = $users->{$login}->{$A_VSUP_EXCHANGE_MAIL};

	# print display name from firstName/lastName only
	my $printedDisplayName = undef;
	if (defined $givenName and length $givenName and defined $sn and length $sn) {
		$printedDisplayName = $givenName . " " . $sn;
	} elsif (defined $givenName and length $givenName and !(defined $sn and length $sn)) {
		$printedDisplayName = $givenName;
	} elsif (!(defined $givenName and length $givenName) and defined $sn and length $sn) {
		$printedDisplayName = $sn;
	}
	if (defined $printedDisplayName and length $printedDisplayName) {
		print FILE "displayName: " . $printedDisplayName . "\n";
		print FILE "gecos: " . $printedDisplayName . "\n";
	}

	if (defined $sn and length $sn) {
		print FILE "sn: " . $sn . "\n";
	}
	if (defined $givenName and length $givenName) {
		print FILE "givenName: " . $givenName . "\n";
	}

	if (defined $vsupPrefMail and length $vsupPrefMail) {
		print FILE "mail: " . $vsupPrefMail . "\n";
	} elsif (defined $exchangeMail and length $exchangeMail) {
		print FILE "mail: " . $exchangeMail . "\n";
	} else {
		print FILE "mail: " . $login . "\@vsup.cz" . "\n";
	}

	if (defined $uco and length $uco) {
		print FILE "vsupPersonPersonalId: " . $uco . "\n";
	}

	if (defined $titleBefore and length $titleBefore) {
		print FILE "vsupPersonTitleHead: " . $titleBefore . "\n";
	}

	if (defined $titleAfter and length $titleAfter) {
		print FILE "vsupPersonTitleTail: " . $titleAfter . "\n";
	}

	if (defined $phone and length $phone) {
		print FILE "telephoneNumber: " . $phone . "\n";
	}

	foreach my $val (@$barcodes) {
		print FILE "vsupPersonIdCardBarcode: " . $val . "\n";
	}

	foreach my $val (@$chipNumbers) {
		print FILE "vsupPersonIdCardChipNumber: " . $val . "\n";
	}

	foreach my $val (@$sshKeys) {
		print FILE "altSecurityIdentities: " . $val . "\n";
	}

	# primary mail must be in proxyAddresses for normal users
	if (defined $exchangeMail and length $exchangeMail) {
		print FILE "proxyAddresses: SMTP:" . $exchangeMail . "\n";
	}
	foreach my $val (@$exchangeMailAliases) {
		print FILE "proxyAddresses: smtp:" . $val . "\n";
	}

	# print classes
	print FILE "objectclass: top\n";
	print FILE "objectclass: person\n";
	print FILE "objectclass: user\n";
	print FILE "objectclass: organizationalPerson\n";

	# There MUST be an empty line after each entry, so entry sorting and diff works on slave part
	print FILE "\n";

}

close(FILE);

perunServicesInit::finalize;

#
# Calculate later from three expiration dates based on users relations on VŠUP.
#
# 1. param - expiration in KOS (studies)
# 2. param - expiration in DC2 (employees)
# 3. param - manually set expiration
#
# Returns Windows FILETIME timestamp of users account expiration
# - in case of expiration on 1.1.4000 -> Zero is returned as "expiration = never".
# - in case of any other exact date, pick the largest (future). If it comes from study system (KOS),
#   add 21 days grace period.
#
sub calculateExpiration() {

	# 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 $result;

	if (!defined $expirationKos and !defined $expirationDc2 and !defined $expirationMan) {
		# if no expiration set in source data - take as "never"
		return 0;
	}

	# case expiration "never" = 1.1.4000
	if ($latest_expiration == Time::Piece->strptime("4000-01-01","%Y-%m-%d")->epoch) {
		# return without specified expiration date
		return 0;
	}

	# (will) expire by studies - add 21 days grace period
	if ($expirationKosTime and ($latest_expiration == $expirationKosTime->epoch)) {
		$result = $latest_expiration + (21*24*60*60);
	} else {
		# Expired by employment or manual - push exact date
		$result = $latest_expiration;
	}

	# Add time 23:59:59 to the date, since we want accounts to be active on the last day
	$result = $result + 86399;

	# add seconds before 1.1.1970 since Windows epoch starts at 1601-01-01T00:00:00Z
	$result = $result + 11644473600;
	# convert seconds to 100-nano seconds (hundreds of nano seconds)
	$result = $result * 10000000;

	return $result;

}
