Closes #2302 - Adds Permissions to Users

- Extends LDAP client to read permission attributes from users
            - Extends database schema with PERMISSION_INFO table and sets schema version to 6.4.0
            - Exetends User models (Builder, Mapper,..) to have permission attribute
    	- Determination of Domains is now able to be done via permissions defined on users

    Signed-off-by: Kálmán Képes <2853992+nyuuyn@users.noreply.github.com>
This commit is contained in:
Kálmán Képes 2023-09-01 09:33:54 +02:00 committed by Elena Mokeeva
parent 8cff46a969
commit 18c34d1dfd
39 changed files with 566 additions and 8 deletions

View File

@ -14,5 +14,6 @@ DELETE FROM OBJECT_REFERENCE;
DELETE FROM SCHEDULED_JOB;
DELETE FROM USER_INFO;
DELETE FROM GROUP_INFO;
DELETE FROM PERMISSION_INFO;
INSERT INTO CONFIGURATION (NAME) VALUES ('MASTER');
COMMIT;

View File

@ -14,6 +14,7 @@ DROP TABLE OBJECT_REFERENCE;
DROP TABLE SCHEDULED_JOB;
DROP TABLE USER_INFO;
DROP TABLE GROUP_INFO;
DROP TABLE PERMISSION_INFO;
DROP SEQUENCE SCHEDULED_JOB_SEQ;
DROP SEQUENCE TASKANA_SCHEMA_VERSION_ID_SEQ;
COMMIT;

View File

@ -357,6 +357,13 @@ CREATE TABLE GROUP_INFO
PRIMARY KEY (USER_ID, GROUP_ID)
);
CREATE TABLE PERMISSION_INFO
(
USER_ID VARCHAR(32) NOT NUll,
PERMISSION_ID VARCHAR(256) NOT NULL,
PRIMARY KEY (USER_ID, PERMISSION_ID)
);
CREATE SEQUENCE SCHEDULED_JOB_SEQ
MINVALUE 1
START WITH 1

View File

@ -0,0 +1,12 @@
-- this script updates the TASKANA database schema from version 6.2.0 to version 6.4.0.
SET SCHEMA %schemaName%;
INSERT INTO TASKANA_SCHEMA_VERSION (ID, VERSION, CREATED)
VALUES (nextval('TASKANA_SCHEMA_VERSION_ID_SEQ'), '6.4.0', CURRENT_TIMESTAMP);
CREATE TABLE PERMISSION_INFO
(
USER_ID VARCHAR(32) NOT NULL,
PERMISSION_ID VARCHAR(256) NOT NULL,
PRIMARY KEY (USER_ID, PERMISSION_ID)
);

View File

@ -363,6 +363,13 @@ CREATE TABLE GROUP_INFO
PRIMARY KEY (USER_ID, GROUP_ID)
);
CREATE TABLE PERMISSION_INFO
(
USER_ID VARCHAR(32) NOT NULL,
PERMISSION_ID VARCHAR(256) NOT NULL,
PRIMARY KEY (USER_ID, PERMISSION_ID)
);
CREATE SEQUENCE SCHEDULED_JOB_SEQ
MINVALUE 1
START WITH 1

View File

@ -0,0 +1,11 @@
-- this script updates the TASKANA database schema from version 6.2.0 to version 6.4.0.
INSERT INTO TASKANA_SCHEMA_VERSION (ID, VERSION, CREATED)
VALUES (nextval('TASKANA_SCHEMA_VERSION_ID_SEQ'), '6.4.0', CURRENT_TIMESTAMP);
CREATE TABLE PERMISSION_INFO
(
USER_ID VARCHAR(32) NOT NULL,
PERMISSION_ID VARCHAR(256) NOT NULL,
PRIMARY KEY (USER_ID, PERMISSION_ID)
);

View File

@ -356,6 +356,13 @@ CREATE TABLE GROUP_INFO
CONSTRAINT GROUP_INFO_PKEY PRIMARY KEY (USER_ID, GROUP_ID)
);
CREATE TABLE PERMISSION_INFO
(
USER_ID VARCHAR2(32) NOT NULL,
PERMISSION_ID VARCHAR2(256) NOT NULL,
CONSTRAINT PERMISSION_INFO_PKEY PRIMARY KEY (USER_ID, PERMISSION_ID)
);
CREATE SEQUENCE SCHEDULED_JOB_SEQ
START WITH 1
INCREMENT BY 1

View File

@ -0,0 +1,12 @@
-- this script updates the TASKANA database schema from version 6.2.0 to version 6.4.0.
ALTER SESSION SET CURRENT_SCHEMA = %schemaName%;
INSERT INTO TASKANA_SCHEMA_VERSION (ID, VERSION, CREATED)
VALUES (nextval('TASKANA_SCHEMA_VERSION_ID_SEQ'), '6.4.0', CURRENT_TIMESTAMP);
CREATE TABLE PERMISSION_INFO
(
USER_ID VARCHAR2(32) NOT NULL,
PERMISSION_ID VARCHAR2(256) NOT NULL,
CONSTRAINT PERMISSION_INFO_PKEY PRIMARY KEY (USER_ID, PERMISSION_ID)
);

View File

@ -360,6 +360,13 @@ CREATE TABLE GROUP_INFO
PRIMARY KEY (USER_ID, GROUP_ID)
);
CREATE TABLE PERMISSION_INFO
(
USER_ID VARCHAR(32) NOT NULL,
PERMISSION_ID VARCHAR(256) NOT NULL,
PRIMARY KEY (USER_ID, PERMISSION_ID)
);
CREATE SEQUENCE SCHEDULED_JOB_SEQ
MINVALUE 1
START WITH 1

View File

