Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
minerva
core
Commits
fd5b6407
Commit
fd5b6407
authored
Jul 18, 2018
by
Piotr Gawron
Browse files
user is automatically created when local user doesn't exist and authentication goes via ldap
parent
8d0dc7af
Changes
17
Expand all
Hide whitespace changes
Inline
Side-by-side
model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java
View file @
fd5b6407
...
...
@@ -221,7 +221,7 @@ public enum ConfigurationElementType {
ConfigurationElementTypeGroup
.
LDAP_CONFIGURATION
),
//
LDAP_EMAIL_ATTRIBUTE
(
"LDAP email attribute"
,
"mail"
,
ConfigurationElementEditType
.
STRING
,
true
,
ConfigurationElementTypeGroup
.
LDAP_CONFIGURATION
),
//
LDAP_FILTER
(
"LDAP filter "
,
"(memberof=cn=
gitlab
,cn=groups,cn=accounts,dc=uni,dc=lu)"
,
ConfigurationElementEditType
.
STRING
,
true
,
LDAP_FILTER
(
"LDAP filter "
,
"(memberof=cn=
minerva
,cn=groups,cn=accounts,dc=uni,dc=lu)"
,
ConfigurationElementEditType
.
STRING
,
true
,
ConfigurationElementTypeGroup
.
LDAP_CONFIGURATION
),
//
;
...
...
model/src/main/java/lcsb/mapviewer/model/user/User.java
View file @
fd5b6407
...
...
@@ -118,8 +118,13 @@ public class User implements Serializable {
*/
private
boolean
removed
=
false
;
@Column
(
name
=
"terms_of_use_consent"
)
private
boolean
termsOfUseConsent
=
false
;
/**
* User is connected to LDAP directory.
*/
private
boolean
connectedToLdap
=
false
;
@Column
(
name
=
"terms_of_use_consent"
)
private
boolean
termsOfUseConsent
=
false
;
/**
* Set of user privileges.
...
...
@@ -378,4 +383,12 @@ public class User implements Serializable {
this
.
termsOfUseConsent
=
termsOfUseConsent
;
}
public
boolean
isConnectedToLdap
()
{
return
connectedToLdap
;
}
public
void
setConnectedToLdap
(
boolean
connectedToLdap
)
{
this
.
connectedToLdap
=
connectedToLdap
;
}
}
persist/src/db/12.1.0~alpha.0/fix_db_20180718.sql
0 → 100644
View file @
fd5b6407
-- user account can be connected to LDAP directory
alter
table
user_table
add
column
connectedtoldap
boolean
default
false
;
persist/src/main/java/lcsb/mapviewer/persist/dao/user/UserDao.java
View file @
fd5b6407
...
...
@@ -2,9 +2,6 @@ package lcsb.mapviewer.persist.dao.user;
import
java.util.List
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
lcsb.mapviewer.model.user.User
;
import
lcsb.mapviewer.persist.dao.BaseDao
;
...
...
@@ -16,102 +13,80 @@ import lcsb.mapviewer.persist.dao.BaseDao;
*/
public
class
UserDao
extends
BaseDao
<
User
>
{
/**
* Service that provides password encoding.
*/
@Autowired
private
PasswordEncoder
passwordEncoder
;
/**
* Default constructor.
*/
public
UserDao
()
{
super
(
User
.
class
,
"removed"
);
}
/**
* Returns user with a given login and password.
*
* @param login
* user login
* @param password
* - user password (plain text)
* @return user for given login and password
*/
public
User
getUserByLoginAndPassword
(
String
login
,
String
password
)
{
if
(
password
==
null
)
{
return
getUserByLoginAndCryptedPassword
(
login
,
null
);
}
return
getUserByLoginAndCryptedPassword
(
login
,
passwordEncoder
.
encode
(
password
));
}
/**
* Default constructor.
*/
public
UserDao
()
{
super
(
User
.
class
,
"removed"
);
}
/**
* Returns user with a given login and password.
*
* @param login
* user login
* @param password
* - user password (encrypted)
* @return user for given login and password
*/
public
User
getUserByLoginAndCryptedPassword
(
String
login
,
String
password
)
{
List
<?>
list
=
getSession
()
.
createQuery
(
" from User where login=:login and cryptedPassword =:passwd "
+
removableAndStatemant
())
.
setParameter
(
"login"
,
login
)
.
setParameter
(
"passwd"
,
password
).
list
();
if
(
list
.
size
()
==
0
)
{
return
null
;
}
else
{
User
user
=
(
User
)
list
.
get
(
0
);
return
user
;
}
}
/**
* Returns user with a given login and password.
*
* @param login
* user login
* @param password
* - user password (encrypted)
* @return user for given login and password
*/
public
User
getUserByLoginAndCryptedPassword
(
String
login
,
String
password
)
{
List
<?>
list
=
getSession
()
.
createQuery
(
" from User where login=:login and cryptedPassword =:passwd "
+
removableAndStatemant
())
.
setParameter
(
"login"
,
login
)
.
setParameter
(
"passwd"
,
password
).
list
();
if
(
list
.
size
()
==
0
)
{
return
null
;
}
else
{
User
user
=
(
User
)
list
.
get
(
0
);
return
user
;
}
}
/**
* Returns user with a given login.
*
* @param login
* user login
* @return user for a given login
*/
public
User
getUserByLogin
(
String
login
)
{
return
getByParameter
(
"login"
,
login
);
}
/**
* Returns user with a given login.
*
* @param login
* user login
* @return user for a given login
*/
public
User
getUserByLogin
(
String
login
)
{
return
getByParameter
(
"login"
,
login
);
}
/**
* Returns user with a given email.
*
* @param email
* user email
* @return user for a given email
*/
public
User
getUserByEmail
(
String
email
)
{
return
getByParameter
(
"email"
,
email
);
}
/**
* Returns user with a given email.
*
* @param email
* user email
* @return user for a given email
*/
public
User
getUserByEmail
(
String
email
)
{
return
getByParameter
(
"email"
,
email
);
}
@Override
public
void
delete
(
User
object
)
{
object
.
setRemoved
(
true
);
object
.
setLogin
(
"[REMOVED]_"
+
object
.
getId
()
+
"_"
+
object
.
getLogin
());
update
(
object
);
}
@Override
public
void
delete
(
User
object
)
{
object
.
setRemoved
(
true
);
object
.
setLogin
(
"[REMOVED]_"
+
object
.
getId
()
+
"_"
+
object
.
getLogin
());
update
(
object
);
}
/**
* Returns {@link User} for given "name surname" string.
*
* @param nameSurnameString
* string identifing user with name and surname separated by single
* space
* @return {@link User} for given "name surname" string
*/
public
User
getUserByNameSurname
(
String
nameSurnameString
)
{
List
<?>
list
=
getSession
()
.
createQuery
(
" from "
+
this
.
getClazz
().
getSimpleName
()
+
" where concat(name, ' ', surname) "
+
" = :param_val "
+
removableAndStatemant
())
.
setParameter
(
"param_val"
,
nameSurnameString
).
list
();
if
(
list
.
size
()
==
0
)
{
return
null
;
}
else
{
return
(
User
)
list
.
get
(
0
);
}
}
/**
* Returns {@link User} for given "name surname" string.
*
* @param nameSurnameString
* string identifing user with name and surname separated by single
* space
* @return {@link User} for given "name surname" string
*/
public
User
getUserByNameSurname
(
String
nameSurnameString
)
{
List
<?>
list
=
getSession
()
.
createQuery
(
" from "
+
this
.
getClazz
().
getSimpleName
()
+
" where concat(name, ' ', surname) "
+
" = :param_val "
+
removableAndStatemant
())
.
setParameter
(
"param_val"
,
nameSurnameString
).
list
();
if
(
list
.
size
()
==
0
)
{
return
null
;
}
else
{
return
(
User
)
list
.
get
(
0
);
}
}
}
persist/src/test/java/lcsb/mapviewer/persist/dao/user/UserDaoTest.java
View file @
fd5b6407
...
...
@@ -122,49 +122,6 @@ public class UserDaoTest extends PersistTestFunctions {
}
}
@Test
public
void
testGetUserByLoginAndPassword
()
throws
Exception
{
try
{
User
user
=
new
User
();
user
.
setCryptedPassword
(
passwordEncoder
.
encode
(
testPasswd
));
user
.
setLogin
(
testLogin
);
userDao
.
add
(
user
);
User
user2
=
userDao
.
getUserByLoginAndPassword
(
testLogin
,
testPasswd
);
assertNotNull
(
user2
);
assertEquals
(
user2
.
getId
(),
user
.
getId
());
assertEquals
(
user2
.
getLogin
(),
user
.
getLogin
());
assertEquals
(
user2
.
getCryptedPassword
(),
user
.
getCryptedPassword
());
userDao
.
delete
(
user
);
// after we remove it we shouldn't be able to get it
User
user3
=
userDao
.
getUserByLoginAndPassword
(
testLogin
,
testPasswd
);
assertNull
(
user3
);
// after we remove it we shouldn't be able to get the removed and modified
// object
User
user4
=
userDao
.
getUserByLoginAndPassword
(
user2
.
getLogin
(),
testPasswd
);
assertNull
(
user4
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
e
;
}
}
@Test
public
void
testGetUserByLoginAndEmptyPassword
()
throws
Exception
{
try
{
User
user
=
new
User
();
user
.
setCryptedPassword
(
passwordEncoder
.
encode
(
testPasswd
));
user
.
setLogin
(
testLogin
);
userDao
.
add
(
user
);
User
user2
=
userDao
.
getUserByLoginAndPassword
(
testLogin
,
null
);
assertNull
(
user2
);
userDao
.
delete
(
user
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
e
;
}
}
@Test
public
void
testGetUserByLogin
()
throws
Exception
{
try
{
...
...
service/src/main/java/lcsb/mapviewer/services/impl/LdapService.java
View file @
fd5b6407
...
...
@@ -78,7 +78,7 @@ public class LdapService implements ILdapService {
@Override
public
boolean
login
(
String
login
,
String
password
)
throws
LDAPException
{
if
(!
isValidConfiguratio
())
{
if
(!
isValidConfiguratio
n
())
{
logger
.
warn
(
"Invalid LDAP configuration"
);
return
false
;
}
...
...
@@ -98,7 +98,7 @@ public class LdapService implements ILdapService {
@Override
public
List
<
String
>
getUsernames
()
throws
LDAPException
{
if
(!
isValidConfiguratio
())
{
if
(!
isValidConfiguratio
n
())
{
logger
.
warn
(
"Invalid LDAP configuration"
);
return
new
ArrayList
<>();
}
...
...
@@ -128,7 +128,7 @@ public class LdapService implements ILdapService {
@Override
public
UserDTO
getUserByLogin
(
String
login
)
throws
LDAPException
{
if
(!
isValidConfiguratio
())
{
if
(!
isValidConfiguratio
n
())
{
logger
.
warn
(
"Invalid LDAP configuration"
);
return
null
;
}
...
...
@@ -187,11 +187,11 @@ public class LdapService implements ILdapService {
}
}
private
Filter
createObjectClassFilter
()
{
private
Filter
createObjectClassFilter
()
throws
LDAPException
{
String
objectClass
=
configurationService
.
getConfigurationValue
(
ConfigurationElementType
.
LDAP_OBJECT_CLASS
);
if
(
objectClass
==
null
||
objectClass
.
trim
().
isEmpty
())
{
objectClass
=
"
*"
;
if
(
objectClass
==
null
||
objectClass
.
trim
().
isEmpty
()
||
objectClass
.
equals
(
"*"
)
)
{
return
Filter
.
create
(
"
objectClass
=
*"
)
;
}
return
Filter
.
createEqualityFilter
(
"objectClass"
,
objectClass
);
...
...
@@ -220,7 +220,7 @@ public class LdapService implements ILdapService {
}
@Override
public
boolean
isValidConfiguratio
()
{
public
boolean
isValidConfiguratio
n
()
{
try
{
String
baseDn
=
configurationService
.
getConfigurationValue
(
ConfigurationElementType
.
LDAP_BASE_DN
);
if
(
baseDn
==
null
||
baseDn
.
trim
().
isEmpty
())
{
...
...
service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java
View file @
fd5b6407
...
...
@@ -5,7 +5,6 @@ import java.io.FileInputStream;
import
java.io.FileNotFoundException
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Calendar
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.HashMap
;
...
...
@@ -60,7 +59,6 @@ import lcsb.mapviewer.converter.zip.ZipEntryFile;
import
lcsb.mapviewer.model.Project
;
import
lcsb.mapviewer.model.ProjectStatus
;
import
lcsb.mapviewer.model.cache.UploadedFileEntry
;
import
lcsb.mapviewer.model.graphics.MapCanvasType
;
import
lcsb.mapviewer.model.log.LogType
;
import
lcsb.mapviewer.model.map.BioEntity
;
import
lcsb.mapviewer.model.map.MiriamData
;
...
...
@@ -477,20 +475,7 @@ public class ProjectService implements IProjectService {
for
(
User
user
:
userDao
.
getAll
())
{
if
(!
processedUser
.
contains
(
user
))
{
processedUser
.
add
(
user
);
for
(
PrivilegeType
type
:
PrivilegeType
.
values
())
{
if
(
Project
.
class
.
equals
(
type
.
getPrivilegeObjectType
()))
{
int
level
=
userService
.
getUserPrivilegeLevel
(
user
,
type
,
(
Integer
)
null
);
if
(
level
<
0
)
{
if
(
configurationService
.
getValue
(
type
).
getValue
().
equalsIgnoreCase
(
"true"
))
{
level
=
1
;
}
else
{
level
=
0
;
}
}
ObjectPrivilege
privilege
=
new
ObjectPrivilege
(
project
,
level
,
type
,
user
);
userService
.
setUserPrivilege
(
user
,
privilege
);
}
}
userService
.
createDefaultProjectPrivilegesForUser
(
project
,
user
);
}
}
...
...
service/src/main/java/lcsb/mapviewer/services/impl/UserService.java
View file @
fd5b6407
This diff is collapsed.
Click to expand it.
service/src/main/java/lcsb/mapviewer/services/interfaces/ILdapService.java
View file @
fd5b6407
...
...
@@ -56,6 +56,6 @@ public interface ILdapService {
* @return true if LDAP configuration
* ({@link ConfigurationElementTypeGroup#LDAP_CONFIGURATION}) is valid
*/
boolean
isValidConfiguratio
();
boolean
isValidConfiguratio
n
();
}
service/src/main/java/lcsb/mapviewer/services/interfaces/IUserService.java
View file @
fd5b6407
...
...
@@ -3,6 +3,7 @@ package lcsb.mapviewer.services.interfaces;
import
java.util.List
;
import
lcsb.mapviewer.commands.ColorExtractor
;
import
lcsb.mapviewer.model.Project
;
import
lcsb.mapviewer.model.user.BasicPrivilege
;
import
lcsb.mapviewer.model.user.PrivilegeType
;
import
lcsb.mapviewer.model.user.User
;
...
...
@@ -198,4 +199,10 @@ public interface IUserService {
String
login
(
String
login
,
String
password
,
String
id
);
ILdapService
getLdapService
();
void
setLdapService
(
ILdapService
ldapService
);
void
createDefaultProjectPrivilegesForUser
(
Project
project
,
User
user
);
}
service/src/test/java/lcsb/mapviewer/services/impl/AllImplServiceTests.java
View file @
fd5b6407
...
...
@@ -6,13 +6,14 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith
(
Suite
.
class
)
@SuiteClasses
({
CommentServiceTest
.
class
,
//
ConfigurationServiceTest
.
class
,
//
ExternalServicesServiceTest
.
class
,
//
LayoutServiceTest
.
class
,
//
Md5PasswordEncoderTest
.
class
,
//
ProjectServiceTest
.
class
,
//
SearchServiceTest
.
class
,
//
UserServiceTest
.
class
,
//
ConfigurationServiceTest
.
class
,
//
ExternalServicesServiceTest
.
class
,
//
LayoutServiceTest
.
class
,
//
LdapServiceTest
.
class
,
//
Md5PasswordEncoderTest
.
class
,
//
ProjectServiceTest
.
class
,
//
SearchServiceTest
.
class
,
//
UserServiceTest
.
class
,
//
})
public
class
AllImplServiceTests
{
...
...
service/src/test/java/lcsb/mapviewer/services/impl/LdapServiceTest.java
View file @
fd5b6407
...
...
@@ -69,9 +69,9 @@ public class LdapServiceTest extends ServiceTestFunctions {
@Test
public
void
testIsValidConfiguration
()
throws
Exception
{
try
{
assertTrue
(
ldapService
.
isValidConfiguratio
());
assertTrue
(
ldapService
.
isValidConfiguratio
n
());
configurationService
.
setConfigurationValue
(
ConfigurationElementType
.
LDAP_BASE_DN
,
""
);
assertFalse
(
ldapService
.
isValidConfiguratio
());
assertFalse
(
ldapService
.
isValidConfiguratio
n
());
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
e
;
...
...
service/src/test/java/lcsb/mapviewer/services/impl/UserServiceTest.java
View file @
fd5b6407
...
...
@@ -11,14 +11,24 @@ import org.apache.log4j.Logger;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.mockito.Mockito
;
import
org.mockito.invocation.InvocationOnMock
;
import
org.mockito.stubbing.Answer
;
import
org.springframework.test.annotation.Rollback
;
import
com.unboundid.ldap.listener.InMemoryDirectoryServer
;
import
com.unboundid.ldap.listener.InMemoryDirectoryServerConfig
;
import
com.unboundid.ldap.sdk.LDAPConnection
;
import
com.unboundid.ldap.sdk.LDAPException
;
import
lcsb.mapviewer.model.Project
;
import
lcsb.mapviewer.model.user.BasicPrivilege
;
import
lcsb.mapviewer.model.user.ConfigurationElementType
;
import
lcsb.mapviewer.model.user.ObjectPrivilege
;
import
lcsb.mapviewer.model.user.PrivilegeType
;
import
lcsb.mapviewer.model.user.User
;
import
lcsb.mapviewer.services.ServiceTestFunctions
;
import
lcsb.mapviewer.services.interfaces.ILdapService
;
@Rollback
(
true
)
public
class
UserServiceTest
extends
ServiceTestFunctions
{
...
...
@@ -46,6 +56,58 @@ public class UserServiceTest extends ServiceTestFunctions {
}
}
@Test
public
void
testLoginFromLdap
()
throws
Exception
{
ILdapService
originalLdapService
=
userService
.
getLdapService
();
try
{
String
login
=
"john.doe.test"
;
String
passwd
=
"test_passwd"
;
assertNull
(
userService
.
getUserByLogin
(
login
));
LdapService
ldapService
=
createMockLdapService
(
"testFiles/ldap/john-doe-test-example.ldif"
,
login
,
passwd
);
userService
.
setLdapService
(
ldapService
);
assertNull
(
userService
.
login
(
login
,
"incorrect password"
));
assertNotNull
(
"User from LDAP wasn't authenticated"
,
userService
.
login
(
login
,
passwd
));
User
user
=
userService
.
getUserByLogin
(
login
);
assertNotNull
(
"After authentication from LDAP user is not present in the system"
,
user
);
assertTrue
(
user
.
isConnectedToLdap
());
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
e
;
}
finally
{
userService
.
setLdapService
(
originalLdapService
);
}
}
private
LdapService
createMockLdapService
(
String
filename
,
String
login
,
String
passwd
)
throws
LDAPException
{
configurationService
.
setConfigurationValue
(
ConfigurationElementType
.
LDAP_BASE_DN
,
"dc=uni,dc=lu"
);
configurationService
.
setConfigurationValue
(
ConfigurationElementType
.
LDAP_OBJECT_CLASS
,
"person"
);
LdapService
ldapService
=
Mockito
.
spy
(
LdapService
.
class
);
ldapService
.
setConfigurationService
(
configurationService
);
Mockito
.
when
(
ldapService
.
getConnection
()).
thenAnswer
(
new
Answer
<
LDAPConnection
>()
{
@Override
public
LDAPConnection
answer
(
InvocationOnMock
invocation
)
throws
Throwable
{
// Create the configuration to use for the server.
InMemoryDirectoryServerConfig
config
=
new
InMemoryDirectoryServerConfig
(
"dc=uni,dc=lu"
);
config
.
addAdditionalBindCredentials
(
"uid="
+
login
+
",cn=users,cn=accounts,dc=uni,dc=lu"
,
passwd
);
config
.
setSchema
(
null
);
// Create the directory server instance, populate it with data from the
// "test-data.ldif" file, and start listening for client connections.
InMemoryDirectoryServer
ds
=
new
InMemoryDirectoryServer
(
config
);
ds
.
importFromLDIF
(
true
,
filename
);
ds
.
startListening
();
return
ds
.
getConnection
();
}
});
return
ldapService
;
}
@Test
public
void
testLoginWithNull
()
{
try
{
...
...
service/testFiles/ldap/john-doe-test-example.ldif
0 → 100644
View file @
fd5b6407
version: 1
dn: dc=uni,dc=lu
objectClass: top
objectClass: domain
dc: uni
dn: cn=accounts,dc=uni,dc=lu
objectClass: extensibleObject
cn: accounts
dn: cn=users,cn=accounts,dc=uni,dc=lu
objectClass: extensibleObject
cn: users
dn: uid=john.doe.test,cn=users,cn=accounts,dc=uni,dc=lu
objectClass: mepOriginEntry
objectClass: ipaSshGroupOfPubKeys
objectClass: posixaccount
objectClass: inetuser
objectClass: krbprincipalaux
objectClass: krbticketpolicyaux
objectClass: organizationalperson
objectClass: inetorgperson
objectClass: ipasshuser
objectClass: top
objectClass: person
objectClass: ipaobject
cn: John Doe
gidNumber: 369550501
homeDirectory: /home/john.doe.test
ipaUniqueID: adf723e6-20e4-11e5-8907-001a4ae51219
sn: Doe
uid: john.doe.test
uidNumber: 369550501
displayName: John Doe
gecos: John Doe
givenName: John
initials: JD
krbLastPwdChange: 20180608111630Z
krbPasswordExpiration: 20190223111630Z
krbPrincipalName: john.doe.test@UNI.LU
loginShell: /bin/bash
mail: john.doe.test@uni.lu
memberOf: cn=lcsb,cn=groups,cn=accounts,dc=uni,dc=lu
memberOf: ipaUniqueID=d7549fa2-03a2-11e5-95f9-00163e0a4f7b,cn=hbac,dc=uni,dc
=lu
memberOf: ipaUniqueID=eff7677e-03a2-11e5-add5-00163e0a4f7b,cn=sudorules,cn=s
udo,dc=uni,dc=lu
memberOf: ipaUniqueID=f10ec0f6-7ef6-11e5-957b-001a4ae5121e,cn=hbac,dc=uni,dc
=lu
memberOf: ipaUniqueID=2cf9b59e-7ef7-11e5-89c0-001a4ae5121e,cn=sudorules,cn=s
udo,dc=uni,dc=lu
memberOf: ipaUniqueID=eeb5f68e-9775-11e5-81fb-00163e0a4f7b,cn=hbac,dc=uni,dc
=lu
memberOf: ipaUniqueID=176f7fb4-9776-11e5-a097-00163e0a4f7b,cn=sudorules,cn=s
udo,dc=uni,dc=lu
memberOf: ipaUniqueID=7dda82ae-99e1-11e5-834b-001a4ae5121e,cn=hbac,dc=uni,dc
=lu
memberOf: ipaUniqueID=a44d842c-99e1-11e5-9c2f-001a4ae5121e,cn=sudorules,cn=s
udo,dc=uni,dc=lu
memberOf: cn=webdav-public-minerva,cn=groups,cn=accounts,dc=uni,dc=lu
memberOf: ipaUniqueID=1d1f58b8-a247-11e5-ac5d-00163e0a4f7b,cn=hbac,dc=uni,dc
=lu
memberOf: ipaUniqueID=41f879a8-a247-11e5-9d34-00163e0a4f7b,cn=sudorules,cn=s
udo,dc=uni,dc=lu
memberOf: ipaUniqueID=33e9e6a2-c8d1-11e5-b578-001a4ae5121e,cn=hbac,dc=uni,dc
=lu