map LADP/ActiveDirectory groups into OTRS groups for customers

Dont create your support topics here! No new topics with questions allowed!

Moderator: crythias

Forum rules
Dont create your support topics here! No new topics with questions allowed!
Post Reply
Giulio Soleni
Znuny wizard
Posts: 392
Joined: 30 Dec 2010, 14:35
Znuny Version: 6.0.x and 5.0.x
Real Name: Giulio Soleni
Company: IKS srl

map LADP/ActiveDirectory groups into OTRS groups for customers

Post by Giulio Soleni »

Hi,
I needed a map between LDAP groups of users and OTRS groups for customers.
This feature is not available out of the box as it is for agents (after a configuration of Config.pm file), therefore I wrote down the following script:
/opt/otrs/bin/myotrs.AddLDAPCustomer2Group.pl

Code: Select all

#!/usr/bin/perl
# --
# bin/myotrs.AddLDAPCustomer2Group.pl - Sync LDAP users belonging to specific LDAP groups to OTRS customer users on specific OTRS groups
# version: 0.8
#
# USAGE bin/myotrs.AddLDAPCustomer2Group.pl --map <LDAP2OTRS.ini> [--simulate]
#
# <LDAP2OTRS.ini> is a text file with the following format: LDAP-Group|OTRS-Group|Permission
# 
# it is possible to assign many OTRS groups to the same LDAP group
# LDAP groups must be specified with the Distinguished Name (DN) notation (see RFC-Specification RFC 4514)
# Permissions can be either ro or rw
# - For all users in LDAP groups that are also OTRS Customer users the permissions for OTRS groups specified within LDAP2OTRS.ini file will be applied
# - For all users NOT in LDAP groups that are OTRS Customer users the permissions for OTRS groups specified within LDAP2OTRS.ini file will be revoked
# - The permissions related to any other OTRS group NOT specified within LDAP2OTRS.ini file will be left untouched
#
# if the --simulate parameter is specified, no actual operation is performed on OTRS
#
# --
use strict;
use warnings;

use Getopt::Long qw(GetOptions);
Getopt::Long::Configure qw(gnu_getopt);

use Net::LDAP;
use File::Basename;

use FindBin qw($RealBin);
use lib dirname($RealBin);
use lib dirname($RealBin) . '/Kernel/cpan-lib';
use lib dirname($RealBin) . '/Custom';

use Kernel::Config;
use Kernel::System::Encode;
use Kernel::System::Log;
use Kernel::System::Time;
use Kernel::System::DB;
use Kernel::System::Main;
use Kernel::System::CustomerUser;
use Kernel::System::CustomerGroup;

my $simulate;
my $filename;
GetOptions(
	'simulate|s' => \$simulate, # simulation mode, no actual operation will be performed on OTRS
	'map|m=s' => \$filename, # input file, each line has the format: ldapGrp,otrsGrp,permission
	) or die "USAGE: $0 --map <LDAP2OTRS.ini> [--simulate]\n";

if (!defined($filename)) {
        print STDERR "USAGE: $0 --map <LDAP2OTRS.ini> [--simulate]\n";
        exit;
}

my %CommonObject;

# create common objects
$CommonObject{ConfigObject} = Kernel::Config->new(%CommonObject);
$CommonObject{EncodeObject} = Kernel::System::Encode->new(%CommonObject);
$CommonObject{LogObject}    = Kernel::System::Log->new(
	LogPrefix => 'myotrs.AddLDAPCustomer2Group',
	%CommonObject,
);
$CommonObject{TimeObject}          = Kernel::System::Time->new(%CommonObject);
$CommonObject{MainObject}          = Kernel::System::Main->new(%CommonObject);
$CommonObject{DBObject}            = Kernel::System::DB->new(%CommonObject);
$CommonObject{CustomerUserObject}  = Kernel::System::CustomerUser->new(%CommonObject);
$CommonObject{CustomerGroupObject} = Kernel::System::CustomerGroup->new(%CommonObject);

my %LDAP2OTRS; # hashtable of hashtables $LDAP2OTRS{ldapGrp}{otrsGrp}=rw|ro

# CONSTANTS
# LDAP Server
my $LDAP_SERVER = \"ldap-server.mylocal.domain";
# LDAP Base Domain
my $LDAP_BASE_DOMAIN=\"DC=mylocal,DC=domain";
# technical user to browse LDAP
my $UID = \"CN=otrs,OU=My Technical Users,DC=mylocal,DC=domain";
my $BIND_PWD = \"Password123";

# connect to LDAP server
my $ldap = Net::LDAP -> new ($$LDAP_SERVER) || die "ERROR: Could not connect to LDAP server\n";

# bind to LDAP server
$ldap -> bind($$UID, password => $$BIND_PWD);

# list attributes to be queried
my @Attrs = ('memberOf','sAMAccountName','userAccountControl');

# open $filename for read (if it exists)
my $fh;
if ( -e $filename ) {
	open $fh, '<', $filename or die "ERROR: Cannot open $filename: $!";
}
else {
	print STDERR "ERROR: $filename does not exist!\n";
	exit;
}

# composition of %LDAP2OTRS hashtable
while ( my $line = <$fh> ) {
	chomp $line;
	$line =~ s/\#.*//;  # skipping comment lines
	$line =~ s/\#$//;   # skipping each # character at end of line
	$line =~ s/^\s+//;  # skipping starting blank
	$line =~ s/\s+$//;  # skipping ending blank
	next unless length $line; # if something useful is left...
	my @row = split( /\|/, $line );
	$LDAP2OTRS{$row[0]}{$row[1]}=$row[2];
}

# close $filename
close($fh);

