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

sub escapeDnValue;

local $::SERVICE_NAME = "ad_ceitec";
local $::PROTOCOL_VERSION = "3.0.0";
my $SCRIPT_VERSION = "3.0.2";

perunServicesInit::init;
my $DIRECTORY = perunServicesInit::getDirectory;
my $fileName = "$DIRECTORY/$::SERVICE_NAME".".ldif";
my $fileNameGroups = "$DIRECTORY/$::SERVICE_NAME"."_groups.ldif";
my $baseDnFileName = "$DIRECTORY/baseDN";
my $baseDnFileNameService = "$DIRECTORY/baseDNService";
my $baseDnFileNameGroups = "$DIRECTORY/baseDNGroups";

my $data = perunServicesInit::getHashedHierarchicalData;

# Constants
our $A_F_BASE_DN;  *A_F_BASE_DN = \'urn:perun:facility:attribute-def:def:adBaseDN'; # previously f:d:ldapBaseDN
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_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_F_UAC_SERV;  *A_F_UAC_SERV = \'urn:perun:facility:attribute-def:def:adServiceUAC';
# Resources = Groups
our $A_R_GROUP_NAME;  *A_R_GROUP_NAME = \'urn:perun:resource:attribute-def:def:adGroupName'; # previously r:d:secGroupCnCeitecAD

# User attributes
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_MAIL;  *A_MAIL = \'urn:perun:user:attribute-def:def:preferredMail';
our $A_EPPNS; *A_EPPNS = \'urn:perun:user:attribute-def:virt:eduPersonPrincipalNames';
our $A_O; *A_O = \'urn:perun:member:attribute-def:def:organization';
our $A_LOGIN; *A_LOGIN = \'urn:perun:user:attribute-def:def:login-namespace:ceitec';
our $A_CN; *A_CN = \'urn:perun:user:attribute-def:def:cnCeitecAD';
our $A_IS_SERVICE; *A_IS_SERVICE = \'urn:perun:user:attribute-def:core:serviceUser';
our $A_M_STATUS; *A_M_STATUS = \'urn:perun:member:attribute-def:core:status';
our $STATUS_VALID;                   *STATUS_VALID =                   \'VALID';

# GATHER USERS
my $users;  # $users->{$login}->{ATTR} = $attrValue;
# GATHER USERS FROM RESOURCES
my $usersByResource;  # $usersByResource->{$A_R_GROUP_NAME}->{users DN} = 1

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

#
# PRINT BASE_DN FILEs
#
my $baseDNUsers = $data->getFacilityAttributeValue( attrName => $A_F_BASE_DN );
my $baseDNService = $data->getFacilityAttributeValue( attrName => $A_F_SERV_BASE_DN );
my $baseDNGroups = $data->getFacilityAttributeValue( attrName => $A_F_GROUP_BASE_DN );
my $domain = $data->getFacilityAttributeValue( attrName => $A_F_DOMAIN );
my $adUac = $data->getFacilityAttributeValue( attrName => $A_F_UAC );
my $adUacService = $data->getFacilityAttributeValue( attrName => $A_F_UAC_SERV );

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

open FILE,">:encoding(UTF-8)","$baseDnFileNameService" or die "Cannot open $baseDnFileNameService: $! \n";
print FILE $baseDNService;
close(FILE);

open FILE,">:encoding(UTF-8)","$baseDnFileNameGroups" or die "Cannot open $baseDnFileNameGroups: $! \n";
print FILE $baseDNGroups;
close(FILE);