@ -0,0 +1,13 @@
-- this script updates the TASKANA database schema from version 6.2.0 to version 6.4.0.
SET search_path = %schemaName%;
INSERT INTO TASKANA_SCHEMA_VERSION (ID, VERSION, CREATED)
VALUES (nextval('TASKANA_SCHEMA_VERSION_ID_SEQ'), '6.4.0', CURRENT_TIMESTAMP);
CREATE TABLE PERMISSION_INFO
(
USER_ID VARCHAR(32) NOT NULL,
PERMISSION_ID VARCHAR(256) NOT NULL,
PRIMARY KEY (USER_ID, PERMISSION_ID)
);

View File

@ -26,6 +26,7 @@ taskana.ldap.userOrglevel3Attribute=someDepartement
taskana.ldap.userOrglevel4Attribute=orgLevel4
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=cn=groups
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupOfUniqueNames

View File

@ -202,6 +202,59 @@ class UserServiceAccTest {
domains -> Set.of(workbasketDomainB.getDomain()).equals(domains), "DOMAIN_B"));
}
@WithAccessId(user = "user-1-1")
@Test
void should_DetermineDomains_When_WorkbasketPermissionsExistForUsersWithPermissions()
throws Exception {
Workbasket workbasketDomainA =
defaultTestWorkbasket().buildAndStore(workbasketService, "businessadmin");
createAccessItem(
"permissions-domaina",
workbasketDomainA,
WorkbasketPermission.READ,
WorkbasketPermission.OPEN);
Workbasket workbasketDomainB =
defaultTestWorkbasket()
.domain("DOMAIN_B")
.buildAndStore(workbasketService, "businessadmin");
createAccessItem(
"permissions-domainb",
workbasketDomainB,
WorkbasketPermission.READ,
WorkbasketPermission.OPEN);
Set<User> users = new HashSet<>();
for (int i = 0; i < 6; i++) {
users.add(
randomTestUser()
.permissions(Set.of("test1", "test2", "permissions-domaina"))
.buildAndStore(userService, "businessadmin"));
}
for (int i = 0; i < 4; i++) {
users.add(
randomTestUser()
.permissions(Set.of("test1", "test2", "permissions-domainb"))
.buildAndStore(userService, "businessadmin"));
}
Set<String> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
List<User> returnedUsers = userService.getUsers(userIds);
assertThat(returnedUsers)
.extracting(User::getDomains)
.areExactly(
6,
new Condition<>(
domains ->
Set.of(workbasketDomainA.getDomain())
.equals(domains), "DOMAIN_A"))
.areExactly(
4,
new Condition<>(
domains ->
Set.of(workbasketDomainB.getDomain())
.equals(domains), "DOMAIN_B"));
}
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnAllUsers_When_TryingToGetUsersWithIdsExistingInCaps() throws Exception {
@ -312,6 +365,20 @@ class UserServiceAccTest {
assertThat(userToCreate).isNotSameAs(userInDatabase).isEqualTo(userInDatabase);
}
@WithAccessId(user = "businessadmin")
@Test
void should_InsertUserInDatabase_When_CreatingUserWithPermissions() throws Exception {
User userToCreate = userService.newUser();
userToCreate.setId("anton4");
userToCreate.setFirstName("Anton");
userToCreate.setLastName("Miller");
userToCreate.setPermissions(Set.of("perm1", "perm2"));
userService.createUser(userToCreate);
User userInDatabase = userService.getUser(userToCreate.getId());
assertThat(userToCreate).isNotSameAs(userInDatabase).isEqualTo(userInDatabase);
}
@WithAccessId(user = "businessadmin")
@TestFactory
Stream<DynamicTest>
@ -483,12 +550,15 @@ class UserServiceAccTest {
userToCreate.setFirstName("firstName");
userToCreate.setLastName("lastName");
userToCreate.setGroups(Set.of("GROUP1-ID-WITH-CAPS", "Group2-Id-With-Caps"));
userToCreate.setPermissions(Set.of("PERMISSION1-ID-WITH-CAPS", "Permission2-Id-With-Caps"));
User userInDatabase = userService.createUser(userToCreate);
assertThat(userInDatabase.getId()).isEqualTo("user-id-with-caps");
assertThat(userInDatabase.getGroups())
.containsExactlyInAnyOrder("group1-id-with-caps", "group2-id-with-caps");
assertThat(userInDatabase.getPermissions())
.containsExactlyInAnyOrder("permission1-id-with-caps", "permission2-id-with-caps");
}
}
@ -549,6 +619,36 @@ class UserServiceAccTest {
return DynamicTest.stream(testCases, Triplet::getLeft, test);
}
@WithAccessId(user = "businessadmin")
@TestFactory
Stream<DynamicTest> should_UpdatePermissions() {
Stream<Triplet<String, Set<String>, Set<String>>> testCases =
Stream.of(
Triplet.of(
"User has no permissions before updating", Set.of(), Set.of("perm1", "perm2")),
Triplet.of("new permissions differ all", Set.of("perm1"), Set.of("perm2", "perm3")),
Triplet.of("some new permissions differ", Set.of("perm1"), Set.of("perm1", "perm2")),
Triplet.of("new permissions are all the same", Set.of("perm1"), Set.of("perm1")));
ThrowingConsumer<Triplet<String, Set<String>, Set<String>>> test =
t -> {
Set<String> existingPerms = t.getMiddle();
Set<String> newPerms = t.getMiddle();
User userToUpdate = randomTestUser()
.permissions(existingPerms)
.buildAndStore(userService);
userToUpdate.setPermissions(newPerms);
userService.updateUser(userToUpdate);
User userInDatabase = userService.getUser(userToUpdate.getId());
assertThat(userInDatabase.getPermissions())
.containsExactlyInAnyOrderElementsOf(newPerms);
};
return DynamicTest.stream(testCases, Triplet::getLeft, test);
}
@WithAccessId(user = "user-1-1")
@Test
void should_ThrowNotAuthorizedException_When_TryingToUpdateUserWithNoAdminRole()
@ -722,6 +822,19 @@ class UserServiceAccTest {
.containsExactlyInAnyOrder("group1-id-with-caps", "group2-id-with-caps");
}
@WithAccessId(user = "businessadmin")
@Test
void should_MakePermissionsIdsLowerCase_When_UpdatingUserWithUpperCasePermissions()
throws Exception {
User userToUpdate = randomTestUser().buildAndStore(userService);
userToUpdate.setPermissions(Set.of("PERM1-ID-WITH-CAPS", "Permission2-Id-With-Caps"));
User updatedUser = userService.updateUser(userToUpdate);
assertThat(updatedUser.getPermissions())
.containsExactlyInAnyOrder("perm1-id-with-caps", "permission2-id-with-caps");
}
@WithAccessId(user = "businessadmin")
@Test
void should_ThrowInvalidArgumentException_When_TryingToUpdateUserWithFirstNameNull() {
@ -826,6 +939,22 @@ class UserServiceAccTest {
assertThat(userInDatabase.getGroups()).isEmpty();
}
@WithAccessId(user = "businessadmin")
@Test
void should_DeletePermissionsFromDatabase_When_UserHadPermissions() throws Exception {
User userToDelete =
randomTestUser()
.permissions(Set.of("permission1", "permission2"))
.buildAndStore(userService);
userService.deleteUser(userToDelete.getId());
// verify that groups are deleted by creating a new user with the same id and check its groups
User newUserWithSameId = randomTestUser().id(userToDelete.getId()).buildAndStore(userService);
User userInDatabase = userService.getUser(newUserWithSameId.getId());
assertThat(userInDatabase.getPermissions()).isEmpty();
}
@WithAccessId(user = "businessadmin")
@Test
void should_ThrowUserNotFoundException_When_TryingToDeleteUserWithNonExistingId() {
@ -913,6 +1042,25 @@ class UserServiceAccTest {
assertThat(userInDatabase.getDomains()).containsExactly(workbasket.getDomain());
}
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnOneDomain_When_PermissionHasSufficientMinimalPermissionsToAssignDomains()
throws Exception {
String permissionsId = UUID.randomUUID().toString();
User user =
randomTestUser()
.permissions(Set.of(permissionsId))
.buildAndStore(userService, "businessadmin");
Workbasket workbasket =
defaultTestWorkbasket().buildAndStore(workbasketService, "businessadmin");
createAccessItem(permissionsId,
workbasket, WorkbasketPermission.OPEN, WorkbasketPermission.READ);
User userInDatabase = userService.getUser(user.getId());
assertThat(userInDatabase.getDomains()).containsExactly(workbasket.getDomain());
}
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnEmptyDomains_When_UserHasSufficientPermissionsWhichThenGetRevoked()
@ -961,6 +1109,29 @@ class UserServiceAccTest {
assertThat(userInDatabase.getDomains()).isEmpty();
}
@WithAccessId(user = "businessadmin")
@Test
void should_ReturnEmptyDomains_When_GroupHasSufficientPermissionsAndThenPermissionIsUpdated()
throws Exception {
String groupId = UUID.randomUUID().toString();
User user = randomTestUser().permissions(Set.of(groupId)).buildAndStore(userService);
Workbasket workbasket = defaultTestWorkbasket().buildAndStore(workbasketService);
createAccessItem(groupId, workbasket, WorkbasketPermission.OPEN, WorkbasketPermission.READ);
User userInDatabase = userService.getUser(user.getId());
assertThat(userInDatabase.getDomains()).containsExactly(workbasket.getDomain());
// then user is updated and other group is assigned
user.setPermissions(Set.of("new group"));
userService.updateUser(user);
userInDatabase = userService.getUser(user.getId());
assertThat(userInDatabase.getDomains()).isEmpty();
}
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnMultipleDomains_When_UserHasSufficientMinimalPermissionsForMultipleDomains()
@ -1007,6 +1178,32 @@ class UserServiceAccTest {
.containsExactlyInAnyOrder(workbasket1.getDomain(), workbasket2.getDomain());
}
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnMultipleDomains_When_UserAndPermHaveSufficientMinimalPermsForMultipleDomains()
throws Exception {
String permissionId = UUID.randomUUID().toString();
User user =
randomTestUser()
.permissions(Set.of(permissionId))
.buildAndStore(userService, "businessadmin");
Workbasket workbasket1 =
defaultTestWorkbasket().buildAndStore(workbasketService, "businessadmin");
Workbasket workbasket2 =
defaultTestWorkbasket()
.domain("DOMAIN_B")
.buildAndStore(workbasketService, "businessadmin");
createAccessItem(
user.getId(), workbasket1, WorkbasketPermission.OPEN, WorkbasketPermission.READ);
createAccessItem(
permissionId, workbasket2, WorkbasketPermission.OPEN, WorkbasketPermission.READ);
User userInDatabase = userService.getUser(user.getId());
assertThat(userInDatabase.getDomains())
.containsExactlyInAnyOrder(workbasket1.getDomain(), workbasket2.getDomain());
}
@Nested
@TestInstance(Lifecycle.PER_CLASS)
class DifferentMinimalPermissionsToAssignDomains implements TaskanaConfigurationModifier {
@ -1050,6 +1247,27 @@ class UserServiceAccTest {
assertThat(userInDatabase.getDomains()).isEmpty();
}
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnEmptyDomains_When_PermHasInsufficientMinimalPermissionsToAssignDomains()
throws Exception {
String permissionId = UUID.randomUUID().toString();
User user =
randomTestUser()
.permissions(Set.of(permissionId))
.buildAndStore(userService, "businessadmin");
Workbasket workbasket =
defaultTestWorkbasket().buildAndStore(workbasketService, "businessadmin");
createAccessItem(permissionId,
workbasket,
WorkbasketPermission.OPEN,
WorkbasketPermission.READ);
User userInDatabase = userService.getUser(user.getId());
assertThat(userInDatabase.getDomains()).isEmpty();
}
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnOneDomain_When_UserHasSufficientMinimalPermissionsToAssignDomains()
@ -1081,6 +1299,7 @@ class UserServiceAccTest {
assertThat(userInDatabase.getDomains()).containsExactly(workbasket.getDomain());
}
}
@Nested
@ -1124,6 +1343,27 @@ class UserServiceAccTest {
assertThat(userInDatabase.getDomains()).isEmpty();
}
@WithAccessId(user = "user-1-1")
@Test
void should_ReturnEmptyDomains_When_PropertyIsNotSetAndPermission()
throws Exception {
String permissionId = UUID.randomUUID().toString();
User user =
randomTestUser()
.permissions(Set.of(permissionId))
.buildAndStore(userService, "businessadmin");
Workbasket workbasket =
defaultTestWorkbasket().buildAndStore(workbasketService, "businessadmin");
createAccessItem(permissionId,
workbasket,
WorkbasketPermission.OPEN,
WorkbasketPermission.READ);
User userInDatabase = userService.getUser(user.getId());
assertThat(userInDatabase.getDomains()).isEmpty();
}
}
}
}