# DEBUG START
# read %LDAP2OTRS hashtable
#for my $ldapGrp ( keys %LDAP2OTRS ) {
#	print "$ldapGrp: \n";
#	for my $otrsGrp ( keys %{ $LDAP2OTRS{$ldapGrp} } ) {
#		print "$otrsGrp=$LDAP2OTRS{$ldapGrp}{$otrsGrp} ";
#	}
#	print "\n";
#}
# DEBUG END

my %OTRSCustomerList = $CommonObject{CustomerUserObject}->CustomerSearch(
	UserLogin => '*',
	Valid     => 1, # not required, default 1
);

# ASSIGNING PERMISSIONS
for my $ldapGrp ( keys %LDAP2OTRS ) {
	print "Searching users within $ldapGrp ...\n";

	# search for users in $ldapGrp
	# see https://technet.microsoft.com/it-it/library/aa996205%28v=exchg.65%29.aspx for details
#	my $result = &LDAPsearch ( $ldap, "(&(userAccountControl=512) (objectCategory=user) (memberOf=$ldapGrp))", \@Attrs, "$$LDAP_BASE_DOMAIN" );
#	my $result = &LDAPsearch ( $ldap, "(&(objectCategory=user) (memberOf=$ldapGrp))", \@Attrs, "$$LDAP_BASE_DOMAIN" );
	my $result = &LDAPsearch ( $ldap, "(&(objectCategory=user)(memberOf=$ldapGrp)(|(userAccountControl=512)(userAccountControl=544)(userAccountControl=66048)(userAccountControl=66080)))", \@Attrs, "$$LDAP_BASE_DOMAIN" );

	# Use the following table as a reference for userAccountControl values
	# 512........Enabled Account
	# 514........Disabled Account
	# 544........Enabled, Password Not Required
	# 546........Disabled, Password Not Required
	# 66048......Enabled, Password Doesn't Expire
	# 66050......Disabled, Password Doesn't Expire
	# 66080......Enabled, Password Doesn't Expire & Not Required
	# 66082......Disabled, Password Doesn't Expire & Not Required
	# 262656.....Enabled, Smartcard Required
	# 262658.....Disabled, Smartcard Required
	# 262688.....Enabled, Smartcard Required, Password Not Required
	# 262690.....Disabled, Smartcard Required, Password Not Required
	# 328192.....Enabled, Smartcard Required, Password Doesn't Expire
	# 328194.....Disabled, Smartcard Required, Password Doesn't Expire
	# 328224.....Enabled, Smartcard Required, Password Doesn't Expire & Not Required
	# 328226.....Disabled, Smartcard Required, Password Doesn't Expire & Not Required

	# get entries from result object
	my @entries = $result->entries;
	if (scalar(@entries) >0) {
		foreach my $entr ( @entries ) {
			my $thisUser = $entr->get_value('sAMAccountName');
			for my $otrsGrp ( keys %{ $LDAP2OTRS{$ldapGrp} } ) {
				my $thisPermission = lc $LDAP2OTRS{$ldapGrp}{$otrsGrp};
				# check if $thisPermission is correct
				if ($thisPermission !~ /^r[ow]$/) {
					print STDERR "ERROR: Wrong permission: '$thisPermission' is NOT a standard permission.\n";
					next;
				}
				# check if $otrsGrp is an OTRS group
				my $CheckGroupID = $CommonObject{CustomerGroupObject}->GroupLookup(
					Group => $otrsGrp,
				);
				if ( !$CheckGroupID ) {
					print STDERR "ERROR: Failed to get Group ID: '$otrsGrp' is NOT an OTRS group.\n";
					next;
				}
				# check if $thisUser is an OTRS customer
				my $CheckCustomerName = $CommonObject{CustomerUserObject}->CustomerName(
					UserLogin => $thisUser,
				);
				if ( !$CheckCustomerName ) {
					print STDERR "ERROR: Failed to get Customer data: '$thisUser' is NOT an OTRS customer user.\n";
					next;
				}
				print "ASSIGNING $thisPermission PERMISSIONS TO $thisUser on $otrsGrp ...\n";
				unless ($simulate) {
					if ($thisPermission eq 'ro') {
						# revoking all permissions, beforehand (otherwise if $thisUser has already rw permissions on $otrsGrp, ro permission cannot be simply applied) 
						if ( !$CommonObject{CustomerGroupObject}->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { rw => 0, }, UserID => 1, ValidID => 1) ) {
							print STDERR "ERROR: Can't reset rw permission for $thisUser Customer on $otrsGrp group\n";
						}
						if ( !$CommonObject{CustomerGroupObject}->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { ro => 0, }, UserID => 1, ValidID => 1) ) {
							print STDERR "ERROR: Can't reset ro permission for $thisUser Customer on $otrsGrp group\n";
						}
					}
					# ...then setting $thisPermission
					if ( !$CommonObject{CustomerGroupObject}->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { $thisPermission => 1, }, UserID => 1, ValidID => 1) ) {
						print STDERR "ERROR: Can't set $thisPermission permission for $thisUser Customer on $otrsGrp group\n";
					}
				}
				delete $OTRSCustomerList{$thisUser}; # remove $thisUser from %OTRSCustomerList
			}
		}
	}
}

# REVOKING PERMISSIONS
for my $ldapGrp ( keys %LDAP2OTRS ) {
	for my $otrsGrp ( keys %{ $LDAP2OTRS{$ldapGrp} } ) {
		# check if $otrsGrp is an OTRS group
		my $CheckGroupID = $CommonObject{CustomerGroupObject}->GroupLookup(
			Group => $otrsGrp,
		);
		if ( !$CheckGroupID ) {
			print STDERR "ERROR: Failed to get Group ID: '$otrsGrp' is NOT an OTRS group.\n";
			next;
		}
		for my $thisUser ( keys %OTRSCustomerList ) {
			print "REVOKING ALL PERMISSIONS TO $thisUser on $otrsGrp ...\n";
			unless ($simulate) {
				# revoking all permissions...
				if ( !$CommonObject{CustomerGroupObject}->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { rw => 0, }, UserID => 1, ValidID => 1) ) {
					print STDERR "ERROR: Can't reset rw permission for $thisUser Customer on $otrsGrp group\n";
				}
				if ( !$CommonObject{CustomerGroupObject}->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { ro => 0, }, UserID => 1, ValidID => 1) ) {
					print STDERR "ERROR: Can't reset ro permission for $thisUser Customer on $otrsGrp group\n";
				}
			}
		}
	}
}