# FOR EACH MEMBER IN FACILITY
foreach my $resourceId ($data->getResourceIds()) {
	foreach my $memberId ($data->getMemberIdsForResource(resource => $resourceId)) {
		my $login = $data->getUserAttributeValue(member => $memberId, attrName => $A_LOGIN);
		my $cn = $data->getUserAttributeValue(member => $memberId, attrName => $A_CN);
		my $memberStatus = $data->getMemberAttributeValue(member => $memberId, attrName => $A_M_STATUS);
		my $serviceUser = $data->getUserAttributeValue(member => $memberId, attrName => $A_IS_SERVICE);

		unless ($cn && $login && ($memberStatus eq $STATUS_VALID)) {
			# skip users without CN or login and all not-valid members
			next;
		}

		# store flat user structure
		$users->{$login}->{$A_FIRST_NAME} = $data->getUserAttributeValue(member => $memberId, attrName => $A_FIRST_NAME);
		$users->{$login}->{$A_LAST_NAME} = $data->getUserAttributeValue(member => $memberId, attrName => $A_LAST_NAME);
		$users->{$login}->{$A_MAIL} = $data->getUserAttributeValue(member => $memberId, attrName => $A_MAIL);
		$users->{$login}->{$A_EPPNS} = $data->getUserAttributeValue(member => $memberId, attrName => $A_EPPNS);
		$users->{$login}->{$A_O} = $data->getMemberAttributeValue(member => $memberId, attrName => $A_O);
		$users->{$login}->{$A_CN} = $cn;
		$users->{$login}->{$A_IS_SERVICE} = $serviceUser;

		# store which users (their DN) are on this resource
		my $groupName = $data->getResourceAttributeValue(resource => $resourceId, attrName => $A_R_GROUP_NAME);

		if (defined $serviceUser and ($serviceUser == 1)) {
			# service users
			$users->{$login}->{"DN"} = "cn=" . escapeDnValue($cn) . "," . $baseDNService;
			$usersByResource->{$groupName}->{"CN=" . escapeDnValue($cn) . "," . $baseDNService} = 1
		}
		else {
			# normal users
			$users->{$login}->{"DN"} = "cn=" . escapeDnValue($cn) . "," . $baseDNUsers;
			$usersByResource->{$groupName}->{"CN=" . escapeDnValue($cn) . "," . $baseDNUsers} = 1
		}

	}

}

#
# Print group data LDIF
#
open FILE,">:encoding(UTF-8)","$fileNameGroups" or die "Cannot open $fileNameGroups: $! \n";

my @groups = sort keys %{$usersByResource};
for my $group (@groups) {

	print FILE "dn: CN=" . $group . "," . $baseDNGroups . "\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;

#
# PRINT user data LDIF
#
open FILE,">:encoding(UTF-8)","$fileName" or die "Cannot open $fileName: $! \n";

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

	my $cn = "$users->{$login}->{$A_CN}";

	# Localy defined attributes
	my $userPrincipalName = "$login\@$domain";
	my $samAccountName = $login;
	my $displayName = "$users->{$login}->{$A_LAST_NAME} $users->{$login}->{$A_FIRST_NAME}";

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

	# skip attributes which are empty and LDAP can't handle it (FIRST_NAME, EMAIL)
	my $sn = $users->{$login}->{$A_LAST_NAME};
	my $givenName = $users->{$login}->{$A_FIRST_NAME};
	my $mail = $users->{$login}->{$A_MAIL};
	my $eppns = $users->{$login}->{$A_EPPNS};
	my $o = $users->{$login}->{$A_O};
	my $service = $users->{$login}->{$A_IS_SERVICE};

	if (defined $displayName and length $displayName) {
		print FILE "displayName: " . $displayName . "\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 $mail and length $mail) {
		print FILE "mail: " . $mail . "\n";
	}
	if (defined $o and length $o) {
		print FILE "company: " . $o . "\n";
	}
	if (defined $samAccountName and length $samAccountName) {
		print FILE "samAccountName: " . $samAccountName . "\n";
	}
	if (defined $userPrincipalName and length $userPrincipalName) {
		print FILE "userPrincipalName: " . $userPrincipalName . "\n";
	}
	foreach my $val (@$eppns) {
		print FILE "eduPersonPrincipalNames: " . $val . "\n";
	}

	# Set userAccountControl to 66048 which means Normal account + Password never expires + enabled
	if (defined $service and ($service == 1)) {
		print FILE "userAccountControl: $adUacService\n";
	} else {
		print FILE "userAccountControl: $adUac\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;


# method looks for specific characters/symbols in the scalar $value
# and escapes each of them using backslash
sub escapeDnValue {
	my $value = shift;

	if (defined($value)){
		# escape one of the characters inside the string: ",", "+", """, "\", "<", ">" or ";"
		$value =~ s/[,+"\\><;]/\\${^MATCH}/pg;

		# escape a whitespace or "#" character occurring at the beginning of the string
		$value =~ s/^\s|^#/\\${^MATCH}/pg;

		# escape a whitespace character occurring at the end of the string
		$value =~ s/\s$/\\ /g;
	}

	return $value;
}