View File

@ -34,6 +34,20 @@ public interface User {
*/
void setGroups(Set<String> groups);
/**
* Returns the permissions of the User.
*
* @return permissions
*/
Set<String> getPermissions();
/**
* Sets the permissions of the User.
*
* @param permissions the permissions of the User
*/
void setPermissions(Set<String> permissions);
/**
* Returns the first name of the User.
*

View File

@ -16,6 +16,8 @@ public interface UserMapper {
@SelectProvider(type = UserMapperSqlProvider.class, method = "findById")
@Result(property = "id", column = "USER_ID")
@Result(property = "groups", column = "USER_ID", many = @Many(select = "findGroupsById"))
@Result(property = "permissions", column = "USER_ID",
many = @Many(select = "findPermissionsById"))
@Result(property = "firstName", column = "FIRST_NAME")
@Result(property = "lastName", column = "LASTNAME")
@Result(property = "fullName", column = "FULL_NAME")
@ -32,6 +34,8 @@ public interface UserMapper {
@Result(property = "id", column = "USER_ID")
@Result(property = "groups", column = "USER_ID", many = @Many(select = "findGroupsById"))
@Result(property = "permissions", column = "USER_ID",
many = @Many(select = "findPermissionsById"))
@Result(property = "firstName", column = "FIRST_NAME")
@Result(property = "lastName", column = "LASTNAME")
@Result(property = "fullName", column = "FULL_NAME")
@ -50,6 +54,9 @@ public interface UserMapper {
@SelectProvider(type = UserMapperSqlProvider.class, method = "findGroupsById")
Set<String> findGroupsById(String id);
@SelectProvider(type = UserMapperSqlProvider.class, method = "findPermissionsById")
Set<String> findPermissionsById(String id);
@InsertProvider(type = UserMapperSqlProvider.class, method = "insert")
void insert(User user);
@ -60,6 +67,13 @@ public interface UserMapper {
@InsertProvider(type = UserMapperSqlProvider.class, method = "insertGroups")
void insertGroups(User user);
@InsertProvider(
type = UserMapperSqlProvider.class,
method = "insertPermissionsOracle",
databaseId = "oracle")
@InsertProvider(type = UserMapperSqlProvider.class, method = "insertPermissions")
void insertPermissions(User user);
@UpdateProvider(type = UserMapperSqlProvider.class, method = "update")
void update(User user);
@ -68,4 +82,7 @@ public interface UserMapper {
@DeleteProvider(type = UserMapperSqlProvider.class, method = "deleteGroups")
void deleteGroups(String id);
@DeleteProvider(type = UserMapperSqlProvider.class, method = "deletePermissions")
void deletePermissions(String id);
}

View File

@ -42,6 +42,13 @@ public class UserMapperSqlProvider {
+ CLOSING_SCRIPT_TAG;
}
public static String findPermissionsById() {
return OPENING_SCRIPT_TAG
+ "SELECT PERMISSION_ID FROM PERMISSION_INFO WHERE USER_ID = #{id} "
+ DB2_WITH_UR
+ CLOSING_SCRIPT_TAG;
}
public static String insert() {
return "INSERT INTO USER_INFO ( " + USER_INFO_COLUMNS + ") VALUES(" + USER_INFO_VALUES + ")";
}
@ -65,6 +72,26 @@ public class UserMapperSqlProvider {
+ CLOSING_SCRIPT_TAG;
}
public static String insertPermissions() {
return OPENING_SCRIPT_TAG
+ "INSERT INTO PERMISSION_INFO (USER_ID, PERMISSION_ID) VALUES "
+ "<foreach item='permission' collection='permissions' "
+ "open='(' separator='),(' close=')'>"
+ "#{id}, #{permission}"
+ "</foreach> "
+ CLOSING_SCRIPT_TAG;
}
public static String insertPermissionsOracle() {
return OPENING_SCRIPT_TAG
+ "INSERT ALL "
+ "<foreach item='permission' collection='permissions' separator='\n'>"
+ "INTO PERMISSION_INFO (USER_ID, PERMISSION_ID) VALUES ( #{id}, #{permission} )"
+ "</foreach> "
+ "SELECT 1 FROM DUAL"
+ CLOSING_SCRIPT_TAG;
}
public static String update() {
return "UPDATE USER_INFO "
+ "SET FIRST_NAME = #{firstName}, "
@ -82,4 +109,8 @@ public class UserMapperSqlProvider {
public static String deleteGroups() {
return "DELETE FROM GROUP_INFO WHERE USER_ID = #{id} ";
}
public static String deletePermissions() {
return "DELETE FROM PERMISSION_INFO WHERE USER_ID = #{id} ";
}
}

View File

@ -122,11 +122,18 @@ public class UserServiceImpl implements UserService {
internalTaskanaEngine.executeInDatabaseConnection(() -> userMapper.update(userToUpdate));
internalTaskanaEngine.executeInDatabaseConnection(
() -> userMapper.deleteGroups(userToUpdate.getId()));
() -> {
userMapper.deleteGroups(userToUpdate.getId());
userMapper.deletePermissions(userToUpdate.getId());
});
if (userToUpdate.getGroups() != null && !userToUpdate.getGroups().isEmpty()) {
internalTaskanaEngine.executeInDatabaseConnection(
() -> userMapper.insertGroups(userToUpdate));
}
if (userToUpdate.getPermissions() != null && !userToUpdate.getPermissions().isEmpty()) {
internalTaskanaEngine.executeInDatabaseConnection(
() -> userMapper.insertPermissions(userToUpdate));
}
((UserImpl) userToUpdate).setDomains(determineDomains(userToUpdate));
if (LOGGER.isDebugEnabled()) {
@ -150,6 +157,7 @@ public class UserServiceImpl implements UserService {
() -> {
userMapper.delete(id);
userMapper.deleteGroups(id);
userMapper.deletePermissions(id);
});
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Method deleteUser() deleted User with id '{}'.", id);
@ -158,6 +166,7 @@ public class UserServiceImpl implements UserService {
private Set<String> determineDomains(User user) {
Set<String> accessIds = new HashSet<>(user.getGroups());
accessIds.addAll(user.getPermissions());
accessIds.add(user.getId());
if (minimalWorkbasketPermissions != null && !minimalWorkbasketPermissions.isEmpty()) {
// since WorkbasketService#accessIdsHavePermissions requires some role permissions we have to
@ -186,6 +195,9 @@ public class UserServiceImpl implements UserService {
if (userToCreate.getGroups() != null && !userToCreate.getGroups().isEmpty()) {
userMapper.insertGroups(userToCreate);
}
if (userToCreate.getPermissions() != null && !userToCreate.getPermissions().isEmpty()) {
userMapper.insertPermissions(userToCreate);
}
} catch (PersistenceException e) {
throw new UserAlreadyExistException(userToCreate.getId(), e);
} finally {
@ -214,6 +226,8 @@ public class UserServiceImpl implements UserService {
user.setId(user.getId().toLowerCase());
user.setGroups(
user.getGroups().stream().map((String::toLowerCase)).collect(Collectors.toSet()));
user.setPermissions(
user.getPermissions().stream().map((String::toLowerCase)).collect(Collectors.toSet()));
}
}
@ -235,6 +249,8 @@ public class UserServiceImpl implements UserService {
newUser.setId(newUser.getId().toLowerCase());
newUser.setGroups(
newUser.getGroups().stream().map((String::toLowerCase)).collect(Collectors.toSet()));
newUser.setPermissions(
newUser.getPermissions().stream().map((String::toLowerCase)).collect(Collectors.toSet()));
}
}
}

View File

@ -8,6 +8,7 @@ import pro.taskana.user.api.models.User;
public class UserImpl implements User {
private String id;
private Set<String> groups = Collections.emptySet();
private Set<String> permissions = Collections.emptySet();
private String firstName;
private String lastName;
private String fullName;
@ -27,6 +28,7 @@ public class UserImpl implements User {
protected UserImpl(UserImpl copyFrom) {
this.id = copyFrom.id;
this.groups = copyFrom.groups;
this.permissions = copyFrom.permissions;
this.firstName = copyFrom.firstName;
this.lastName = copyFrom.lastName;
this.fullName = copyFrom.fullName;
@ -62,6 +64,16 @@ public class UserImpl implements User {
this.groups = groups;
}
@Override
public Set<String> getPermissions() {
return permissions;
}
@Override
public void setPermissions(Set<String> permissions) {
this.permissions = permissions;
}
@Override
public String getFirstName() {
return firstName;
@ -201,6 +213,7 @@ public class UserImpl implements User {
return Objects.hash(
id,
groups,
permissions,
firstName,
lastName,
fullName,
@ -230,6 +243,7 @@ public class UserImpl implements User {
UserImpl other = (UserImpl) obj;
return Objects.equals(id, other.id)
&& Objects.equals(groups, other.groups)
&& Objects.equals(permissions, other.permissions)
&& Objects.equals(firstName, other.firstName)
&& Objects.equals(lastName, other.lastName)
&& Objects.equals(fullName, other.fullName)
@ -251,6 +265,8 @@ public class UserImpl implements User {
+ id
+ ", groups="
+ groups
+ ", permissions="
+ permissions
+ ", firstName="
+ firstName
+ ", lastName="

View File

@ -28,6 +28,11 @@ public class UserBuilder implements EntityBuilder<User, UserService> {
return this;
}
public UserBuilder permissions(Set<String> permissions) {
testUser.setPermissions(permissions);
return this;
}
public UserBuilder firstName(String firstName) {
testUser.setFirstName(firstName);
return this;

View File

@ -75,6 +75,8 @@ taskana.ldap.userOrglevel2Attribute=orgLevel2
taskana.ldap.userOrglevel3Attribute=someDepartement
taskana.ldap.userOrglevel4Attribute=orgLevel4
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=cn=groups
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupOfUniqueNames

View File

@ -53,6 +53,8 @@ taskana.ldap.userOrglevel2Attribute=orgLevel2
taskana.ldap.userOrglevel3Attribute=someDepartement
taskana.ldap.userOrglevel4Attribute=orgLevel4
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=ou=groups
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupOfUniqueNames

View File

@ -71,6 +71,7 @@ taskana.ldap.userOrglevel3Attribute=someDepartement
taskana.ldap.userOrglevel4Attribute=orgLevel4
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupofuniquenames

View File

@ -16,6 +16,11 @@ cn: users
objectclass: top
objectclass: container
dn: cn=other-users,OU=Test,O=TASKANA
cn: users
objectclass: top
objectclass: container
dn: cn=organisation,OU=Test,O=TASKANA
cn: organisation
objectclass: top
@ -91,7 +96,7 @@ description: desc
phoneNumber: 012345678
mobileNumber: 09876554321
email: Titus.Toll@taskana.de
orgLevel1: QWERT
orgLevel1: ABC
orgLevel2: DEF/GHI
someDepartement: JKL
orgLevel4: MNO/PQR
@ -104,6 +109,8 @@ sn: Toll
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 1
cn: Titus Toll
userPassword: teamlead-1
permission: organize
permission: inet
dn: uid=user-1-1,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -119,6 +126,8 @@ sn: Mustermann
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 1
cn: Max Mustermann
userPassword: user-1-1
permission: organize
permission: inet
dn: uid=user-1-2,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -134,6 +143,9 @@ sn: Eifrig
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 1
cn: Elena Eifrig
userPassword: user-1-2
permission: organize
permission: inet
permission: program
dn: uid=user-1-3,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -211,6 +223,9 @@ sn: Bach
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 2
cn: Thomas Bach
userPassword: user-2-3
permission: organize
permission: inet
permission: program
dn: uid=user-2-4,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -267,6 +282,9 @@ sn: Meyer
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 2
cn: Wiebke Meyer
userPassword: user-2-7
permission: organize
permission: inet
permission: manage
dn: uid=user-2-8,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -352,6 +370,26 @@ sn: Bio
ou: Organisationseinheit/Organisationseinheit B
cn: Brunhilde Bio
userPassword: user-b-2
permission: organize
permission: inet
permission: siegen
permission: frieden
########################
# Users in other cn
########################
dn: uid=otheruser,cn=other-users,OU=Test,O=TASKANA
objectclass: inetorgperson
objectclass: organizationalperson
objectclass: person
objectclass: top
givenName: Other
description: User in other cn than search root
uid: otheruser
sn: User
ou: Other
cn: Other User
userPassword: otheruser
########################

View File

@ -30,6 +30,7 @@ taskana.ldap.userOrglevel3Attribute=someDepartement
taskana.ldap.userOrglevel4Attribute=orgLevel4
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=cn=groups
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupOfUniqueNames

View File

@ -22,6 +22,7 @@ taskana.ldap.groupNameAttribute=cn
taskana.ldap.minSearchForLength=3
taskana.ldap.maxNumberOfReturnedAccessIds=50
taskana.ldap.groupsOfUser=memberUid
taskana.ldap.userPermissionsAttribute=permission
####### JobScheduler cron expression that specifies when the JobSchedler runs
taskana.jobscheduler.async.cron=0 * * * * *

View File

@ -16,6 +16,7 @@ taskana.ldap.userLastnameAttribute=sn
taskana.ldap.userFullnameAttribute=cn
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupofuniquenames

View File

@ -23,6 +23,7 @@ taskana.ldap.userOrglevel3Attribute=someDepartement
taskana.ldap.userOrglevel4Attribute=orgLevel4
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupOfUniqueNames

View File

@ -21,6 +21,7 @@ taskana.ldap.userOrglevel3Attribute=someDepartement
taskana.ldap.userOrglevel4Attribute=orgLevel4
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupOfUniqueNames

View File

@ -109,6 +109,8 @@ sn: Toll
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 1
cn: Titus Toll
userPassword: teamlead-1
permission: organize
permission: inet
dn: uid=user-1-1,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -124,6 +126,8 @@ sn: Mustermann
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 1
cn: Max Mustermann
userPassword: user-1-1
permission: organize
permission: inet
dn: uid=user-1-2,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -139,6 +143,9 @@ sn: Eifrig
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 1
cn: Elena Eifrig
userPassword: user-1-2
permission: organize
permission: inet
permission: program
dn: uid=user-1-3,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -216,6 +223,9 @@ sn: Bach
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 2
cn: Thomas Bach
userPassword: user-2-3
permission: organize
permission: inet
permission: program
dn: uid=user-2-4,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -272,6 +282,9 @@ sn: Meyer
ou: Organisationseinheit/Organisationseinheit KSC/Organisationseinheit KSC 2
cn: Wiebke Meyer
userPassword: user-2-7
permission: organize
permission: inet
permission: manage
dn: uid=user-2-8,cn=users,OU=Test,O=TASKANA
objectclass: inetorgperson
@ -357,6 +370,10 @@ sn: Bio
ou: Organisationseinheit/Organisationseinheit B
cn: Brunhilde Bio
userPassword: user-b-2
permission: organize
permission: inet
permission: siegen
permission: frieden
########################
# Users in other cn

View File

@ -413,6 +413,10 @@ public class LdapClient {
return LdapSettings.TASKANA_LDAP_USER_MEMBER_OF_GROUP_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserPermissionsAttribute() {
return LdapSettings.TASKANA_LDAP_USER_PERMISSIONS_ATTRIBUTE.getValueFromEnv(env);
}
public String getGroupSearchBase() {
return LdapSettings.TASKANA_LDAP_GROUP_SEARCH_BASE.getValueFromEnv(env);
}
@ -538,6 +542,7 @@ public class LdapClient {
return new String[] {
getUserIdAttribute(),
getUserMemberOfGroupAttribute(),
getUserPermissionsAttribute(),
getUserFirstnameAttribute(),
getUserLastnameAttribute(),
getUserFullnameAttribute(),
@ -630,6 +635,20 @@ public class LdapClient {
return groups;
}
private Set<String> getPermissionIdsFromContext(final DirContextOperations context) {
String[] permissionAttributes = context.getStringAttributes(getUserPermissionsAttribute());
Set<String> permissions =
permissionAttributes != null ? Set.of(permissionAttributes) : Collections.emptySet();
if (useLowerCaseForAccessIds) {
permissions =
permissions.stream()
.filter(Objects::nonNull)
.map(String::toLowerCase)
.collect(Collectors.toSet());
}
return permissions;
}
/** Context Mapper for user entries. */
class GroupContextMapper extends AbstractContextMapper<AccessIdRepresentationModel> {
@ -650,6 +669,7 @@ public class LdapClient {
final User user = new UserImpl();
user.setId(getUserIdFromContext(context));
user.setGroups(getGroupIdsFromContext(context));
user.setPermissions(getPermissionIdsFromContext(context));
user.setFirstName(context.getStringAttribute(getUserFirstnameAttribute()));
user.setLastName(context.getStringAttribute(getUserLastnameAttribute()));
user.setFullName(context.getStringAttribute(getUserFullnameAttribute()));

View File

@ -19,6 +19,7 @@ enum LdapSettings {
TASKANA_LDAP_USER_ORG_LEVEL_3_ATTRIBUTE("taskana.ldap.userOrglevel3Attribute"),
TASKANA_LDAP_USER_ORG_LEVEL_4_ATTRIBUTE("taskana.ldap.userOrglevel4Attribute"),
TASKANA_LDAP_USER_MEMBER_OF_GROUP_ATTRIBUTE("taskana.ldap.userMemberOfGroupAttribute"),
TASKANA_LDAP_USER_PERMISSIONS_ATTRIBUTE("taskana.ldap.userPermissionsAttribute"),
TASKANA_LDAP_GROUP_SEARCH_BASE("taskana.ldap.groupSearchBase"),
TASKANA_LDAP_BASE_DN("taskana.ldap.baseDn"),
TASKANA_LDAP_GROUP_SEARCH_FILTER_NAME("taskana.ldap.groupSearchFilterName"),

View File

@ -62,7 +62,7 @@ public class UserInfoRefreshJob extends AbstractTaskanaJob {
.map(refreshUserPostprocessorManager::processUserAfterRefresh)
.collect(Collectors.toList());
addExistingConfigurationDataToUsers(usersAfterProcessing);
clearExistingUsersAndGroups();
clearExistingUsersAndGroupsAndPermissions();
insertNewUsers(usersAfterProcessing);
LOGGER.info("Job to refresh all user info has finished.");
@ -72,18 +72,18 @@ public class UserInfoRefreshJob extends AbstractTaskanaJob {
}
}
private void clearExistingUsersAndGroups() {
private void clearExistingUsersAndGroupsAndPermissions() {
sqlConnectionRunner.runWithConnection(
connection -> {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Trying to delete all users and groups");
LOGGER.debug("Trying to delete all users, groups and permissions");
}
String sql = "DELETE FROM USER_INFO; DELETE FROM GROUP_INFO";
String sql = "DELETE FROM USER_INFO; DELETE FROM GROUP_INFO; DELETE FROM PERMISSION_INFO";
PreparedStatement statement = connection.prepareStatement(sql);
statement.execute();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Successfully deleted all users and groups");
LOGGER.debug("Successfully deleted all users, groups and permissions");
}
if (!connection.getAutoCommit()) {

View File

@ -22,6 +22,7 @@ public class UserRepresentationModelAssembler
UserRepresentationModel repModel = new UserRepresentationModel();
repModel.setUserId(entity.getId());
repModel.setGroups(entity.getGroups());
repModel.setPermissions(entity.getPermissions());
repModel.setFirstName(entity.getFirstName());
repModel.setLastName(entity.getLastName());
repModel.setFullName(entity.getFullName());
@ -43,6 +44,7 @@ public class UserRepresentationModelAssembler
UserImpl user = new UserImpl();
user.setId(repModel.getUserId());
user.setGroups(repModel.getGroups());
user.setPermissions(repModel.getPermissions());
user.setFirstName(repModel.getFirstName());
user.setLastName(repModel.getLastName());
user.setFullName(repModel.getFullName());

View File

@ -14,6 +14,8 @@ public class UserRepresentationModel extends RepresentationModel<UserRepresentat
@NotNull private String userId;
/** The groups of the User. */
private Set<String> groups;
/** The permissions of the User. */
private Set<String> permissions;
/**
* The domains of the User.
*
@ -62,6 +64,14 @@ public class UserRepresentationModel extends RepresentationModel<UserRepresentat
this.groups = groups;
}
public Set<String> getPermissions() {
return permissions;
}
public void setPermissions(Set<String> permissions) {
this.permissions = permissions;
}
public String getFirstName() {
return firstName;
}
@ -172,6 +182,7 @@ public class UserRepresentationModel extends RepresentationModel<UserRepresentat
super.hashCode(),
userId,
groups,
permissions,
domains,
firstName,
lastName,
@ -201,6 +212,7 @@ public class UserRepresentationModel extends RepresentationModel<UserRepresentat
UserRepresentationModel other = (UserRepresentationModel) obj;
return Objects.equals(userId, other.userId)
&& Objects.equals(groups, other.groups)
&& Objects.equals(permissions, other.permissions)
&& Objects.equals(domains, other.domains)
&& Objects.equals(firstName, other.firstName)
&& Objects.equals(lastName, other.lastName)

View File

@ -215,6 +215,7 @@ class LdapClientTest {
{"taskana.ldap.userSearchFilterName", "objectclass"},
{"taskana.ldap.groupsOfUser", "memberUid"},
{"taskana.ldap.groupNameAttribute", "cn"},
{"taskana.ldap.userPermissionsAttribute", "permission"},
{"taskana.ldap.groupSearchFilterValue", "groupOfUniqueNames"},
{"taskana.ldap.groupSearchFilterName", "objectclass"},
{"taskana.ldap.groupSearchBase", "ou=groups"},
@ -230,7 +231,7 @@ class LdapClientTest {
{"taskana.ldap.userOrglevel1Attribute", "orgLevel1"},
{"taskana.ldap.userOrglevel2Attribute", "orgLevel2"},
{"taskana.ldap.userOrglevel3Attribute", "orgLevel3"},
{"taskana.ldap.userOrglevel4Attribute", "orgLevel4"},
{"taskana.ldap.userOrglevel4Attribute", "orgLevel4"}
})
.forEach(
strings ->

View File

@ -91,6 +91,23 @@ class UserInfoRefreshJobIntTest {
.hasSameSizeAs(ldapGroups)
.containsExactlyElementsOf(ldapGroups);
}
// validate permissions
for (int i = 0; i < users.size(); i++) {
User user = users.get(i);
List<String> permissionIds = getPermissionInfo(connection, user.getId());
permissionIds.sort(Comparator.naturalOrder());
User ldapUser = ldapusers.get(i);
List<String> ldapPermissions =
ldapUser.getPermissions().stream()
.sorted(Comparator.naturalOrder())
.collect(Collectors.toList());
assertThat(permissionIds)
.hasSameSizeAs(ldapPermissions)
.containsExactlyElementsOf(ldapPermissions);
}
}
}
@ -142,4 +159,19 @@ class UserInfoRefreshJobIntTest {
}
return groupIds;
}
private List<String> getPermissionInfo(Connection connection, String userId) throws Exception {
List<String> permissionIds = new ArrayList<>();
PreparedStatement ps =
connection.prepareStatement(
"SELECT permission_id FROM "
+ connection.getSchema()
+ ".permission_info WHERE user_id = ?");
ps.setString(1, userId);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
permissionIds.add(rs.getString(1));
}
return permissionIds;
}
}

View File

@ -184,6 +184,7 @@ class UserControllerIntTest {
UserRepresentationModel newUser = new UserRepresentationModel();
newUser.setUserId("12345");
newUser.setGroups(Set.of("group1", "group2"));
newUser.setPermissions(Set.of("perm1", "perm2"));
newUser.setFirstName("Hans");
newUser.setLastName("Georg");
newUser.setFullName("Georg, Hans");

View File

@ -29,6 +29,7 @@ class UserRepresentationModelAssemblerTest {
UserImpl user = (UserImpl) userService.newUser();
user.setId("user-1-2");
user.setGroups(Set.of("group1", "group2"));
user.setPermissions(Set.of("perm1", "perm2"));
user.setFirstName("Hans");
user.setLastName("Georg");
user.setFullName("Hans Georg");
@ -52,6 +53,7 @@ class UserRepresentationModelAssemblerTest {
UserRepresentationModel repModel = new UserRepresentationModel();
repModel.setUserId("user-1-2");
repModel.setGroups(Set.of("group1", "group2"));
repModel.setPermissions(Set.of("perm1", "perm2"));
repModel.setFirstName("Hans");
repModel.setLastName("Georg");
repModel.setFullName("Hans Georg");
@ -75,6 +77,7 @@ class UserRepresentationModelAssemblerTest {
UserImpl user = (UserImpl) userService.newUser();
user.setId("user-1-2");
user.setGroups(Set.of("group1", "group2"));
user.setPermissions(Set.of("perm1", "perm2"));
user.setFirstName("Hans");
user.setLastName("Georg");
user.setFullName("Hans Georg");
@ -104,6 +107,7 @@ class UserRepresentationModelAssemblerTest {
assertThat(entity.getId()).isEqualTo(repModel.getUserId());
assertThat(entity.getGroups()).isEqualTo(repModel.getGroups());
assertThat(entity.getPermissions()).isEqualTo(repModel.getPermissions());
assertThat(entity.getFirstName()).isEqualTo(repModel.getFirstName());
assertThat(entity.getLastName()).isEqualTo(repModel.getLastName());
assertThat(entity.getFullName()).isEqualTo(repModel.getFullName());

View File

@ -28,6 +28,7 @@ taskana.ldap.userOrglevel3Attribute=someDepartement
taskana.ldap.userOrglevel4Attribute=orgLevel4
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupOfUniqueNames

View File

@ -34,6 +34,7 @@ taskana.ldap.userOrglevel3Attribute=someDepartement
taskana.ldap.userOrglevel4Attribute=orgLevel4
taskana.ldap.userIdAttribute=uid
taskana.ldap.userMemberOfGroupAttribute=memberOf
taskana.ldap.userPermissionsAttribute=permission
taskana.ldap.groupSearchBase=
taskana.ldap.groupSearchFilterName=objectclass
taskana.ldap.groupSearchFilterValue=groupOfUniqueNames