# unbind (disconnect) from server
$ldap->unbind;

sub LDAPsearch
{
	my ($ldap,$searchString,$attrs,$base) = @_;
	# if they don't pass a base... set it for them
	if (!$base ) { $base = "$$LDAP_BASE_DOMAIN"; }
	# if they don't pass an array of attributes...
	# set up something for them

#   if (!$attrs ) { $attrs = [ 'cn','type' ]; }
	if (!$attrs ) { $attrs = [ 'sAMAccountName' ]; }

	my $result = $ldap->search ( base    => "$base",
								 scope   => "sub",
								 filter  => "$searchString",
								 attrs   =>  $attrs
								);
	return $result;
}

Due to the fact that the script makes reference to some OTRS perl modules, it must be put within /opt/otrs/bin folder to work.
It must also make reference to a text configuration file where the map between LDAP groups and OTRS groups, along with the permissions to be assigned to the customers users belonging to the specified OTRS groups.
An example of this file that can be put wherever you would like is the following:
/tmp/LDAP2OTRS.ini

Code: Select all

CN=OTRS_TestUsers,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_X_grp|rw
CN=OTRS_TestUsers,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_Y_grp|ro
CN=OTRS_TestUsers,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_Z_grp|ro
CN=OTRS_CUSTOMERS1,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_X_grp|rw
CN=OTRS_CUSTOMERS1,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_Y_grp|ro
CN=OTRS_CUSTOMERS1,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_W_grp|ro
CN=OTRS_CUSTOMERS1,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_Z_grp|ro
CN=OTRS_CUSTOMERS2,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_X_grp|rw
CN=OTRS_CUSTOMERS2,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_Y_grp|ro
CN=OTRS_CUSTOMERS2,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_Z_grp|rw
CN=OTRS_CUSTOMERS2,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_J_grp|ro
CN=OTRS_CUSTOMERS2,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_W_grp|ro
CN=OTRS_TestUsers,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_X_grp|rw
CN=OTRS_FakeTestUsers,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_X_grp|rw
As specified in the header of the script itself:
it is possible to assign many OTRS groups to the same LDAP group
LDAP groups must be specified with the Distinguished Name (DN) notation (see RFC-Specification RFC 4514)
Permissions can be either ro or rw
- For all users in LDAP groups that are also OTRS Customer users the permissions for OTRS groups specified within LDAP2OTRS.ini file will be applied
- For all users NOT in LDAP groups that are OTRS Customer users the permissions for OTRS groups specified within LDAP2OTRS.ini file will be revoked
- The permissions related to any other OTRS group NOT specified within LDAP2OTRS.ini file will be left untouched

if the --simulate parameter is specified, no actual operation is performed on OTRS

One only warning: since the same user can belong to several LDAP groups it's up to you to define a LDAP2OTRS.ini configuration file that make sense :) I mean, take for example the two lines:
CN=OTRS_CUSTOMERS1,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_Z_grp|ro
CN=OTRS_CUSTOMERS2,OU=Lists and Groups,DC=mylocal,DC=domain|ACME-Support_Z_grp|rw
... if the same user belongs to both OTRS_CUSTOMERS1 and OTRS_CUSTOMERS2 LDAP groups you are trying here to assign him/her both ro and rw permissions on ACME-Support_Z_grp OTRS group.
Since the map used within the script is an unsorted hash, the final behavior in similar cases is unpredictable and the user could finally be granted ro permissions or rw permissions for ACME-Support_Z_grp... just take care ;)
OTRS 6.0.x on CentOS 7.x with MariaDB 10.2.x database connected to an Active Directory for Agents and Customers.
ITSM and FAQ modules installed.
Giulio Soleni
Znuny wizard
Posts: 392
Joined: 30 Dec 2010, 14:35
Znuny Version: 6.0.x and 5.0.x
Real Name: Giulio Soleni
Company: IKS srl

Re: map LADP/ActiveDirectory groups into OTRS groups for customers

Post by Giulio Soleni »

I have an updated version of the script for OTRS 4 with an improved routine.

now the behavior is the following (LDAP2OTRS.ini is the configuration file that specify the group mappings):
- For all users in the Customer LDAP base group (Customer::AuthModule::LDAP::GroupDN in Config.pm) the permissions for OTRS groups specified within LDAP2OTRS.ini file will be revoked
- For all users in LDAP groups specified within LDAP2OTRS.ini file that are also OTRS Customer users the permissions for OTRS groups specified within LDAP2OTRS.ini file will be applied
- The permissions related to any other LDAP and OTRS group NOT specified within LDAP2OTRS.ini file will be left untouched
- The permissions related to any OTRS user directly specified on the OTRS db, will be left untouched

I also added a logging subroutine that create a logfile within a specified folder with a log-rotation of 2MB

This is the updated script.
As before it must be placed within /opt/otrs/bin folder to work.
Please edit the lines with the comment "# >>>> JUST AN EXAMPLE... EDIT THIS LINE" to adapt the script to your actual environment.

Code: Select all

