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;
}
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
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
![Smile :)](./images/smilies/icon_smile.gif)
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
![Wink ;)](./images/smilies/icon_wink.gif)