#!/usr/bin/perl
# --
# bin/iks.AddLDAPCustomer2Group.pl - Sync LDAP users belonging to specific LDAP groups to OTRS customer users on specific OTRS groups
# version: 1.2
#
# USAGE bin/iks.AddLDAPCustomer2Group.pl --map <LDAP2OTRS.ini> [--simulate]
#
# <LDAP2OTRS.ini> is a text file with the following format: LDAP-Group|OTRS-Group|Permission
# 
# it is possible to assign many OTRS groups to the same LDAP group
# LDAP groups must be specified with the Distinguished Name (DN) notation (see RFC-Specification RFC 4514)
# Permissions can be either ro or rw
# - For all users in $$LDAP_BASE_OTRS_CUSTOMERS_USERS LDAP group that are OTRS Customer users the permissions for OTRS groups specified within LDAP2OTRS.ini file will be revoked
# - For all users in LDAP groups specified within LDAP2OTRS.ini file that are also OTRS Customer users the permissions for OTRS groups specified within LDAP2OTRS.ini file will be applied
# - The permissions related to any other LDAP and OTRS group NOT specified within LDAP2OTRS.ini file will be left untouched
# - The permissions related to any OTRS user directly specified on the OTRS db, will be left untouched
#
# if the --simulate parameter is specified, no actual operation is performed on OTRS
#
# Copyright (C) 2015 IKS srl, http://www.iks.it/
# --
use strict;
use warnings;

use Getopt::Long qw(GetOptions);
Getopt::Long::Configure qw(gnu_getopt);

use Sys::Hostname;
use Time::Local;
use Net::LDAP;
use File::Basename;

use FindBin qw($RealBin);
use lib dirname($RealBin);
use lib dirname($RealBin) . '/Kernel/cpan-lib';
use lib dirname($RealBin) . '/Custom';

use Kernel::System::ObjectManager;

# create object manager
local $Kernel::OM = Kernel::System::ObjectManager->new(
    'Kernel::System::Log' => {
        LogPrefix => 'OTRS-iks.AddLDAPCustomer2Group',
    },
);

# init logging parameters
# Generic log call prototype is:
# $LogMsg="...string to log...";
# &LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
my $LogFileName='/otrs_bck/groupsAD/LDAP2OTRS';   # >>>> JUST AN EXAMPLE... EDIT THIS LINE ACCORDING TO YOUR NEEDS
my $LogMsg;
my $EchoToDisplay=\1;

my $simulate;
my $filename;
GetOptions(
	'simulate|s' => \$simulate, # simulation mode, no actual operation will be performed on OTRS
	'map|m=s' => \$filename, # input file, each line has the format: ldapGrp,otrsGrp,permission
	) or die "USAGE: $0 --map <LDAP2OTRS.ini> [--simulate]\n";

if (!defined($filename)) {
        print STDERR "USAGE: $0 --map <LDAP2OTRS.ini> [--simulate]\n";
        exit;
}

$LogMsg="************************** STARTING AddLDAPCustomer2Group PROCEDURE **************************";
&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);

# create common objects
my %LDAP2OTRS; # hashtable of hashtables $LDAP2OTRS{ldapGrp}{otrsGrp}=rw|ro

# CONSTANTS
# LDAP Server
my $LDAP_SERVER = \"ldap-server.mylocal.domain";   # >>>> JUST AN EXAMPLE... EDIT THIS LINE ACCORDING TO THE SETTINGS IN /opt/otrs/Kernel/Config.pm (Customer::AuthModule::LDAP::Host)

# LDAP Base Domain
my $LDAP_BASE_DOMAIN=\"DC=mylocal,DC=domain";   # >>>> JUST AN EXAMPLE... EDIT THIS LINE ACCORDING TO THE SETTINGS IN /opt/otrs/Kernel/Config.pm (Customer::AuthModule::LDAP::BaseDN) 

# technical user to browse LDAP
my $UID = \"CN=otrs,OU=My Technical Users,DC=mylocal,DC=domain";   # >>>> JUST AN EXAMPLE... EDIT THIS LINE ACCORDING TO THE SETTINGS IN /opt/otrs/Kernel/Config.pm (Customer::AuthModule::LDAP::SearchUserDN)
my $BIND_PWD = \"password123";   # >>>> JUST AN EXAMPLE... EDIT THIS LINE ACCORDING TO THE SETTINGS IN /opt/otrs/Kernel/Config.pm (Customer::AuthModule::LDAP::SearchUserPw)

my $LDAP_BASE_OTRS_CUSTOMERS_USERS=\"CN=OTRS Customers,OU=List and Groups,DC=your,DC=dom";   ## >>>> JUST AN EXAMPLE... EDIT THIS LINE ACCORDING TO THE SETTINGS IN /opt/otrs/Kernel/Config.pm (Customer::AuthModule::LDAP::GroupDN)

# connect to LDAP server
my $ldap = Net::LDAP -> new ($$LDAP_SERVER) || die "ERROR: Could not connect to LDAP server\n";

# bind to LDAP server
$ldap -> bind($$UID, password => $$BIND_PWD);

# list attributes to be queried
my @Attrs = ('memberOf','sAMAccountName','userAccountControl');

# open $filename for read (if it exists)
my $fh;
if ( -e $filename ) {
	open $fh, '<', $filename or die "ERROR: Cannot open $filename: $!";
}
else {
	$LogMsg="ERROR: $filename does not exist!";
	&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#	print STDERR "ERROR: $filename does not exist!\n";
	exit;
}

# composition of %LDAP2OTRS hashtable
while ( my $line = <$fh> ) {
	chomp $line;
	$line =~ s/\#.*//;  # skipping comment lines
	$line =~ s/\#$//;   # skipping each # character at end of line
	$line =~ s/^\s+//;  # skipping starting blank
	$line =~ s/\s+$//;  # skipping ending blank
	next unless length $line; # if something useful is left...
	my @row = split( /\|/, $line );
	$LDAP2OTRS{$row[0]}{$row[1]}=$row[2];
}

# close $filename
close($fh);

# DEBUG START
# read %LDAP2OTRS hashtable
#for my $ldapGrp ( keys %LDAP2OTRS ) {
#	print "$ldapGrp: \n";
#	for my $otrsGrp ( keys %{ $LDAP2OTRS{$ldapGrp} } ) {
#		print "$otrsGrp=$LDAP2OTRS{$ldapGrp}{$otrsGrp} ";
#	}
#	print "\n";
#}
# DEBUG END

# REVOKING PERMISSIONS TO ALL USERS BELONGING TO $$LDAP_BASE_OTRS_CUSTOMERS_USERS
for my $ldapGrp ( keys %LDAP2OTRS ) {
	for my $otrsGrp ( keys %{ $LDAP2OTRS{$ldapGrp} } ) {
		# check if $otrsGrp is an OTRS group
		my $CheckGroupID = $Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupLookup(
			Group => $otrsGrp,
		);
		if ( !$CheckGroupID ) {
			$LogMsg="ERROR: Failed to get Group ID: '$otrsGrp' is NOT an OTRS group.";
			&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#			print STDERR "ERROR: Failed to get Group ID: '$otrsGrp' is NOT an OTRS group.\n";
			next;
		}
		
		# search for users in $$LDAP_BASE_OTRS_CUSTOMERS_USERS
		# see https://technet.microsoft.com/it-it/library/aa996205%28v=exchg.65%29.aspx for details		
		my $result = &LDAPsearch ( $ldap, "(&(objectCategory=user)(memberOf=$$LDAP_BASE_OTRS_CUSTOMERS_USERS)(|(userAccountControl=512)(userAccountControl=544)(userAccountControl=66048)(userAccountControl=66080)))", \@Attrs, "$$LDAP_BASE_DOMAIN" );
		
		# Use the following table as a reference for userAccountControl values
		# 512........Enabled Account
		# 514........Disabled Account
		# 544........Enabled, Password Not Required
		# 546........Disabled, Password Not Required
		# 66048......Enabled, Password Doesn't Expire
		# 66050......Disabled, Password Doesn't Expire
		# 66080......Enabled, Password Doesn't Expire & Not Required
		# 66082......Disabled, Password Doesn't Expire & Not Required
		# 262656.....Enabled, Smartcard Required
		# 262658.....Disabled, Smartcard Required
		# 262688.....Enabled, Smartcard Required, Password Not Required
		# 262690.....Disabled, Smartcard Required, Password Not Required
		# 328192.....Enabled, Smartcard Required, Password Doesn't Expire
		# 328194.....Disabled, Smartcard Required, Password Doesn't Expire
		# 328224.....Enabled, Smartcard Required, Password Doesn't Expire & Not Required
		# 328226.....Disabled, Smartcard Required, Password Doesn't Expire & Not Required
		
		my @entries = $result->entries;
		if (scalar(@entries) >0) {
			foreach my $entr ( @entries ) {
				my $thisUser = $entr->get_value('sAMAccountName');
				# check if $thisUser is an OTRS customer
				my $CheckCustomerName = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerName(
					UserLogin => $thisUser,
				);
				if ( !$CheckCustomerName ) {
					$LogMsg="ERROR: Failed to get Customer data: '$thisUser' is NOT an OTRS customer user.";
					&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#					print STDERR "ERROR: Failed to get Customer data: '$thisUser' is NOT an OTRS customer user.\n";
					next;
				}		

				$LogMsg="REVOKING ALL PERMISSIONS TO $thisUser on $otrsGrp ...";
				&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#				print "REVOKING ALL PERMISSIONS TO $thisUser on $otrsGrp ...\n";
				unless ($simulate) {
					# revoking all permissions...
					if ( !$Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { rw => 0, }, UserID => 1, ValidID => 1) ) {
						$LogMsg="ERROR: Can't reset rw permission for $thisUser Customer on $otrsGrp group";
						&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#						print STDERR "ERROR: Can't reset rw permission for $thisUser Customer on $otrsGrp group\n";
					}
					if ( !$Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { ro => 0, }, UserID => 1, ValidID => 1) ) {
						$LogMsg="ERROR: Can't reset ro permission for $thisUser Customer on $otrsGrp group";
						&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#						print STDERR "ERROR: Can't reset ro permission for $thisUser Customer on $otrsGrp group\n";
					}
				}
			}
		}
	}
}

# ASSIGNING PERMISSIONS
for my $ldapGrp ( keys %LDAP2OTRS ) {
	$LogMsg="Searching users within $ldapGrp ...";
	&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#	print "Searching users within $ldapGrp ...\n";

	# search for users in $ldapGrp
	my $result = &LDAPsearch ( $ldap, "(&(objectCategory=user)(memberOf=$ldapGrp)(|(userAccountControl=512)(userAccountControl=544)(userAccountControl=66048)(userAccountControl=66080)))", \@Attrs, "$$LDAP_BASE_DOMAIN" );
	# get entries from result object
	my @entries = $result->entries;
	if (scalar(@entries) >0) {
		foreach my $entr ( @entries ) {
			my $thisUser = $entr->get_value('sAMAccountName');
			for my $otrsGrp ( keys %{ $LDAP2OTRS{$ldapGrp} } ) {
				my $thisPermission = lc $LDAP2OTRS{$ldapGrp}{$otrsGrp};
				# check if $thisPermission is correct
				if ($thisPermission !~ /^r[ow]|reset$/) {
					$LogMsg="ERROR: Wrong permission: '$thisPermission' is NOT a standard permission.";
					&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#					print STDERR "ERROR: Wrong permission: '$thisPermission' is NOT a standard permission.\n";
					next;
				}
				if ($thisPermission eq 'reset') {
					# permissions for this OTRS group have been already reset... just skipping to next group.
					next;
				}
				
				# check if $otrsGrp is an OTRS group
				my $CheckGroupID = $Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupLookup(
					Group => $otrsGrp,
				);
				if ( !$CheckGroupID ) {
					$LogMsg="ERROR: Failed to get Group ID: '$otrsGrp' is NOT an OTRS group.";
					&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#					print STDERR "ERROR: Failed to get Group ID: '$otrsGrp' is NOT an OTRS group.\n";
					next;
				}
				# check if $thisUser is an OTRS customer
				my $CheckCustomerName = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerName(
					UserLogin => $thisUser,
				);
				if ( !$CheckCustomerName ) {
					$LogMsg="ERROR: Failed to get Customer data: '$thisUser' is NOT an OTRS customer user.";
					&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#					print STDERR "ERROR: Failed to get Customer data: '$thisUser' is NOT an OTRS customer user.\n";
					next;
				}
				$LogMsg="ASSIGNING $thisPermission PERMISSIONS TO $thisUser on $otrsGrp ...";
				&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#				print "ASSIGNING $thisPermission PERMISSIONS TO $thisUser on $otrsGrp ...\n";
				unless ($simulate) {
					if ($thisPermission eq 'ro') {
						# revoking all permissions, beforehand (otherwise if $thisUser has already rw permissions on $otrsGrp, ro permission cannot be simply applied) 
						if ( !$Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { rw => 0, }, UserID => 1, ValidID => 1) ) {
							$LogMsg="ERROR: Can't reset rw permission for $thisUser Customer on $otrsGrp group";
							&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#							print STDERR "ERROR: Can't reset rw permission for $thisUser Customer on $otrsGrp group\n";
						}
						if ( !$Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { ro => 0, }, UserID => 1, ValidID => 1) ) {
							$LogMsg="ERROR: Can't reset ro permission for $thisUser Customer on $otrsGrp group";
							&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#							print STDERR "ERROR: Can't reset ro permission for $thisUser Customer on $otrsGrp group\n";
						}
					}
					# ...then setting $thisPermission
					if ( !$Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupMemberAdd(Group => $otrsGrp, GID => $CheckGroupID, UID => $thisUser, Permission => { $thisPermission => 1, }, UserID => 1, ValidID => 1) ) {
						$LogMsg="ERROR: Can't set $thisPermission permission for $thisUser Customer on $otrsGrp group";
						&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);
#						print STDERR "ERROR: Can't set $thisPermission permission for $thisUser Customer on $otrsGrp group\n";
					}
				}
			}
		}
	}
}

# unbind (disconnect) from server
$ldap->unbind;

$LogMsg="************************** AddLDAPCustomer2Group PROCEDURE COMPLETE! **************************";
&LogToFile($LogFileName, $LogMsg, $$EchoToDisplay);

sub LDAPsearch
{
	my ($ldap,$searchString,$attrs,$base) = @_;
	# if they don't pass a base... set it for them
	if (!$base ) { $base = "$$LDAP_BASE_DOMAIN"; }
	# if they don't pass an array of attributes...
	# set up something for them

#   if (!$attrs ) { $attrs = [ 'cn','type' ]; }
	if (!$attrs ) { $attrs = [ 'sAMAccountName' ]; }

	my $result = $ldap->search ( base    => "$base",
								 scope   => "sub",
								 filter  => "$searchString",
								 attrs   =>  $attrs
								);
	return $result;
}

sub LogToFile
{
	my ($inLogFileName, $inLogMessage, $echoToDisplay) = @_;
	
	my $OPENFILE_ERROR=\106;
	
	if ($simulate)
	{
		$inLogMessage='SIMULATION :: '.$inLogMessage; 
	}
	
	if ($inLogFileName eq "")
	{
		if ($echoToDisplay == 1)
		{
			print "$inLogMessage\n";
		}
		return;
	}

	my $LogFileName00=$inLogFileName.'_00.log';
	my $LogFileName01=$inLogFileName.'_01.log';
	
	my @ExactlyNow=localtime;
	# @ExactlyNow = ( seconds , minutes , hours , day_of_month , month-1 , year-1900 , day_of_week , day_of_year , isday ) 

	if ($ExactlyNow[3] < 10) 
	{$ExactlyNow[3]="0".$ExactlyNow[3];} # set 2 digits precision for day
	if ($ExactlyNow[2] < 10) 
	{$ExactlyNow[2]="0".$ExactlyNow[2];} # set 2 digits precision for hours
	if ($ExactlyNow[1] < 10) 
	{$ExactlyNow[1]="0".$ExactlyNow[1];} # set 2 digits precision for minutes
	if ($ExactlyNow[0] < 10) 
	{$ExactlyNow[0]="0".$ExactlyNow[0];} # set 2 digits precision for seconds
	$ExactlyNow[5]=$ExactlyNow[5]+1900;
	my @Months=('jan','feb','mar','apr','maj','jun','jul','aug','sep','oct','nov','dec');
	for my $cnt (0..11)
	{
		if ($ExactlyNow[4] eq $cnt)
		{ $ExactlyNow[4] = $Months[$cnt]; }
	}

	my $TimeStamp=$ExactlyNow[5].'-'.$ExactlyNow[4].'-'.$ExactlyNow[3].' '.$ExactlyNow[2].':'.$ExactlyNow[1].':'.$ExactlyNow[0];

	my @attrs = stat($LogFileName00); 
	
	if (defined($attrs[7])) # if $LogFileName00 exists...
	{
		$attrs[7]=$attrs[7]/1024;
		# check the size of $LogFileName00
		if ($attrs[7] > 2000) # if > 2MB 
		{
			rename ($LogFileName00, $LogFileName01);
			if (!open (WLOG,"> $LogFileName00")) # open $LogFileName00 for write
			{
				print STDERR "SYSTEM ERROR: The Logfile $LogFileName00 cannot be created!\n";
				return $$OPENFILE_ERROR;
			}
		}
		else # the size of $LogFileName00 is less than 2MB
		{ 
			if (!open (WLOG,">> $LogFileName00"))
			{
				print STDERR "SYSTEM ERROR: The Logfile $LogFileName00 cannot be created!\n";
				return $$OPENFILE_ERROR;
			}
		}
	}
	else # $LogFileName00 does not exists
	{
		if (!open (WLOG,"> $LogFileName00"))
		{
			print STDERR "SYSTEM ERROR: The Logfile $LogFileName00 cannot be created!\n";
			return $$OPENFILE_ERROR;
		}
	}
	
	print WLOG "$TimeStamp :: $inLogMessage\n";
	close (WLOG);
	
	if ($echoToDisplay == 1)
	{
		print "$inLogMessage\n";
	}
}
OTRS 6.0.x on CentOS 7.x with MariaDB 10.2.x database connected to an Active Directory for Agents and Customers.
ITSM and FAQ modules installed.
Mondra
Znuny newbie
Posts: 3
Joined: 07 Oct 2015, 17:27
Znuny Version: 4.0.12
Real Name: Eric Mondragon
Company: Private

Re: map LADP/ActiveDirectory groups into OTRS groups for customers

Post by Mondra »

Hi there,

I use a lot of the information in your script for this, however I did a different workaround, I modify the CustomerAuth Module, and get all the verification using another module from OTRS, I leave the code here in case you want to check it.

The file is /opt/otrs/Kernel/System/CustomerAuth/LDAP.pm

The code needs to be under the Auth Subroutine

You will get the following from using this code:
1. The Customer User / Group association will be added at the moment of the login
2. If you remove the User from the LDAP Group, it will be remove from OTRS as well, this happen just at login, if the user is already logged it will keep the group association until next login.
3. If the user auth is not ok it won't add / modify any group association.

Code: Select all

	

# login note
$Kernel::OM->Get('Kernel::System::Log')->Log(
	Priority => 'notice',
	Message =>
		"CustomerUser: $Param{User} ($UserDN) authentication ok (REMOTE_ADDR: $RemoteAddr).",
);

# Above code is from OTRS

##### ------------ Start of Custom Code to Add Groups to Customer via LDAP

# Filter3 will get all groups event if they are nested!
my $Filter3 = "(member:1.2.840.113556.1.4.1941:=$UserDN)";	

# The search is by user, the module first Auth the user and then assign groups as per LDAP / AD (In my case was Active Directory)
my $Result3 = $LDAP->search(
	base   => $Self->{BaseDN},
	filter => $Filter3,
	attrs  => "member",
);



# This part will remove access to all groups, in case you remove them from LDAP, This part needs to be fix as you can have a lot of groups assigned to a customer, I think the best way is to check current groups agains ldap groups....
my %CurrentGroups = $Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupMemberList( UserID => $Param{User}, Type => 'rw', Result => 'HASH', );
foreach my $key ( keys %CurrentGroups ) {
	my %ParamG = (
		UserID     => '1',
		ValidID    => '1',
		UID        => $Param{User},
		Group      => $CurrentGroups{$key},
		GID		   => $key,
		Permission => { 'rw' => 0, },
	);
	$Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupMemberAdd(%ParamG);
	undef %ParamG
};

# Use the information from the search and add the permissions as need it
my $count=$Result3->count;
for (my $i=0;$i<$count;$i++) {
	my $reference = $Result3->entry($i);
	my %ParamG = (
		UserID     => '1',
		ValidID    => '1',
		UID        => $Param{User},
		# I use the CN from the DN of the group, in my case the CN would show me just the name of the group 
		# Example:  CN=ITCustomer,OU=OTRS,OU=Internal IT,OU=Backoffice,OU=CRTC,OU=XXXXXXXXX,DC=XXXXXXXXXX,DC=com
		# I just get the ITCustomer and compare it to the a group created on the server
		Group      => $reference->get_value('cn'),
		# In my case I need all customer to get RW to the groups they belong to
		Permission => { 'rw' => 1, },
	);
	# Here I use an OTRS Module to Lookup for the group
	$ParamG{GID} = $Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupLookup(%ParamG);
	# If the group do not exist then it just skip it and go to the next one, in will log a Notice and an Error on the system log
	if ( !$ParamG{GID} ) {
		$Kernel::OM->Get('Kernel::System::Log')->Log(
			Priority => 'notice',
			Message => "Group ID for '$ParamG{Group}' not found, Skipping this group",
		);
	}
	else {
		# If the group exist it will try to add it, again with an OTRS Module
		if ( !$Kernel::OM->Get('Kernel::System::CustomerGroup')->GroupMemberAdd(%ParamG) ) {
			# If not it will show an error on the system log
			$Kernel::OM->Get('Kernel::System::Log')->Log(
				Priority => 'error',
				Message => "Unable to add the Group '$ParamG{Group}' for User '$Param{User}', please contact the system Administrator",
			);
		}
		else {
			# If the group is added then it will log on the system a notice that the user was added.
			$Kernel::OM->Get('Kernel::System::Log')->Log(
				Priority => 'notice',
				Message => "Added User '$Param{User}' to group '$ParamG{Group}'.",
			);
		}
	}
	undef %ParamG
};

#----------------------- End of custom code.

# Below code is part of OTRS Module

    # take down session
    $LDAP->unbind();
    $LDAP->disconnect();
    return $Param{User};
}


Hope you like it and if you think of a better way to do it, let me know!
Patrick83
Znuny newbie
Posts: 13
Joined: 12 Nov 2015, 10:03
Znuny Version: 5.0.2
Real Name: Patrick

Re: map LADP/ActiveDirectory groups into OTRS groups for customers

Post by Patrick83 »

Hey,

a little question about the script. The config.pm call the script?
Is there an Entry in Config.pm to write?

Thanks for feedback.

Patrick
Do you turn it off and on again?
Mondra
Znuny newbie
Posts: 3
Joined: 07 Oct 2015, 17:27
Znuny Version: 4.0.12
Real Name: Eric Mondragon
Company: Private

Re: map LADP/ActiveDirectory groups into OTRS groups for customers

Post by Mondra »

Hi Patrick,

If your question is about my script, you will have to add the normal stuff for Customer to log in (This in the Config.pm file):

The part in the LDAP is just to change the normal behavior while assigning groups.

The following is part of my Config.pm:

Code: Select all

#**********************************************
# Customer active directory configuration

	# This is an example configuration for an LDAP auth. backend.
	# (make sure Net::LDAP is installed!)
	$Self->{'Customer::AuthModule'} = 'Kernel::System::CustomerAuth::LDAP';
	$Self->{'Customer::AuthModule::LDAP::Host'} = 'X.X.X.X';
	$Self->{'Customer::AuthModule::LDAP::BaseDN'} = 'dc=XXXXl,dc=com';
	$Self->{'Customer::AuthModule::LDAP::UID'} = 'sAMAccountName';
	# The following filter is allow just members of the OTRS Customer Group, if you need everyone to log in, remove this line, or change it as needed
	$Self->{'Customer::AuthModule::LDAP::AlwaysFilter'} = '(objectcategory=CN=Person,CN=Schema,CN=Configuration,DC=XXXXX,DC=com)'; #'(memberOf:1.2.840.113556.1.4.1941:=CN=OTRSCustomers,OU=OTRS,OU=Internal IT,OU=Backoffice,OU=CRTC,OU=XXXXX,DC=XXXXXX,DC=com)';

	# The following is valid but would only be necessary if the
	# anonymous user does NOT have permission to read from the LDAP tree
	$Self->{'Customer::AuthModule::LDAP::SearchUserDN'} = 'CN=otrs,OU=OTRS,OU=Internal IT,OU=Backoffice,OU=CRTC,OU=XXXXX,DC=XXXXXX,DC=com';
	$Self->{'Customer::AuthModule::LDAP::SearchUserPw'} = 'XXXXXX';
	
	# CustomerUser
	# (customer ldap backend and settings)
	$Self->{CustomerUser} = {
		Name => 'XXXXXXX',
		Module => 'Kernel::System::CustomerUser::LDAP',
		Params => {
			# ldap host
			Host => 'X.X.X.X',
			# ldap base dn
			BaseDN => 'dc=XXXX,dc=com',
			# search scope (one|sub)
			SSCOPE => 'sub',
			# I added this filter because our company active directory is a mess, to avoid having computers as users in OTRS
			AlwaysFilter => '(objectcategory=CN=Person,CN=Schema,CN=Configuration,DC=XXXXXXX,DC=com)',
			# The following is valid but would only be necessary if the
			# anonymous user does NOT have permission to read from the LDAP tree
			UserDN => 'CN=otrs,OU=OTRS,OU=Internal IT,OU=Backoffice,OU=CRTC,OU=XXXXXX,DC=XXXXX,DC=com',
			UserPw => 'XXXXXXX',
		},
		# customer unique id
		CustomerKey => 'sAMAccountName',
		# customer #
		CustomerID => 'mail',
		CustomerUserListFields => ['sAMAccountName', 'cn', 'mail'],
		CustomerUserSearchFields => ['sAMAccountName', 'cn', 'mail'],
		CustomerUserSearchPrefix => '',
		CustomerUserSearchSuffix => '*',
		CustomerUserSearchListLimit => 1000,
		CustomerUserPostMasterSearchFields => ['mail'],
		CustomerUserNameFields => ['givenname', 'sn'],
		# show not own tickets in customer panel, CompanyTickets
		CustomerUserExcludePrimaryCustomerID => 0,
		# add an ldap filter for valid users (expert setting)
	#    CustomerUserValidFilter => '(!(description=locked))',
		# administrator can't change customer preferences
		AdminSetPreferences => 0,
	#    # cache time to live in sec. - cache any database queries
	#    CacheTTL => 0,
		Map => [
			# note: Login, Email and CustomerID are mandatory!
			# var, frontend, storage, shown (1=always,2=lite), required, storage-type, http-link, readonly
			[ 'UserTitle',      'Title',      'title',           1, 0, 'var', '', 0 ],
			[ 'UserFirstname',  'Firstname',  'givenname',       1, 1, 'var', '', 0 ],
			[ 'UserLastname',   'Lastname',   'sn',              1, 1, 'var', '', 0 ],
			[ 'UserLogin',      'Username',   'sAMAccountName',             1, 1, 'var', '', 0 ],
			[ 'UserEmail',      'Email',      'mail',            1, 1, 'var', '', 0 ],
			[ 'UserCustomerID', 'CustomerID', 'mail',            0, 1, 'var', '', 0 ],
	#       [ 'UserCustomerIDs', 'CustomerIDs', 'second_customer_ids', 1, 0, 'var', '', 0 ],
			[ 'UserPhone',      'Phone',      'telephonenumber', 1, 0, 'var', '', 0 ],
			[ 'UserAddress',    'Address',    'postaladdress',   1, 0, 'var', '', 0 ],
			[ 'UserComment',    'Comment',    'description',     1, 0, 'var', '', 0 ],
		],
	},


	
# End of customer active directory configuration
#***********************************************
Also this code is working with OTRS 4.0.12, I'm migrating to Version 5, and I already found a problem with Agent AD login, haven't done Customers Login yet, so if I find something I'll let you know.

Best Regards,
Eric Mondragon
Ralph
Znuny newbie
Posts: 40
Joined: 17 Jun 2015, 13:40
Znuny Version: 3.3.9 (win)

Re: map LADP/ActiveDirectory groups into OTRS groups for customers

Post by Ralph »

Does anyone know if this works for OTRS 5 and what is needed for it to work?

Asking this with regards to viewtopic.php?f=62&t=31487&p=128261

Thanks!

Edit, solved my issues (see the thread I linked).
Post Reply