diff --git a/datastore/src/main/java/net/jami/datastore/dao/GroupDao.java b/datastore/src/main/java/net/jami/datastore/dao/GroupDao.java index e33bc036bd6ae74f8c9003a62f7d8accd23c3787..045136c89bac0c790a8ac83c9bf4f366cef8f8fd 100644 --- a/datastore/src/main/java/net/jami/datastore/dao/GroupDao.java +++ b/datastore/src/main/java/net/jami/datastore/dao/GroupDao.java @@ -22,7 +22,7 @@ public class GroupDao extends AbstractDao<Group>{ SQLConnection connection = DataStore.connectionPool.getConnection(); try{ PreparedStatement ps = connection.getConnection().prepareStatement("INSERT INTO groups " + - "(name, blueprint) VALUES (?, ?)"); + "(id, name, blueprint) VALUES (?, ?, ?)"); ps = object.getInsert(ps); return ps.executeUpdate() != 0; } @@ -38,17 +38,17 @@ public class GroupDao extends AbstractDao<Group>{ @Override public boolean updateObject(StatementList update, StatementList constraints) { - String name = update.getStatements().get(0).getValue(); - String blueprint = update.getStatements().get(1).getValue(); - String oldName = constraints.getStatements().get(0).getValue(); + String id = update.getStatements().get(0).getValue(); + String name = update.getStatements().get(1).getValue(); + String blueprint = update.getStatements().get(2).getValue(); SQLConnection connection = DataStore.connectionPool.getConnection(); try{ - PreparedStatement ps = connection.getConnection().prepareStatement("UPDATE groups SET name = ?, blueprint = ? WHERE name = ?"); + PreparedStatement ps = connection.getConnection().prepareStatement("UPDATE groups SET name = ?, blueprint = ? WHERE id = ?"); ps.setString(1, name); ps.setString(2, blueprint); - ps.setString(3, oldName); + ps.setString(3, id); return ps.executeUpdate() != 0; } catch (SQLException e){ @@ -63,12 +63,12 @@ public class GroupDao extends AbstractDao<Group>{ @Override public boolean deleteObject(StatementList constraints) { - String name = constraints.getStatements().get(0).getValue(); + String id = constraints.getStatements().get(0).getValue(); SQLConnection connection = DataStore.connectionPool.getConnection(); try { - PreparedStatement ps = connection.getConnection().prepareStatement("DELETE FROM groups WHERE name = ?"); - ps.setString(1, name); + PreparedStatement ps = connection.getConnection().prepareStatement("DELETE FROM groups WHERE id = ?"); + ps.setString(1, id); return ps.executeUpdate() != 0; } catch (SQLException e){ log.error("An error has occurred while trying to delete a group: " + e.toString()); diff --git a/datastore/src/main/java/net/jami/datastore/dao/UserGroupMappingsDao.java b/datastore/src/main/java/net/jami/datastore/dao/UserGroupMappingsDao.java index 9e576cf818e59272bd9cceeccfa37a742691810d..c58b31aa1cfc694e794c18195c59bdb36ebdc107 100644 --- a/datastore/src/main/java/net/jami/datastore/dao/UserGroupMappingsDao.java +++ b/datastore/src/main/java/net/jami/datastore/dao/UserGroupMappingsDao.java @@ -7,6 +7,7 @@ import net.jami.jams.common.dao.connectivity.SQLConnection; import net.jami.jams.common.objects.user.UserGroupMapping; import java.sql.PreparedStatement; +import java.sql.SQLException; @Slf4j public class UserGroupMappingsDao extends AbstractDao<UserGroupMapping>{ @@ -22,7 +23,7 @@ public class UserGroupMappingsDao extends AbstractDao<UserGroupMapping>{ SQLConnection connection = DataStore.connectionPool.getConnection(); try { PreparedStatement ps = connection.getConnection().prepareStatement("INSERT INTO usergroupmappings " + - "(username, groups)" + " VALUES " + "(?,?)"); + "(username, groupId)" + " VALUES " + "(?,?)"); ps = object.getInsert(ps); return ps.executeUpdate() != 0; } catch (Exception e) { @@ -34,22 +35,30 @@ public class UserGroupMappingsDao extends AbstractDao<UserGroupMapping>{ } @Override - public boolean updateObject(StatementList update, StatementList constraints) { + public boolean deleteObject(StatementList constraints) { - String groups = update.getStatements().get(0).getValue(); String username = constraints.getStatements().get(0).getValue(); - + String groupId = constraints.getStatements().get(1).getValue(); SQLConnection connection = DataStore.connectionPool.getConnection(); + try { - PreparedStatement ps = connection.getConnection().prepareStatement("UPDATE usergroupmappings SET groups = ? WHERE username = ?"); - ps.setString(1, groups); - ps.setString(2, username); - return ps.executeUpdate() != 0; - } catch (Exception e) { - log.error("An error has occurred while trying to update a user profile: " + e.toString()); + if(username.equals("*")){ + PreparedStatement ps = connection.getConnection().prepareStatement("DELETE FROM usergroupmappings WHERE groupId = ?"); + ps.setString(1, groupId); + return ps.executeUpdate() != 0; + } + else { + PreparedStatement ps = connection.getConnection().prepareStatement("DELETE FROM usergroupmappings WHERE username = ? AND groupId = ?"); + ps.setString(1, username); + ps.setString(2, groupId); + return ps.executeUpdate() != 0; + } + } catch (SQLException e){ + log.error("An error has occurred while trying to delete a group member: " + e.toString()); return false; } finally { DataStore.connectionPool.returnConnection(connection); } } + } diff --git a/datastore/src/main/java/net/jami/datastore/dao/UserProfileDao.java b/datastore/src/main/java/net/jami/datastore/dao/UserProfileDao.java index 56d06de63995072ce7e05a415638e76f42ec586a..7fa7a52e4e1489f464f6a026576aba2264e7ad2c 100644 --- a/datastore/src/main/java/net/jami/datastore/dao/UserProfileDao.java +++ b/datastore/src/main/java/net/jami/datastore/dao/UserProfileDao.java @@ -46,8 +46,8 @@ public class UserProfileDao extends AbstractDao<UserProfile> { SQLConnection connection = DataStore.connectionPool.getConnection(); try { PreparedStatement ps = connection.getConnection().prepareStatement("INSERT INTO local_directory " + - "(username, firstName, lastName, email, profilePicture, organization, phoneNumber, phoneNumberExtension, faxNumber, mobileNumber, groupMemberships)" + - " VALUES " + "(?,?,?,?,?,?,?,?,?,?,?)"); + "(username, firstName, lastName, email, profilePicture, organization, phoneNumber, phoneNumberExtension, faxNumber, mobileNumber)" + + " VALUES " + "(?,?,?,?,?,?,?,?,?,?)"); ps = object.getInsert(ps); return ps.executeUpdate() != 0; } catch (Exception e) { @@ -63,7 +63,7 @@ public class UserProfileDao extends AbstractDao<UserProfile> { SQLConnection connection = DataStore.connectionPool.getConnection(); try { - PreparedStatement ps = connection.getConnection().prepareStatement("UPDATE local_directory SET firstname = ?, lastName = ?, email = ?, profilePicture = ?, organization = ?, phoneNumber = ?, phoneNumberExtension = ?, faxNumber = ?, mobileNumber = ?, groupMemberships = ? WHERE username = ?"); + PreparedStatement ps = connection.getConnection().prepareStatement("UPDATE local_directory SET firstname = ?, lastName = ?, email = ?, profilePicture = ?, organization = ?, phoneNumber = ?, phoneNumberExtension = ?, faxNumber = ?, mobileNumber = ? WHERE username = ?"); for (int i = 1; i < update.getStatements().size(); i++) { ps.setString(i, update.getStatements().get(i).getValue()); } diff --git a/datastore/src/main/java/net/jami/datastore/main/DataStore.java b/datastore/src/main/java/net/jami/datastore/main/DataStore.java index 15cb8c23b8b6e5ec908a80eb113790ac45b51a2f..ae4c229c3d80b6f17cfad1718564c34033823ead 100644 --- a/datastore/src/main/java/net/jami/datastore/main/DataStore.java +++ b/datastore/src/main/java/net/jami/datastore/main/DataStore.java @@ -144,27 +144,6 @@ public class DataStore implements AuthenticationSource { return userProfileDao.storeObject(userProfile); } - public boolean updateUserGroupMappings (UserProfile userProfile) { - StatementList statementList = new StatementList(); - statementList.addStatement(new StatementElement("username", "=", userProfile.getUsername(), "")); - if (getUserGroupMappingsDao().getObjects(statementList) != null && !getUserGroupMappingsDao().getObjects(statementList).isEmpty()) { - UserGroupMapping mapping = getUserGroupMappingsDao().getObjects(statementList).get(0); - List<String> list = new ArrayList<>(); - if (mapping.getUsername().equals(userProfile.getUsername())) { - String[] splits = mapping.getGroups().split(","); - - for (int i = 0; i < splits.length; i++) - list.add(splits[i]); - } - if (!list.isEmpty()) - userProfile.setGroupMemberships(list); - - return (updateUserProfile(userProfile)); - } - - return true; - } - public boolean updateUserProfile(UserProfile userProfile){ StatementList update = new StatementList(); @@ -178,18 +157,6 @@ public class DataStore implements AuthenticationSource { update.addStatement(new StatementElement("phoneNumberExtension","=",userProfile.getPhoneNumberExtension(),"")); update.addStatement(new StatementElement("faxNumber","=",userProfile.getFaxNumber(),"")); update.addStatement(new StatementElement("mobileNumber","=",userProfile.getMobileNumber(),"")); - if (userProfile.getGroupMemberships() != null && !userProfile.getGroupMemberships().isEmpty()) { - String groups = ""; - for (String s: userProfile.getGroupMemberships()) { - if (groups.equals("")) - groups = s; - else - groups = groups.concat("," + s); - } - update.addStatement(new StatementElement("groupMemberships","=", groups ,"")); - } else - update.addStatement(new StatementElement("groupMemberships","=", "" ,"")); - return userProfileDao.updateObject(update, null); } diff --git a/datastore/src/main/resources/db/migration/V27__UserGroupMappings.sql b/datastore/src/main/resources/db/migration/V27__UserGroupMappings.sql new file mode 100644 index 0000000000000000000000000000000000000000..f8567a237d566869d2b40604ee886d68b9d47bc3 --- /dev/null +++ b/datastore/src/main/resources/db/migration/V27__UserGroupMappings.sql @@ -0,0 +1,6 @@ +ALTER TABLE usergroupmappings DROP PRIMARY KEY; +ALTER TABLE usergroupmappings ADD COLUMN GROUPID varchar(36); +UPDATE usergroupmappings SET GROUPID = groups; +ALTER TABLE usergroupmappings DROP COLUMN groups; +ALTER TABLE usergroupmappings ADD id int NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1); +ALTER TABLE usergroupmappings ADD PRIMARY KEY (id); \ No newline at end of file diff --git a/datastore/src/main/resources/db/migration/V35__Group.sql b/datastore/src/main/resources/db/migration/V35__Group.sql new file mode 100644 index 0000000000000000000000000000000000000000..a8ca7fb4705c16f01062d0bb72de44d6c2958667 --- /dev/null +++ b/datastore/src/main/resources/db/migration/V35__Group.sql @@ -0,0 +1,2 @@ +ALTER TABLE groups DROP PRIMARY KEY; +ALTER TABLE groups ADD ID varchar(36) PRIMARY KEY not null default ''; \ No newline at end of file diff --git a/datastore/src/test/resources/db/migration/V17__UserGroupMappings.sql b/datastore/src/test/resources/db/migration/V17__UserGroupMappings.sql index d9b8deb1d998a7288617435c88902c52d1ed2817..2bcd6e18669b3eab87546bd5e9ba9aa337fb5236 100644 --- a/datastore/src/test/resources/db/migration/V17__UserGroupMappings.sql +++ b/datastore/src/test/resources/db/migration/V17__UserGroupMappings.sql @@ -1,3 +1,3 @@ CREATE TABLE usergroupmappings (username varchar(255), -groupMemberships CLOB, +groups varchar(5000), PRIMARY KEY (username)); \ No newline at end of file diff --git a/datastore/src/test/resources/db/migration/V27__UserGroupMappings.sql b/datastore/src/test/resources/db/migration/V27__UserGroupMappings.sql new file mode 100644 index 0000000000000000000000000000000000000000..f8567a237d566869d2b40604ee886d68b9d47bc3 --- /dev/null +++ b/datastore/src/test/resources/db/migration/V27__UserGroupMappings.sql @@ -0,0 +1,6 @@ +ALTER TABLE usergroupmappings DROP PRIMARY KEY; +ALTER TABLE usergroupmappings ADD COLUMN GROUPID varchar(36); +UPDATE usergroupmappings SET GROUPID = groups; +ALTER TABLE usergroupmappings DROP COLUMN groups; +ALTER TABLE usergroupmappings ADD id int NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1); +ALTER TABLE usergroupmappings ADD PRIMARY KEY (id); \ No newline at end of file diff --git a/datastore/src/test/resources/db/migration/V35__Group.sql b/datastore/src/test/resources/db/migration/V35__Group.sql new file mode 100644 index 0000000000000000000000000000000000000000..a8ca7fb4705c16f01062d0bb72de44d6c2958667 --- /dev/null +++ b/datastore/src/test/resources/db/migration/V35__Group.sql @@ -0,0 +1,2 @@ +ALTER TABLE groups DROP PRIMARY KEY; +ALTER TABLE groups ADD ID varchar(36) PRIMARY KEY not null default ''; \ No newline at end of file diff --git a/jams-common/src/main/java/net/jami/jams/common/objects/user/Group.java b/jams-common/src/main/java/net/jami/jams/common/objects/user/Group.java index 0bd5a5d883a3c17ae8c5b950e6f3b8ec2c5e5b71..a77b95c193f512e6c444b1b7145a17197db5e9b8 100644 --- a/jams-common/src/main/java/net/jami/jams/common/objects/user/Group.java +++ b/jams-common/src/main/java/net/jami/jams/common/objects/user/Group.java @@ -17,20 +17,21 @@ import java.util.List; @Getter @Setter public class Group implements DatabaseObject { + private String id; private String name; private String blueprint; - private List<String> groupMembers; public Group(ResultSet rs) throws SQLException { + this.id = rs.getString("id"); this.name = rs.getString("name"); this.blueprint = rs.getString("blueprint"); - this.groupMembers = new ArrayList<>(); } @Override public PreparedStatement getInsert(PreparedStatement ps) throws SQLException { - ps.setString(1, name); - ps.setString(2, blueprint); + ps.setString(1, id); + ps.setString(2, name); + ps.setString(3, blueprint); return ps; } @@ -43,4 +44,14 @@ public class Group implements DatabaseObject { public PreparedStatement getUpdate(PreparedStatement ps) throws Exception { return null; } + + public boolean isEmpty() { + if(this.id == null && this.name == null) return true; + return false; + } + + public boolean hasBlueprint() { + if(this.blueprint == null || this.blueprint.equals("")) return false; + return true; + } } diff --git a/jams-common/src/main/java/net/jami/jams/common/objects/user/UserGroupMapping.java b/jams-common/src/main/java/net/jami/jams/common/objects/user/UserGroupMapping.java index 46f477a9cd89d47d07c77cd8230dcc7ea362f038..2925ebd6574128af58fca49d8ab1602809272f17 100644 --- a/jams-common/src/main/java/net/jami/jams/common/objects/user/UserGroupMapping.java +++ b/jams-common/src/main/java/net/jami/jams/common/objects/user/UserGroupMapping.java @@ -16,35 +16,17 @@ import java.sql.ResultSet; public class UserGroupMapping implements DatabaseObject { private String username; - private String groups; + private String groupId; public UserGroupMapping(ResultSet rs) throws Exception { this.username = rs.getString("username"); - this.groups = rs.getString("groups"); - } - - public void addGroup(String s) { - if (this.groups.equals("")) - this.groups = s; - else - this.groups = this.groups.concat("," + s); - } - - public void removeGroup(String s) { - if (this.groups.contains(s) ) { - if (this.groups.contains(",")) { - this.groups = this.groups.replace(s + ",", ""); - this.groups = this.groups.replace("," + s, ""); - } - else - this.groups = ""; - } + this.groupId = rs.getString("groupId"); } @Override public PreparedStatement getInsert(PreparedStatement ps) throws Exception { ps.setString(1, username); - ps.setString(2, groups); + ps.setString(2, groupId); return ps; } diff --git a/jams-common/src/main/java/net/jami/jams/common/objects/user/UserProfile.java b/jams-common/src/main/java/net/jami/jams/common/objects/user/UserProfile.java index b9a67de117c6306898f77bc4603362217c2113c6..23e56b572372e83cfb0d8a4a2fc289ca7f259f88 100644 --- a/jams-common/src/main/java/net/jami/jams/common/objects/user/UserProfile.java +++ b/jams-common/src/main/java/net/jami/jams/common/objects/user/UserProfile.java @@ -75,7 +75,6 @@ public class UserProfile implements DatabaseObject { private String faxNumber; private String mobileNumber; private String id; - private List<String> groupMemberships; public UserProfile(ResultSet rs) throws Exception { this.username = rs.getString("username"); @@ -88,7 +87,6 @@ public class UserProfile implements DatabaseObject { this.phoneNumberExtension = rs.getString("phoneNumberExtension"); this.faxNumber = rs.getString("faxNumber"); this.mobileNumber = rs.getString("mobileNumber"); - this.groupMemberships = new ArrayList<>(Arrays.asList(rs.getString("groupMemberships").split(","))); } @JsonIgnore @@ -130,10 +128,6 @@ public class UserProfile implements DatabaseObject { ps.setString(8, phoneNumberExtension); ps.setString(9, faxNumber); ps.setString(10, mobileNumber); - if (!groupMemberships.isEmpty()) - ps.setString(11, JsonStream.serialize(groupMemberships)); - else - ps.setString(11, ""); return ps; } @@ -176,8 +170,6 @@ public class UserProfile implements DatabaseObject { this.faxNumber = ""; if (this.mobileNumber == null) this.mobileNumber = ""; - if (this.groupMemberships == null) - this.groupMemberships = new ArrayList<>(); } } diff --git a/jams-react-client/public/locales/en/translation.json b/jams-react-client/public/locales/en/translation.json index 9ef108cebc783872bec8c454c19a12691bb0af21..0ca38adde08d25a48b0ee2afb4b9225d9bbdc0f0 100644 --- a/jams-react-client/public/locales/en/translation.json +++ b/jams-react-client/public/locales/en/translation.json @@ -235,5 +235,6 @@ "add_user_to_group": "Add user to group ...", "add_user_to_a_group": "Add user to a group", "remove_from_group": "Remove from group", - "myprofile": "My profile" + "myprofile": "My profile", + "select_blueprint": "Select a blueprint" } diff --git a/jams-react-client/public/locales/fr/translation.json b/jams-react-client/public/locales/fr/translation.json index 5aad0beffe2cdce47eb835da9ad81325e2147216..9e037d6eeb81e050c255894ae0a6797f1581c291 100644 --- a/jams-react-client/public/locales/fr/translation.json +++ b/jams-react-client/public/locales/fr/translation.json @@ -235,5 +235,6 @@ "add_user_to_group": "Ajouter un utilisateur au groupe...", "add_user_to_a_group": "Ajouter l'utilisateur à un groupe", "remove_from_group": "Retirer du groupe", - "myprofile": "Mon profil" + "myprofile": "My profile", + "select_blueprint": "Select a blueprint" } diff --git a/jams-react-client/src/assets/img/favicon_jami.png b/jams-react-client/src/assets/img/favicon_jami.png new file mode 100644 index 0000000000000000000000000000000000000000..794f3628e2eb68df1f8c287ee4ed9f37e73fb9c0 Binary files /dev/null and b/jams-react-client/src/assets/img/favicon_jami.png differ diff --git a/jams-react-client/src/components/Drawer/Drawer.js b/jams-react-client/src/components/Drawer/Drawer.js index 267c939ac7db6ce3b51db304abd27fb254616de0..6cb438483107c600b28110cae9dba2db59b13ded 100644 --- a/jams-react-client/src/components/Drawer/Drawer.js +++ b/jams-react-client/src/components/Drawer/Drawer.js @@ -77,7 +77,7 @@ export default function TemporaryDrawer(props) { button key={target.name} onClick={() => { - props.addElementToTarget(target.name); + props.addElementToTarget(target); props.setOpenDrawer(false); }} > diff --git a/jams-react-client/src/globalUrls.js b/jams-react-client/src/globalUrls.js index 85565dc03b6289195fa520f770d5ccc74a9b42b6..9d7f89c414d1973260595bbf25a7705faf02e06e 100644 --- a/jams-react-client/src/globalUrls.js +++ b/jams-react-client/src/globalUrls.js @@ -17,9 +17,11 @@ const api_path_delete_auth_user_revoke = "/api/auth/user"; const api_path_delete_admin_device_revoke = "/api/admin/device"; const api_path_delete_auth_device_revoke = "/api/auth/device"; const api_path_rename_device = "/api/auth/device"; -const api_path_get_list_group = "/api/admin/group"; -const api_path_delete_group = "/api/admin/group"; -const api_path_put_update_group = "/api/admin/group"; +const api_path_get_list_group = "/api/admin/groups"; +const api_path_post_create_group = "/api/admin/group"; +const api_path_delete_group = "/api/admin/group/"; +const api_path_put_update_group = "/api/admin/group/"; +const api_path_get_group = "/api/admin/group/"; const api_path_get_server_status = "/api/info"; const api_path_get_post_configuration_auth_service = "/api/configuration/authservice"; @@ -31,7 +33,6 @@ const api_path_get_subscription_status = "/api/admin/subscription"; const api_path_get_directories = "/api/auth/directories"; const api_path_get_needs_update = "/api/admin/update"; const api_path_get_start_update = "/api/admin/update"; -const api_path_post_create_group = "/api/admin/group"; const api_path_post_create_user = "/api/admin/user"; const api_path_put_update_user = "/api/admin/user"; const api_path_get_auth_user = "/api/auth/user"; @@ -50,6 +51,10 @@ const api_path_blueprints = "/api/admin/policy"; const api_path_get_user_profile = "/api/auth/userprofile/"; const api_path_get_ns_name_from_addr = "/api/nameserver/addr/"; const api_path_get_ns_addr_from_name = "/api/nameserver/name/"; +const api_path_get_group_members = "/api/admin/group/members/"; +const api_path_post_group_member = "/api/admin/group/members/"; +const api_path_delete_group_member = "/api/admin/group/members/"; +const api_path_get_admin_user_groups = "/api/admin/user/groups/"; module.exports = { uri, @@ -98,8 +103,13 @@ module.exports = { api_path_get_list_group, api_path_post_create_group, api_path_put_update_group, + api_path_get_group, api_path_blueprints, api_path_get_user_profile, api_path_get_ns_name_from_addr, api_path_get_ns_addr_from_name, + api_path_get_group_members, + api_path_post_group_member, + api_path_delete_group_member, + api_path_get_admin_user_groups }; diff --git a/jams-react-client/src/index.js b/jams-react-client/src/index.js index 587dbcdf0964e6616ab20a4cb89194c75e92cac9..117ab849fc49b886c2411185b68b949c5a7b0e59 100644 --- a/jams-react-client/src/index.js +++ b/jams-react-client/src/index.js @@ -52,7 +52,7 @@ auth.isServerInstalled(() => { <ProtectedRoute path="/user/:username" component={UserRoute} /> <ProtectedRoute path="/createuser" component={CreateUserRoute} /> <ProtectedRoute path="/groups" component={GroupsRoute} /> - {/* <ProtectedRoute path="/group/:groupname" component={GroupRoute} /> */} + <ProtectedRoute path="/group/:groupid" component={GroupRoute} /> <ProtectedRoute path="/blueprints" component={BlueprintsRoute} /> <ProtectedRoute path="/blueprint/:blueprintname" component={BlueprintRoute} /> <ProtectedRoute path="/settings" component={SettingsRoute} /> diff --git a/jams-react-client/src/routes/BlueprintRoute.js b/jams-react-client/src/routes/BlueprintRoute.js index 182d9901069503604ace5eafce6670e79f5ccabd..d9f873043b4cc16fe6faedaa767556c8d6142ef9 100644 --- a/jams-react-client/src/routes/BlueprintRoute.js +++ b/jams-react-client/src/routes/BlueprintRoute.js @@ -3,7 +3,7 @@ import React from "react"; import BaseLayout from "layouts/BaseLayout.js"; import Blueprint from "views/Blueprint/Blueprint"; -export default function UsersRoute (props) { +export default function BlueprintRoute (props) { return ( <BaseLayout component={<Blueprint blueprintName={props.match.params.blueprintname} />} /> ) diff --git a/jams-react-client/src/routes/BlueprintsRoute.js b/jams-react-client/src/routes/BlueprintsRoute.js index cd7657009979b8207e33ba8d545c288379ceec4f..35037ee61b7476ead9dd4d1e805768c85f5a99b0 100644 --- a/jams-react-client/src/routes/BlueprintsRoute.js +++ b/jams-react-client/src/routes/BlueprintsRoute.js @@ -4,7 +4,7 @@ import Blueprints from "views/Blueprints/Blueprints.js"; import ListLayout from "layouts/ListLayout.js"; -export default function UsersRoute () { +export default function BlueprintsRoute () { return ( <ListLayout component={<Blueprints />} /> ) diff --git a/jams-react-client/src/routes/GroupRoute.js b/jams-react-client/src/routes/GroupRoute.js index 4fe4eb49fb407c4d3539f3e32472a5194f16c35f..d33110e1ef064dec3122e017ea14734cc2c75c23 100644 --- a/jams-react-client/src/routes/GroupRoute.js +++ b/jams-react-client/src/routes/GroupRoute.js @@ -1,11 +1,10 @@ import React from "react"; +import ListLayout from "layouts/ListLayout.js"; import EditGroup from "views/Groups/EditGroup.js"; -import BaseLayout from "layouts/BaseLayout.js"; - -export default function UsersRoute (props) { +export default function GroupRoute (props) { return ( - <BaseLayout component={<EditGroup groupName={props.match.params.groupname} />} /> + <ListLayout component={<EditGroup groupid={props.match.params.groupid} />} /> ) } \ No newline at end of file diff --git a/jams-react-client/src/routes/GroupsRoute.js b/jams-react-client/src/routes/GroupsRoute.js index ea731f5c42aaa0397bddaa2e8f25ea65afa2aab1..db4bc7133b07d4316194805b972f6993acb899f5 100644 --- a/jams-react-client/src/routes/GroupsRoute.js +++ b/jams-react-client/src/routes/GroupsRoute.js @@ -1,10 +1,9 @@ import React from "react"; -import Groups from "views/Groups/Groups.js"; - import ListLayout from "layouts/ListLayout.js"; +import Groups from "views/Groups/Groups.js"; -export default function UsersRoute () { +export default function GroupsRoute () { return ( <ListLayout component={<Groups />} /> ) diff --git a/jams-react-client/src/routes/SettingsRoute.js b/jams-react-client/src/routes/SettingsRoute.js index 60fd2a61988b26b930a2375aa81c4593b4d899fa..c645ee93c07a9c2fdf3a6d3a67cf22cb53a00a74 100644 --- a/jams-react-client/src/routes/SettingsRoute.js +++ b/jams-react-client/src/routes/SettingsRoute.js @@ -4,7 +4,7 @@ import Settings from "views/Settings/Settings.js"; import BaseLayout from "layouts/BaseLayout.js"; -export default function UsersRoute (props) { +export default function SettingsRoute (props) { return ( <BaseLayout component={<Settings />} /> ) diff --git a/jams-react-client/src/views/Groups/EditGroup.js b/jams-react-client/src/views/Groups/EditGroup.js index e84c6899964295f229156cfc04ea17aa361b06b4..4c0ddf6ce201e03563e2be83241be5bc3b03bae7 100644 --- a/jams-react-client/src/views/Groups/EditGroup.js +++ b/jams-react-client/src/views/Groups/EditGroup.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback } from "react"; import { Link, useHistory } from "react-router-dom"; import classnames from "classnames"; @@ -23,6 +23,8 @@ import TableHead from '@material-ui/core/TableHead'; import TableRow from "@material-ui/core/TableRow"; import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; +import InputLabel from "@material-ui/core/InputLabel"; + import Select from "@material-ui/core/Select"; @@ -37,9 +39,14 @@ import axios from "axios" import configApiCall from "../../api" import { api_path_get_list_group, + api_path_get_group, api_path_put_update_group, api_path_get_user_directory_search, - api_path_get_user_profile + api_path_get_user_profile, + api_path_blueprints, + api_path_get_group_members, + api_path_post_group_member, + api_path_delete_group_member } from "../../globalUrls" import dashboardStyle from "assets/jss/material-dashboard-react/views/dashboardStyle.js"; @@ -55,6 +62,8 @@ import i18next from "i18next"; import auth from "auth.js"; +import { debounce } from "lodash"; + const useStyles = makeStyles((theme) => ({ ...devicesStyle, ...dashboardStyle, @@ -95,6 +104,9 @@ const useStyles = makeStyles((theme) => ({ maxHeight: '60vh', minWidth: '80vh', maxWidth: '80vh' + }, + inputBottomMargin: { + marginBottom: "1rem" } })); @@ -102,11 +114,16 @@ export default function EditGroup(props) { const classes = useStyles(); const history = useHistory(); - const [name, setName] = React.useState(props.groupName); - const [newName, setNewName] = React.useState(props.groupName); - const [blueprint, setBlueprint] = React.useState(null); + const [name, setName] = React.useState(""); + const [newName, setNewName] = React.useState(""); + const [blueprints, setBlueprints] = React.useState([]); + const [selectedBlueprint, setSelectedBlueprint] = React.useState({ + value: 0, + label: "No blueprint", + }); const [groupMembers, setGroupMembers] = React.useState([]); const [openDrawer, setOpenDrawer] = React.useState(false); + const [groupNameExits, setGroupNameExits] = React.useState(false); const [users, setUsers] = React.useState([]); const getUserInfo = username => new Promise((resolve, reject) => { @@ -126,89 +143,91 @@ export default function EditGroup(props) { }); }) - const getUsersInformation = userNames => { - userNames.forEach((username)=> { - getUserInfo(username).then((userInfo) => setGroupMembers(groupMembers.push(userInfo))) - }) - } - const getGroup = () => { - axios(configApiCall(api_path_get_list_group+"?groupName="+props.groupName, 'GET', null, null)).then((response) => { - let groups=response.data; - if(groups.length > 1){ - groups.map((group) => { - if(group.name === props.groupName){ - props.getBlueprintsOptions().forEach((blueprintOption) => { - if(blueprintOption.label === group["blueprint"]){ - console.log("Group option value : " + blueprintOption.value); - setBlueprint(blueprintOption.value); - } - }) - - getUserInfo(group["groupMembers"]).then((userInfo) => { - let newGroupMembers = groupMembers; - newGroupMembers.push(userInfo); - setGroupMembers(newGroupMembers); -  }) - } + axios(configApiCall(api_path_get_group + props.groupid, 'GET', null, null)).then((response) => { + let group = response.data; + + axios(configApiCall(api_path_blueprints + "?name=*", "GET", null, null)) + .then((response) => { + setBlueprints(response.data); + let index = 0; + response.data.map((blueprint) => { + if(blueprint.name == group["blueprint"]) + setSelectedBlueprint({ value: index, label: blueprint.name }); + index += 1; + }); + }) + .catch((error) => { + console.log(error); + if (error.response.status === 401) { + auth.authenticated = false; + history.push("/"); + } + if (error.response.status === 500) { + setBlueprints([]); + } + }); + + axios(configApiCall(api_path_get_group_members + props.groupid, 'GET', null, null)).then((response) => { + let members = response.data; + members.forEach((member) => { + getUserInfo(member.username).then((userInfo) => { + let newGroupMembers = groupMembers; + newGroupMembers.push(userInfo); + setGroupMembers(newGroupMembers); + + //This state update is added to force the groupMembers to displayed on first page loading + setOpenDrawer(true); + setOpenDrawer(false); +  }) }) - } - else{ - if(groups.name === props.groupName){ - props.getBlueprintsOptions().forEach((blueprintOption) => { - if(blueprintOption.label === groups["blueprint"]){ - setBlueprint(blueprintOption.value) - } - }) - groups["groupMembers"].forEach((username)=> { - getUserInfo(username).then((userInfo) => { - let newGroupMembers = groupMembers; - newGroupMembers.push(userInfo); - setGroupMembers(newGroupMembers); -  }) - }) - + }).catch((error) => { + if (error.response.status === 404) { + setGroupMembers([]); } - } + else + console.log(error); + }) + + + setName(group.name); + setNewName(group.name); }).catch((error) => { - console.log("Error fetching group members of: " + props.groupName + " " + error) + console.log("Error fetching group members of: " + props.name + " " + error) }) } - const getGroupsOptions = () => { - let blueprintsOptions = [] - let index = 0 - if(props.blueprints.length === 0) - blueprintsOptions.push({value: index, label: "No blueprint found"}) + const getBlueprintsOptions = (inputs) => { + let blueprintsOptions = []; + let index = 0; + if (blueprints.length === 0) + blueprintsOptions.push({ value: index, label: "No blueprint found" }); else { - props.blueprints.map((blueprintElement) => { - blueprintsOptions.push({value: index, label: blueprintElement.name}); - index += 1 - }) + blueprints.map((blueprint) => { + blueprintsOptions.push({ value: index, label: blueprint.name }); + index += 1; + }); } - return blueprintsOptions - } + return blueprintsOptions; + }; - const blueprintsOptionsItems = tool.buildSelectMenuItems(getGroupsOptions()); + const blueprintsOptionsItems = tool.buildSelectMenuItems(getBlueprintsOptions(blueprints)); React.useEffect(()=>{ getGroup(); searchUsers(); }, []) - const handleUpdateGroup = (blueprintValue) => { + const updateGroup = (blueprintValue) => { - let url = ''; - if(blueprintValue == null){ - url = api_path_put_update_group+"?groupName="+props.groupName+"&newName="+name+"&blueprintName=&groupMembers="; - }else{ - url = api_path_put_update_group+"?groupName="+props.groupName+"&newName="+name+"&blueprintName="+props.getBlueprintsOptions()[blueprintValue].label+"&groupMembers="; + let data = { + "name": newName, + "blueprint": blueprintValue ? blueprintValue : selectedBlueprint.label } - axios(configApiCall(url, 'PUT', null, null)).then((response) => { - setNewName(name); - setBlueprint(blueprintValue); + axios(configApiCall(api_path_put_update_group + props.groupid, 'PUT', data, null)).then((response) => { + setName(newName); }).catch((error) => { console.log("Error updating group: " + error) }) @@ -245,29 +264,83 @@ export default function EditGroup(props) { }); }; - const updateUserInGroup = (user) => { - let url = ''; - if(blueprint == null){ - url = api_path_put_update_group+"?groupName="+props.groupName+"&newName="+props.groupName+"&blueprintName=&groupMembers="+[user.username,]; - }else{ - url = api_path_put_update_group+"?groupName="+props.groupName+"&newName="+props.groupName+"&blueprintName="+props.getBlueprintsOptions()[blueprint].label+"&groupMembers="+[user.username,]; + const addUserInGroup = (user) => { + let data = { + "username": user.username } + axios(configApiCall(api_path_post_group_member + props.groupid, 'POST', data, null)).then((response) => { + let newGroupMembers = groupMembers; + newGroupMembers.push(user); + setGroupMembers(newGroupMembers); - axios(configApiCall(url, 'PUT', null, null)).then((response) => { + //This state update is added to force the groupMembers to refreshed displayed + setOpenDrawer(true); + setOpenDrawer(false); + }).catch((error) => { + if (error.response.status === 409) { + alert(`${user.username} is already part of ${name}`); + } + else + console.log("Error updating group: " + error) + }) + } + + const deleteUserFromGroup = (user) => { + let data = { + "username": user.username + } + axios(configApiCall(api_path_delete_group_member + props.groupid, 'DELETE', data, null)).then((response) => { let newGroupMembers = groupMembers; - if(newGroupMembers.includes(user)) newGroupMembers.splice(newGroupMembers.indexOf(user), 1); - else newGroupMembers.push(user); + newGroupMembers.splice(newGroupMembers.indexOf(user), 1); setGroupMembers(newGroupMembers); + + //This state update is added to force the groupMembers to refreshed displayed + setOpenDrawer(true); + setOpenDrawer(false); }).catch((error) => { console.log("Error updating group: " + error) }) } - const handleBlueprintsChange = (blueprintValue) => { - setBlueprint(blueprintValue) - handleUpdateGroup(blueprintValue) + const handleBlueprintsChange = (e) => { + updateGroup(getBlueprintsOptions()[e.target.value].label !== "No blueprint found" ? getBlueprintsOptions()[e.target.value].label : ""); + setSelectedBlueprint(getBlueprintsOptions()[e.target.value]); } + const initCheckGroupNameExists = useCallback( + debounce( + (searchGroupNameValue) => + handleCheckGroupNameExists(searchGroupNameValue), + 500 + ), + [] + ); + + + const handleCheckGroupNameExists = (searchGroupNameValue) => { + + axios( + configApiCall( + api_path_get_list_group + "?groupName=" + searchGroupNameValue, + "GET", + null, + null + ) + ) + .then((response) => { + setGroupNameExits(false); + response.data.forEach((group) => { + if(searchGroupNameValue === group.name){ + setGroupNameExits(true); + } + }); + }) + .catch((error) => { + console.log(error); + setGroupNameExits(false); + }); + }; + const tableCellClasses = classnames(classes.tableCell); return( @@ -279,7 +352,7 @@ export default function EditGroup(props) { placeholder={i18next.t("add_user_to_group", "Add user to group ...")} searchTargets={searchUsers} targets={users} - addElementToTarget={updateUserInGroup} + addElementToTarget={addUserInGroup} targetName={name} type="user" /> @@ -290,50 +363,51 @@ export default function EditGroup(props) { <CardIcon color="info"> <EditIcon /> </CardIcon> - <p className={classes.cardCategory}>{i18next.t("edit_group", "Edit group")}</p> - <h3 className={classes.cardTitle}>{newName}</h3> + <p className={classes.cardCategory}>{i18next.t("edit_group", "Edit group")}</p> + <h3 className={classes.cardTitle}>{name}</h3> </CardHeader> <CardBody profile> <div className={classes.root}> <Grid container spacing={2}> <Grid item xs={12} sm={12} md={12}> - <FormControl className={classes.margin} size="medium" error={props.groupNameExits}> + <FormControl className={classes.margin} size="medium" error={groupNameExits}> <Input id="name" - placeholder={props.groupName} + placeholder={name} startAdornment={ <InputAdornment position="start"> <PeopleOutlineIcon /> </InputAdornment> } endAdornment={ - <IconButton color="primary" aria-label="upload picture" component="span" + <IconButton color="primary" aria-label="update name" component="span" onClick={() => { - handleUpdateGroup(blueprint); + updateGroup(); }} - disabled={props.groupNameExits} + disabled={groupNameExits || name == newName} > <SaveIcon /> </IconButton> } onChange={e => { - setName(e.target.value); - props.initCheckGroupNameExists(e.target.value) + setNewName(e.target.value); + initCheckGroupNameExists(e.target.value) }} /> </FormControl> </Grid> <Grid item xs={12} sm={12} md={12}> + <InputLabel className={classes.inputBottomMargin} htmlFor="blueprint">{i18next.t("select_blueprint", "Select a blueprint")}</InputLabel> <FormControl className={classes.margin} fullWidth> <Select labelId="demo-simple-select-label" fullWidth - value={blueprint} - onChange={(e) => handleBlueprintsChange(e.target.value)} + value={selectedBlueprint.value} + onChange={handleBlueprintsChange} variant="outlined" children={blueprintsOptionsItems} - disabled={props.blueprints.length === 0} - /> + disabled={blueprints.length === 0} + /> </FormControl> </Grid> </Grid> @@ -342,7 +416,7 @@ export default function EditGroup(props) { </Card> </GridItem> <GridItem xs={12} sm={12} md={12}> - <Button color="primary" onClick={() => {setOpenDrawer(true)}}><AddCircleOutlineIcon /> {i18next.t("add_user_to", "Add user to")} {props.groupName}</Button> + <Button color="primary" onClick={() => {setOpenDrawer(true)}}><AddCircleOutlineIcon /> {i18next.t("add_user_to", "Add user to")} {name}</Button> <Table className={classes.table}> <TableHead> <TableRow> @@ -379,7 +453,7 @@ export default function EditGroup(props) { <Link to={`/user/${user.username}`}>{user.lastName}</Link> </TableCell> <TableCell align="right" className={classes.tableActions}> - <Button color="primary" onClick={() => updateUserInGroup(user)}>{i18next.t("remove_user", "Remove user")}</Button> + <Button color="primary" onClick={() => deleteUserFromGroup(user)}>{i18next.t("remove_user", "Remove user")}</Button> </TableCell> </TableRow> )} diff --git a/jams-react-client/src/views/Groups/Groups.js b/jams-react-client/src/views/Groups/Groups.js index a26eb1b3a09c4acb62ad29fdfc3276618bcc7a91..aba42d2c3c053ca62063d77febfb3356cc37274c 100644 --- a/jams-react-client/src/views/Groups/Groups.js +++ b/jams-react-client/src/views/Groups/Groups.js @@ -1,5 +1,5 @@ import React, { useEffect, useState, useCallback } from "react"; -import { useHistory } from "react-router-dom"; +import { Link, useHistory } from "react-router-dom"; // @material-ui/core components import { makeStyles } from "@material-ui/core/styles"; import InputLabel from "@material-ui/core/InputLabel"; @@ -32,6 +32,7 @@ import { api_path_get_list_group, api_path_delete_group, api_path_blueprints, + api_path_get_group_members } from "globalUrls"; import AddCircleOutlineIcon from "@material-ui/icons/AddCircleOutline"; @@ -86,6 +87,9 @@ const styles = { }, whiteButtonText: { color: "white", + }, + inputBottomMargin: { + marginBottom: "1rem" } }; @@ -95,9 +99,8 @@ export default function Groups() { const classes = useStyles(); const history = useHistory(); const [groups, setGroups] = React.useState([]); - const [selectedGroupName, setSelectedGroupName] = React.useState(""); const [loading, setLoading] = React.useState(false); - const [zeroGroup, setZeroGroup] = React.useState(true); + const [zeroGroup, setZeroGroup] = React.useState(false); const [progress, setProgress] = React.useState(0); const [searchValue, setSearchValue] = React.useState(null); @@ -107,31 +110,34 @@ export default function Groups() { const [groupName, setGroupName] = React.useState(""); const [groupNameExits, setGroupNameExits] = React.useState(false); - const [removedGroup, setRemovedGroup] = React.useState(); + const [removedGroup, setRemovedGroup] = React.useState({ + "id": 0, + "name": "groupeName" + }); const [openRemoveDialog, setOpenRemoveDialog] = React.useState(); const [disableCreate, setDisableCreate] = React.useState(true); - const handleRemoveGroup = (groupRemovedName) => { - setRemovedGroup(groupRemovedName); + const handleRemoveGroup = (group) => { + setRemovedGroup(group); setOpenRemoveDialog(true); }; const removeGroup = () => { axios( configApiCall( - api_path_delete_group + "?groupName=" + removedGroup, + api_path_delete_group + removedGroup.id, "DELETE", null, null ) ) .then((response) => { - console.log("Successfully deleted " + removedGroup); + console.log("Successfully deleted " + removedGroup.name); setOpenRemoveDialog(false); }) .catch((error) => { - console.log("Could not delete " + removedGroup + " " + error); + console.log("Could not delete " + removedGroup.name + " " + error); setOpenRemoveDialog(false); }); @@ -152,6 +158,24 @@ export default function Groups() { return blueprintsOptions; }; + const getBlueprints = () => { + axios(configApiCall(api_path_blueprints + "?name=*", "GET", null, null)) + .then((response) => { + setBlueprints(response.data); + setSelectedBlueprint(getBlueprintsOptions()[0]); + }) + .catch((error) => { + console.log(error); + if (error.response.status === 401) { + auth.authenticated = false; + history.push("/"); + } + if (error.response.status === 500) { + setBlueprints([]); + } + }); + } + useEffect(() => { setLoading(true); const timer = setInterval(() => { @@ -165,30 +189,26 @@ export default function Groups() { }, 500); axios( - configApiCall(api_path_get_list_group + "?groupName=*", "GET", null, null) + configApiCall(api_path_get_list_group, "GET", null, null) ) .then((response) => { let allGroups = response.data; - if (allGroups.length === 0) setZeroGroup(true); - else { - setZeroGroup(false); - } - setGroups(allGroups); - axios(configApiCall(api_path_blueprints + "?name=*", "GET", null, null)) - .then((response) => { - setBlueprints(response.data); - setSelectedBlueprint(getBlueprintsOptions()[0]); + + allGroups.forEach((group)=> { + axios(configApiCall(api_path_get_group_members + group.id, 'GET', null, null)).then((response) => { + group["groupMembersLength"] = response.data.length; + }).catch((error) => { + if (error.response.status === 401) { + auth.authenticated = false; + history.push("/"); + } + if (error.response.status === 404) { + group["groupMembersLength"] = 0; + } + }); }) - .catch((error) => { - console.log(error); - if (error.response.status === 401) { - auth.authenticated = false; - history.push("/"); - } - if (error.response.status === 500) { - setBlueprints([]); - } - }); + setGroups(allGroups); + getBlueprints(); setLoading(false); }) .catch((error) => { @@ -197,20 +217,16 @@ export default function Groups() { auth.authenticated = false; history.push("/"); } + if (error.response.status === 404){ + getBlueprints(); + setZeroGroup(true); + } }); return () => { clearInterval(timer); }; }, [openCreate, openRemoveDialog, history]); - const [selectedGroup, setSelectedGroup] = useState(false); - - const redirectToGroup = (e, name) => { - e.preventDefault(); - setSelectedGroup(true); - setSelectedGroupName(name); - }; - const handleCheckGroupNameExists = (searchGroupNameValue) => { setDisableCreate(true); axios( @@ -222,8 +238,15 @@ export default function Groups() { ) ) .then((response) => { - setDisableCreate(true); - setGroupNameExits(true); + let allGroups = response.data; + setDisableCreate(false); + setGroupNameExits(false); + allGroups.forEach((group) => { + if(searchGroupNameValue === group.name){ + setDisableCreate(true); + setGroupNameExits(true); + } + }); }) .catch((error) => { console.log(error); @@ -262,15 +285,15 @@ export default function Groups() { null ) ) - .then(() => { + .then((response) => { console.log("Successfully created " + groupName); setOpenCreate(false); + history.push(`/group/${response.data.id}`); }) .catch((error) => { - console.log("Error creating group: " + error); + alert("Error creating group: " + error); setOpenCreate(false); }); - history.push(`/group/${groupName}`); }; const blueprintsOptionsItems = tool.buildSelectMenuItems( @@ -294,22 +317,8 @@ export default function Groups() { </h4> </div> ); - } else if (selectedGroup && auth.hasAdminScope()) { - return ( - <div> - <EditGroup - groupName={selectedGroupName} - initCheckGroupNameExists={initCheckGroupNameExists} - groupNameExits={groupNameExits} - blueprintsOptionsItems={blueprintsOptionsItems} - handleBlueprintsChange={handleBlueprintsChange} - getBlueprintsOptions={getBlueprintsOptions} - blueprints={blueprints} - disableCreate={disableCreate} - /> - </div> - ); - } else { + } + else { return ( <div> <Dialog @@ -351,6 +360,7 @@ export default function Groups() { )} </Grid> <Grid item xs={12} sm={12} md={12}> + <InputLabel className={classes.inputBottomMargin} htmlFor="blueprint">{i18next.t("select_blueprint", "Select a blueprint")}</InputLabel> <Select labelId="demo-simple-select-label" fullWidth @@ -387,7 +397,7 @@ export default function Groups() { <DialogTitle id="alert-dialog-title">{i18next.t("remove_group", "Remove group")}</DialogTitle> <DialogContent> <DialogContentText id="alert-dialog-description"> - {i18next.t("are_you_sure_you_want_to_delete", "Are you sure you want to delete")} <strong>{removedGroup}</strong> ? + {i18next.t("are_you_sure_you_want_to_delete", "Are you sure you want to delete")} <strong>{removedGroup.name}</strong> ? </DialogContentText> </DialogContent> <DialogActions> @@ -428,7 +438,7 @@ export default function Groups() { )} {!zeroGroup && <Search />} <div className={classes.loading}> - {loading && ( + {!zeroGroup && loading && ( <LinearProgress variant="determinate" value={progress} /> )} </div> @@ -438,7 +448,7 @@ export default function Groups() { <div className={classes.groupsNotFound}> <InfoIcon /> <p style={{ marginLeft: "10px" }}>{i18next.t("no_groups_found", "No groups Found")}</p> </div> - ) : groups.length >= 1 ? ( + ) : ( groups .filter((data) => { if (searchValue === null) { @@ -458,10 +468,7 @@ export default function Groups() { xl={2} key={group.name}> <Card profile> - <a - href="#" - onClick={(e) => redirectToGroup(e, group.name)} - > + <Link to={`/group/${group.id}`}> <CardBody profile> <h3 className={classes.cardTitle}>{group.name}</h3> <ul> @@ -471,7 +478,7 @@ export default function Groups() { style={{ marginRight: "10px" }} /> <strong style={{ marginRight: "5px" }}> - {group.groupMembers.length} + {group.groupMembersLength} </strong> {i18next.t("users", "users")} </li> @@ -487,12 +494,12 @@ export default function Groups() { </li> </ul> </CardBody> - </a> + </Link> <CardFooter> <IconButton color="secondary" onClick={() => { - handleRemoveGroup(group.name); + handleRemoveGroup(group); }} > <DeleteOutlineIcon /> @@ -501,51 +508,6 @@ export default function Groups() { </Card> </GridItem> )) - ) : ( - <GridItem xs={12} sm={12} md={2} key={groups.name}> - <Card profile> - - <a href="#" onClick={(e) => redirectToGroup(e, groups.name)}> - <CardBody profile> - <h3 className={classes.cardTitle}>{groups.name}</h3> - <ul> - <li> - <PersonIcon - fontSize="small" - style={{ marginRight: "10px" }} - /> - <strong style={{ marginRight: "5px" }}> - {typeof groups.groupMembers !== "undefined" - ? groups.groupMembers - : 0} - </strong> - {i18next.t("users", "users")} - </li> - <li> - <MailOutlineIcon - fontSize="small" - style={{ marginRight: "10px" }} - /> - <strong style={{ marginRight: "5px" }}> - {i18next.t("blueprint", "Blueprint")} - </strong> - {selectedBlueprint.label} - </li> - </ul> - </CardBody> - </a> - <CardFooter> - <IconButton - color="secondary" - onClick={() => { - handleRemoveGroup(groups.name); - }} - > - <DeleteOutlineIcon /> - </IconButton> - </CardFooter> - </Card> - </GridItem> )} </GridContainer> </div> diff --git a/jams-react-client/src/views/UserProfile/DisplayUserProfile.js b/jams-react-client/src/views/UserProfile/DisplayUserProfile.js index d115c82cf6b6caded4a46b2421127e40d55a9f4e..d5bcfa316d51b773181226ef3d71f8449b6fc3b0 100644 --- a/jams-react-client/src/views/UserProfile/DisplayUserProfile.js +++ b/jams-react-client/src/views/UserProfile/DisplayUserProfile.js @@ -1,5 +1,5 @@ import React, { useEffect } from "react"; -import { useHistory } from "react-router-dom"; +import { Link, useHistory } from "react-router-dom"; import classnames from "classnames"; // @material-ui/core components @@ -58,7 +58,10 @@ import { api_path_get_user_profile, api_path_delete_admin_user_revoke, api_path_get_list_group, - api_path_put_update_group, + api_path_get_group, + api_path_post_group_member, + api_path_get_admin_user_groups, + api_path_delete_group_member } from "globalUrls"; import dashboardStyle from "assets/jss/material-dashboard-react/views/dashboardStyle.js"; @@ -249,50 +252,49 @@ export default function DisplayUserProfile(props) { }); } - const updateUserInGroup = (group) => { + const removeUserFromGroup = (group) => { + + let data = { + "username": props.username + } + axios( configApiCall( - api_path_get_list_group + "?groupName=" + group, - "GET", - null, + api_path_delete_group_member + group.groupId, + "DELETE", + data, null ) ).then((response)=>{ - - let url = ""; - let oldGroup = response.data.name; - if (response.data.blueprint == null) { - url = - api_path_put_update_group + - "?groupName=" + - response.data.name + - "&newName=" + - response.data.name + - "&blueprintName=&groupMembers=" + - [props.username]; - } else { - url = - api_path_put_update_group + - "?groupName=" + - response.data.name + - "&newName=" + - response.data.name + - "&blueprintName=" + - response.data.blueprint + - "&groupMembers=" + - [props.username]; - } - - axios(configApiCall(url, "PUT", null, null)) - .then(() => { - let newGroupMemberships = groupMemberships; - if(newGroupMemberships.includes(oldGroup)) newGroupMemberships.splice(newGroupMemberships.indexOf(oldGroup), 1); - else newGroupMemberships.push(oldGroup); - setGroupMemberships(newGroupMemberships); - }) - .catch((error) => { - console.log("Error updating group: " + error); - }); + let newGroupMemberships = groupMemberships; + newGroupMemberships.splice(newGroupMemberships.indexOf(group), 1); + setGroupMemberships(newGroupMemberships); + }).catch((error)=>{console.log(error)}) + + }; + + const addUserToGroup = (group) => { + + let data = { + "username": props.username + } + + axios( + configApiCall( + api_path_post_group_member + group.id, + "POST", + data, + null + ) + ).then((response)=>{ + let newGroupMemberships = groupMemberships; + newGroupMemberships.push( + { + "groupId": group.id, + "name": group.name + } + ); + setGroupMemberships(newGroupMemberships); }).catch((error)=>{console.log(error)}) }; @@ -329,10 +331,28 @@ export default function DisplayUserProfile(props) { ) .then((response) => { setUser(response.data); - if(response.data.groupMemberships.length === 1 && response.data.groupMemberships[0] === "") { - setGroupMemberships([]); - } - else setGroupMemberships(response.data.groupMemberships) + axios(configApiCall(api_path_get_admin_user_groups + props.username, + "GET", + null, + null + )).then((userGroups) => { + let userGroupsData = userGroups.data; + userGroupsData.forEach((group) => { + axios(configApiCall(api_path_get_group + group.groupId, 'GET', null, null)).then((groupInfo) =>{ + group["name"] = groupInfo.data.name; + }); + }); + setGroupMemberships(userGroupsData); + }).catch((error) => { + console.log(error); + if (error.response.status === 401) { + auth.authenticated = false; + history.push("/"); + } + if (error.response.status === 404) { + setGroupMemberships([]); + } + }) searchGroups("*") setLoading(false); }) @@ -367,10 +387,28 @@ export default function DisplayUserProfile(props) { ) .then((response) => { setUser(response.data); - if(response.data.groupMemberships.length === 1 && response.data.groupMemberships[0] === "") { - setGroupMemberships([]); - } - else setGroupMemberships(response.data.groupMemberships) + axios(configApiCall(api_path_get_admin_user_groups + props.username, + "GET", + null, + null + )).then((userGroups) => { + let userGroupsData = userGroups.data; + userGroupsData.forEach((group) => { + axios(configApiCall(api_path_get_group + group.groupId, 'GET', null, null)).then((groupInfo) =>{ + group["name"] = groupInfo.data.name; + }); + }); + setGroupMemberships(userGroupsData); + }).catch((error) => { + console.log(error); + if (error.response.status === 401) { + auth.authenticated = false; + history.push("/"); + } + if (error.response.status === 404) { + setGroupMemberships([]); + } + }) setLoading(false); }) .catch((error) => { @@ -497,7 +535,7 @@ export default function DisplayUserProfile(props) { placeholder={i18next.t("add_user_to_group", "Add user to group ...")} searchTargets={searchGroups} targets={groups} - addElementToTarget={updateUserInGroup} + addElementToTarget={addUserToGroup} targetName={props.username} type="group" /> @@ -668,13 +706,15 @@ export default function DisplayUserProfile(props) { </TableRow> </TableHead> <TableBody> - {groupMemberships !== [""] && groupMemberships.map(group => + {groupMemberships !== [] && groupMemberships.map(group => <TableRow key={group} className={classes.tableRow}> <TableCell className={tableCellClasses}> - {group} + <Link to={`/group/${group.groupId}`}> + {group.name} + </Link> </TableCell> <TableCell align="right" className={classes.tableActions}> - <Button color="primary" onClick={() => updateUserInGroup(group)}>{i18next.t("remove_from_group", "Remove from group")}</Button> + <Button color="primary" onClick={() => removeUserFromGroup(group)}>{i18next.t("remove_from_group", "Remove from group")}</Button> </TableCell> </TableRow> ) } diff --git a/jams-server/src/main/java/net/jami/jams/server/core/workflows/AddUserToGroupFlow.java b/jams-server/src/main/java/net/jami/jams/server/core/workflows/AddUserToGroupFlow.java deleted file mode 100644 index 00f57cbf0f973b4aac83eee9bcdf0ead2d862ab7..0000000000000000000000000000000000000000 --- a/jams-server/src/main/java/net/jami/jams/server/core/workflows/AddUserToGroupFlow.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.jami.jams.server.core.workflows; - -import lombok.extern.slf4j.Slf4j; -import net.jami.jams.common.dao.StatementElement; -import net.jami.jams.common.dao.StatementList; -import net.jami.jams.common.objects.user.UserGroupMapping; -import net.jami.jams.common.objects.user.UserProfile; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static net.jami.jams.server.Server.dataStore; -import static net.jami.jams.server.Server.userAuthenticationModule; - -@Slf4j -public class AddUserToGroupFlow { - - public static void addUserToGroup(String groupName, String username) { - userAuthenticationModule.getAuthSources().forEach((k, v) -> { - UserProfile profile = v.getUserProfile(username); - List<UserProfile> groupProfiles = v.searchUserProfiles(username, "LOGON_NAME", Optional.empty()); - if (!groupProfiles.isEmpty()) { - UserGroupMapping mapping = null; - StatementList statementList = new StatementList(); - statementList.addStatement(new StatementElement("username", "=", username, "")); - if (dataStore.getUserGroupMappingsDao().getObjects(statementList).isEmpty()) { - // if the mapping doesn't exist, create it and add the group directly. - - try { - mapping = new UserGroupMapping(); - } catch (Exception e) { - e.printStackTrace(); - } - mapping.setUsername(username); - mapping.setGroups(""); - mapping.addGroup(groupName); - dataStore.getUserGroupMappingsDao().storeObject(mapping); - - } else { - // otherwise, update the object. - - mapping = dataStore.getUserGroupMappingsDao().getObjects(statementList).get(0); - if (!mapping.getGroups().contains(groupName)) { - mapping.addGroup(groupName); - String newGroups = mapping.getGroups(); - - StatementList update = new StatementList(); - StatementElement st0 = new StatementElement("groups", "=", newGroups, ""); - update.addStatement(st0); - StatementList constraint = new StatementList(); - StatementElement st1 = new StatementElement("username", "=", username, ""); - constraint.addStatement(st1); - dataStore.getUserGroupMappingsDao().updateObject(update, constraint); - } - } - - if (profile != null) { - if (profile.getGroupMemberships() == null) - profile.setGroupMemberships(new ArrayList<>()); - - if (!profile.getGroupMemberships().contains(groupName)) - profile.getGroupMemberships().add(groupName); - - dataStore.updateUserProfile(profile); - } - } - }); - } -} diff --git a/jams-server/src/main/java/net/jami/jams/server/core/workflows/RegisterDeviceFlow.java b/jams-server/src/main/java/net/jami/jams/server/core/workflows/RegisterDeviceFlow.java index 81f22b94f2b864329f6216c392da6ff8f32703fa..8bb441d86d9bab88bc4bf761dd3383c27c45450a 100644 --- a/jams-server/src/main/java/net/jami/jams/server/core/workflows/RegisterDeviceFlow.java +++ b/jams-server/src/main/java/net/jami/jams/server/core/workflows/RegisterDeviceFlow.java @@ -32,6 +32,8 @@ import net.jami.jams.common.objects.requests.DeviceRegistrationRequest; import net.jami.jams.common.objects.responses.DeviceRegistrationResponse; import net.jami.jams.common.objects.user.*; import net.jami.jams.dht.DeviceReceiptGenerator; +import net.jami.jams.common.objects.user.Group; + import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -70,57 +72,40 @@ public class RegisterDeviceFlow { } dataStore.getDeviceDao().storeObject(device); + Group group = new Group(); + statementList = new StatementList(); statementList.addStatement(new StatementElement("username", "=", username, "")); if (dataStore.getUserGroupMappingsDao().getObjects(statementList) != null && !dataStore.getUserGroupMappingsDao().getObjects(statementList).isEmpty()) { UserGroupMapping mapping = dataStore.getUserGroupMappingsDao().getObjects(statementList).get(0); - List<String> list = new ArrayList<>(); - if (mapping.getUsername().equals(userProfile.getUsername())) { - String[] splits = mapping.getGroups().split(","); - - for (int i = 0; i < splits.length; i++) - list.add(splits[i]); - } - if (!list.isEmpty()) - userProfile.setGroupMemberships(list); + statementList = new StatementList(); + statementList.addStatement(new StatementElement("id", "=", mapping.getGroupId(), "")); + group = dataStore.getGroupDao().getObjects(statementList).get(0); } - // Now we build this response out. DeviceRegistrationResponse response = new DeviceRegistrationResponse(); - if (userProfile.getGroupMemberships() != null) { - userProfile.getGroupMemberships().forEach(e -> { - if (!e.equals("")) { - StatementElement st = new StatementElement("name", "=", e, ""); - StatementList statementList1 = new StatementList(); - statementList1.addStatement(st); - - Group group = dataStore.getGroupDao().getObjects(statementList1).get(0); - String policyName = group.getBlueprint(); - if (group != null && policyName != null) { - StatementElement st2 = new StatementElement("name", "=", policyName, ""); - StatementList statementList2 = new StatementList(); - statementList2.addStatement((st2)); - try { - Policy policy = dataStore.getPolicyDao().getObjects(statementList2).get(0); - response.setPolicyData(policy.getPolicyData()); - } catch (Exception e1) { - log.warn("No policy available for user - not adding a policy component to response"); - } - } - } - }); + if (!group.isEmpty() && group.hasBlueprint()) { + StatementElement st2 = new StatementElement("name", "=", group.getBlueprint(), ""); + StatementList statementList2 = new StatementList(); + statementList2.addStatement((st2)); + try { + Policy policy = dataStore.getPolicyDao().getObjects(statementList2).get(0); + response.setPolicyData(policy.getPolicyData()); + } catch (Exception e1) { + log.warn("No policy available for user - not adding a policy component to response"); + } } - // We need to set the device receipt.... + // Device receipt String[] devReceipt = DeviceReceiptGenerator.generateReceipt(user.getPrivateKey(), user.getCertificate().getPublicKey(), device.getCertificate().getPublicKey(), user.getEthAddress()); response.setDeviceReceipt(devReceipt[0]); response.setReceiptSignature(devReceipt[1]); response.setDisplayName(userProfile.getFirstName() + " " + userProfile.getLastName()); - // We need to set + response.setNameServer(nameServer.getURI()); if (userProfile.getProfilePicture() != null) response.setUserPhoto(userProfile.getProfilePicture()); - // Finally we set the certificate chain. + // Chain certificate response.setCertificateChain(new X509Certificate[] { certificateAuthority.getCA(), user.getCertificate(), device.getCertificate() }); return response; diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/directory/DirectoryEntryServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/directory/DirectoryEntryServlet.java index 472680dabbf2452e64a29cf00eac7b30138966ea..33c6804f7dbbe599c3b637decb395fbccdd713f6 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/directory/DirectoryEntryServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/directory/DirectoryEntryServlet.java @@ -78,7 +78,6 @@ public class DirectoryEntryServlet extends HttpServlet { userProfile.setFaxNumber(obj.get("faxNumber").toString()); userProfile.setMobileNumber(obj.get("mobileNumber").toString()); userProfile.setId(obj.get("jamiId").toString()); - userProfile.setGroupMemberships(new ArrayList<>()); userAuthenticationModule.getAuthSources().get(new AuthModuleKey(realm, AuthenticationSourceType.LOCAL)) .setUserProfile(userProfile); @@ -112,7 +111,7 @@ public class DirectoryEntryServlet extends HttpServlet { if (callingUser.getAccessLevel() == AccessLevel.ADMIN || (callingUser.getAccessLevel() == AccessLevel.USER && callingUser.getUsername().equals(targetUser.getUsername()))) { select = new StatementList(); select.addStatement(new StatementElement("username", "=", userProfile.getUsername(), "")); - if (dataStore.updateUserProfile(userProfile) && dataStore.updateUserGroupMappings(userProfile)) + if (dataStore.updateUserProfile(userProfile)) resp.setStatus(200); else resp.sendError(500, "Could not update the users's profile information"); diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/AddGroupServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/AddGroupServlet.java new file mode 100644 index 0000000000000000000000000000000000000000..c29f498ab4a57efb2e4f2c4b072df47a99e33355 --- /dev/null +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/AddGroupServlet.java @@ -0,0 +1,42 @@ +package net.jami.jams.server.servlets.api.admin.group; + +import com.google.gson.JsonObject; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.jami.jams.common.annotations.JsonContent; +import net.jami.jams.common.annotations.ScopedServletMethod; +import net.jami.jams.common.objects.user.AccessLevel; +import net.jami.jams.common.objects.user.Group; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.UUID; + +import static net.jami.jams.server.Server.dataStore; + +@WebServlet("/api/admin/group") +@Slf4j +public class AddGroupServlet extends HttpServlet { + + @Override + @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) + @JsonContent + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + Group group = new Group(); + UUID uuid = UUID.randomUUID(); + group.setId(uuid.toString()); + group.setName(req.getParameter("name")); + group.setBlueprint(req.getParameter("blueprintName")); + if (dataStore.getGroupDao().storeObject(group)) { + JsonObject data = new JsonObject(); + data.addProperty("id", uuid.toString()); + resp.getOutputStream().write(data.toString().getBytes()); + resp.setStatus(200); + } + else + resp.sendError(500, "Could not create a group successfully!"); + } +} \ No newline at end of file diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupProfileServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupProfileServlet.java deleted file mode 100644 index b78615b41ccc778040880b8c766c910f416ecf79..0000000000000000000000000000000000000000 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupProfileServlet.java +++ /dev/null @@ -1,78 +0,0 @@ -package net.jami.jams.server.servlets.api.admin.group; - -import com.jsoniter.output.JsonStream; -import jakarta.servlet.ServletException; -import jakarta.servlet.annotation.WebServlet; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import net.jami.jams.common.annotations.JsonContent; -import net.jami.jams.common.annotations.ScopedServletMethod; -import net.jami.jams.common.dao.StatementElement; -import net.jami.jams.common.dao.StatementList; -import net.jami.jams.common.objects.user.AccessLevel; -import net.jami.jams.common.objects.user.Group; -import net.jami.jams.common.objects.user.UserProfile; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import static net.jami.jams.server.Server.dataStore; - -@WebServlet("/api/admin/group/*") -@Slf4j -public class GroupProfileServlet extends HttpServlet { - - - @Override - @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) - @JsonContent - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - - List<Group> groups = new ArrayList<>(); - Group singleGroup = null; - String groupName = req.getPathInfo().replace("/",""); - - if (!groupName.equals("*")) { - StatementList statementList = new StatementList(); - StatementElement st = new StatementElement("name", "=", groupName, ""); - - statementList.addStatement(st); - if (!dataStore.getGroupDao().getObjects(statementList).isEmpty()) - singleGroup = dataStore.getGroupDao().getObjects(statementList).get(0); - } else { - groups = dataStore.getGroupDao().getObjects(null); - } - - if (singleGroup != null) { - Group finalSingleGroup = singleGroup; - List<UserProfile> profiles = dataStore.getUserProfileDao().getObjects(null).stream().filter(profile -> - profile.getGroupMemberships().contains(finalSingleGroup.getName())).collect(Collectors.toList()); - - profiles.forEach(profile -> - finalSingleGroup.getGroupMembers().add(profile.getUsername())); - - resp.getOutputStream().write(JsonStream.serialize(finalSingleGroup).getBytes()); - resp.setStatus(200); - } - if (!groups.isEmpty()) { - List<UserProfile> profiles = dataStore.getUserProfileDao().getObjects(null); - for (Group group: groups) { - for (UserProfile p: profiles) { - if (p.getGroupMemberships().contains(group.getName())) - group.getGroupMembers().add(p.getUsername()); - } - } - - resp.getOutputStream().write(JsonStream.serialize(groups).getBytes()); - resp.setStatus(200); - } - else { - log.info("No existing groups were found."); - resp.setStatus(200); - } - } -} diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupServlet.java index 77d0d0e4288b5e4eca6d24491e8d60be3393a7b6..88cf7bc1c6279fd73655c81d0ae953741c9b97f0 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupServlet.java @@ -13,66 +13,41 @@ import net.jami.jams.common.dao.StatementElement; import net.jami.jams.common.dao.StatementList; import net.jami.jams.common.objects.user.AccessLevel; import net.jami.jams.common.objects.user.Group; -import net.jami.jams.common.objects.user.UserGroupMapping; import net.jami.jams.common.objects.user.UserProfile; -import net.jami.jams.server.core.workflows.AddUserToGroupFlow; +import org.json.JSONObject; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import static net.jami.jams.server.Server.dataStore; -import static net.jami.jams.server.Server.userAuthenticationModule; -@WebServlet("/api/admin/group") +@WebServlet("/api/admin/group/*") @Slf4j public class GroupServlet extends HttpServlet { + @Override @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) @JsonContent protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - - List<Group> groups = new ArrayList<>(); Group singleGroup = null; + String id = req.getPathInfo().replace("/",""); - if (!req.getParameter("groupName").equals("*")) { - StatementList statementList = new StatementList(); - StatementElement st = new StatementElement("name", "=", req.getParameter("groupName"), ""); + StatementList statementList = new StatementList(); + StatementElement st = new StatementElement("id", "=", id, ""); - statementList.addStatement(st); + statementList.addStatement(st); + if (!dataStore.getGroupDao().getObjects(statementList).isEmpty()) singleGroup = dataStore.getGroupDao().getObjects(statementList).get(0); - } else { - groups = dataStore.getGroupDao().getObjects(null); - } if (singleGroup != null) { - Group finalSingleGroup = singleGroup; - List<UserProfile> profiles = dataStore.getUserProfileDao().getObjects(null).stream().filter(profile -> - profile.getGroupMemberships().contains(finalSingleGroup.getName())).collect(Collectors.toList()); - - profiles.forEach(profile -> - finalSingleGroup.getGroupMembers().add(profile.getUsername())); - - resp.getOutputStream().write(JsonStream.serialize(finalSingleGroup).getBytes()); - resp.setStatus(200); - } - if (!groups.isEmpty()) { - List<UserProfile> profiles = dataStore.getUserProfileDao().getObjects(null); - for (Group group: groups) { - for (UserProfile p: profiles) { - if (p.getGroupMemberships().contains(group.getName())) - group.getGroupMembers().add(p.getUsername()); - } - } - - resp.getOutputStream().write(JsonStream.serialize(groups).getBytes()); + resp.getOutputStream().write(JsonStream.serialize(singleGroup).getBytes()); resp.setStatus(200); } else { - log.info("No existing groups were found."); - resp.setStatus(200); + log.info("No group with this id was found!" ); + resp.setStatus(404); } } @@ -80,125 +55,54 @@ public class GroupServlet extends HttpServlet { @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) @JsonContent protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException { - String name = req.getParameter("groupName"); - String newName = req.getParameter("newName"); - String blueprint = req.getParameter("blueprintName"); - StatementList statementList = new StatementList(); - StatementElement st = new StatementElement("name", "=", name, ""); - statementList.addStatement(st); - Group targetGroup = dataStore.getGroupDao().getObjects(statementList).get(0); - List<UserProfile> profiles = dataStore.getUserProfileDao().getObjects(null).stream().filter(profile -> - profile.getGroupMemberships().contains(targetGroup.getName())).collect(Collectors.toList()); - - profiles.forEach(profile -> - targetGroup.getGroupMembers().add(profile.getUsername())); - - String groupMembers = req.getParameter("groupMembers"); - - // if the username sent isn't already part of the group members, then we add it - if (!targetGroup.getGroupMembers().contains(groupMembers)) { - AddUserToGroupFlow.addUserToGroup(name, req.getParameter("groupMembers")); - } else { - // otherwise remove the group from the user's memberships. - statementList = new StatementList(); - st = new StatementElement("username", "=", groupMembers, ""); - statementList.addStatement(st); - - UserProfile targetProfile = dataStore.getUserProfileDao().getObjects(statementList).get(0); - UserGroupMapping mapping = dataStore.getUserGroupMappingsDao().getObjects(statementList).get(0); - mapping.removeGroup(name); - - StatementList update = new StatementList(); - StatementElement st0 = new StatementElement("groups", "=", mapping.getGroups(), ""); - update.addStatement(st0); - StatementList constraint = new StatementList(); - StatementElement st1 = new StatementElement("username", "=", mapping.getUsername(), ""); - constraint.addStatement(st1); - - // update user mappings - dataStore.getUserGroupMappingsDao().updateObject(update, constraint); - - List<String> groups = targetProfile.getGroupMemberships(); - groups.remove(name); - // TODO: LDAP/AD handling - dataStore.updateUserProfile(targetProfile); - } + String id = req.getPathInfo().replace("/",""); - StatementList update = new StatementList(); - StatementElement st0; + final JSONObject obj = new JSONObject(req.getReader().lines().collect(Collectors.joining(System.lineSeparator()))); - if (newName != null) - st0 = new StatementElement("name", "=", newName, ""); - else - st0 = new StatementElement("name", "=", name, ""); + String name = obj.getString("name"); + String blueprint = obj.getString("blueprint"); - StatementElement st1 = new StatementElement("blueprint", "=", blueprint, ""); + StatementList update = new StatementList(); + + StatementElement st0 = new StatementElement("id", "=", id, ""); update.addStatement(st0); + + StatementElement st1 = new StatementElement("name", "=", name, ""); update.addStatement(st1); + + StatementElement st2 = new StatementElement("blueprint", "=", blueprint, ""); + update.addStatement(st2); + StatementList constraint = new StatementList(); - StatementElement st2 = new StatementElement("name", "=", name, ""); - constraint.addStatement(st2); if (dataStore.getGroupDao().updateObject(update, constraint)) resp.setStatus(200); - else resp.sendError(500, "could not update the group's name!"); - } - - @Override - @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) - @JsonContent - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { - Group group = new Group(); - group.setName(req.getParameter("name")); - group.setBlueprint(req.getParameter("blueprintName")); - group.setGroupMembers(new ArrayList<String>()); - if (dataStore.getGroupDao().storeObject(group)) - resp.setStatus(200); - else - resp.sendError(500, "Could not create a group successfully!"); + else resp.sendError(500, "Could not update group!"); } @Override @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) @JsonContent protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException { + String id = req.getPathInfo().replace("/",""); + + StatementElement statementElement = new StatementElement("id", "=", id, ""); - StatementElement statementElement = new StatementElement("name", "=", req.getParameter("groupName"), ""); StatementList constraint = new StatementList(); constraint.addStatement(statementElement); if (dataStore.getGroupDao().deleteObject(constraint)) { - // if deletion was successful, cascade the deletion onto the users - List<UserProfile> validProfiles = dataStore.getUserProfileDao().getObjects(null).stream().filter(profile -> - profile.getGroupMemberships().remove(req.getParameter("groupName"))).collect(Collectors.toList()); - - validProfiles.parallelStream().forEach(profile -> { - - StatementList statementList = new StatementList(); - StatementElement st = new StatementElement("username", "=", profile.getUsername(), ""); - statementList.addStatement(st); - - UserGroupMapping mapping = dataStore.getUserGroupMappingsDao().getObjects(statementList).get(0); - mapping.removeGroup(req.getParameter("groupName")); - - StatementList update = new StatementList(); - StatementElement st0 = new StatementElement("groups", "=", mapping.getGroups(), ""); - update.addStatement(st0); - StatementList constraint2 = new StatementList(); - StatementElement st1 = new StatementElement("username", "=", mapping.getUsername(), ""); - constraint2.addStatement(st1); - - // update user mappings - dataStore.getUserGroupMappingsDao().updateObject(update, constraint2); - - // then update the profile itself - dataStore.updateUserProfile(profile); - }); - - - resp.setStatus(200); + StatementElement statementElement1 = new StatementElement("username", "=", "*", ""); + StatementElement statementElement2 = new StatementElement("groupId", "=", id, ""); + StatementList constraintMapping = new StatementList(); + constraintMapping.addStatement(statementElement1); + constraintMapping.addStatement(statementElement2); + if (dataStore.getUserGroupMappingsDao().deleteObject(constraintMapping)) + resp.setStatus(200); + else + resp.sendError(500, "Could not delete the group mappings successfully!"); } else { resp.sendError(500, "Could not delete the group successfully!"); } } -} \ No newline at end of file +} diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupsServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupsServlet.java new file mode 100644 index 0000000000000000000000000000000000000000..6fe101f2a94bc6acfd09b3407308ef5f22e247c4 --- /dev/null +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/GroupsServlet.java @@ -0,0 +1,46 @@ +package net.jami.jams.server.servlets.api.admin.group; + +import com.google.gson.JsonObject; +import com.jsoniter.output.JsonStream; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.jami.jams.common.annotations.JsonContent; +import net.jami.jams.common.annotations.ScopedServletMethod; +import net.jami.jams.common.dao.StatementElement; +import net.jami.jams.common.dao.StatementList; +import net.jami.jams.common.objects.user.AccessLevel; +import net.jami.jams.common.objects.user.Group; +import net.jami.jams.common.objects.user.UserProfile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static net.jami.jams.server.Server.dataStore; + +@WebServlet("/api/admin/groups") +@Slf4j +public class GroupsServlet extends HttpServlet { + + @Override + @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) + @JsonContent + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + List<Group> groups = dataStore.getGroupDao().getObjects(null); + + if (!groups.isEmpty()) { + resp.getOutputStream().write(JsonStream.serialize(groups).getBytes()); + resp.setStatus(200); + } + else { + resp.setStatus(404); + } + } +} \ No newline at end of file diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/UserGroupServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/UserGroupServlet.java new file mode 100644 index 0000000000000000000000000000000000000000..82666f4d172513792af28e2524741934eac8c677 --- /dev/null +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/group/UserGroupServlet.java @@ -0,0 +1,123 @@ +package net.jami.jams.server.servlets.api.admin.group; + +import com.google.gson.JsonObject; +import com.jsoniter.output.JsonStream; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.jami.jams.common.annotations.JsonContent; +import net.jami.jams.common.annotations.ScopedServletMethod; +import net.jami.jams.common.dao.StatementElement; +import net.jami.jams.common.dao.StatementList; +import net.jami.jams.common.objects.user.AccessLevel; +import net.jami.jams.common.objects.user.UserGroupMapping; +import org.json.JSONObject; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static net.jami.jams.server.Server.dataStore; + +@WebServlet("/api/admin/group/members/*") +@Slf4j +public class UserGroupServlet extends HttpServlet { + /** + * @apiVersion 1.0.0 + * @api {get} /api/admin/group/[groupId]/members/ Get JAMS groups members + * @apiName getGroupMembers + * @apiGroup Group + * + * @apiSuccess (200) {body} Array of usernames + * @apiSuccessExample {json} Success-Response: + * { + * "usernames": ["user1", "user2", "user3"] + * } + * @apiError (404) {null} null could not find members for this groups + */ + @Override + @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) + @JsonContent + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + + String groupId = req.getPathInfo().replace("/",""); + + StatementList statementList = new StatementList(); + StatementElement st = new StatementElement("groupId", "=", groupId, ""); + + statementList.addStatement(st); + + List<UserGroupMapping> result = dataStore.getUserGroupMappingsDao().getObjects(statementList); + + if (result.isEmpty()) + resp.sendError(404, "No users found for this group!"); + else { + resp.getOutputStream().write(JsonStream.serialize(result).getBytes()); + resp.setStatus(200); + } + } + + @Override + @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) + @JsonContent + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + + String groupId = req.getPathInfo().replace("/",""); + + final JSONObject obj = new JSONObject(req.getReader().lines().collect(Collectors.joining(System.lineSeparator()))); + + String username = obj.getString("username"); + + StatementList statementList = new StatementList(); + statementList.addStatement(new StatementElement("groupId", "=", groupId, "AND")); + statementList.addStatement(new StatementElement("username", "=", username, "")); + + if (!dataStore.getUserGroupMappingsDao().getObjects(statementList).isEmpty()) { + resp.sendError( 409, "The user already part of the group!"); + return; + } + + UserGroupMapping mapping = new UserGroupMapping(); + + mapping.setGroupId(groupId); + mapping.setUsername(username); + + if (dataStore.getUserGroupMappingsDao().storeObject(mapping)) { + resp.setStatus(200); + } + else + resp.sendError(500, "Could not add user to group!"); + + } + + @Override + @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) + @JsonContent + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException { + + String groupId = req.getPathInfo().replace("/",""); + + final JSONObject obj = new JSONObject(req.getReader().lines().collect(Collectors.joining(System.lineSeparator()))); + + String username = obj.getString("username"); + + + StatementElement statementElement1 = new StatementElement("username", "=", username, "AND"); + StatementElement statementElement2 = new StatementElement("groupId", "=", groupId, ""); + + StatementList constraint = new StatementList(); + constraint.addStatement(statementElement1); + constraint.addStatement(statementElement2); + if (dataStore.getUserGroupMappingsDao().deleteObject(constraint)) { + resp.setStatus(200); + } + else { + resp.sendError(500, "Could not delete mapping between user and group!"); + } + } +} \ No newline at end of file diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/users/UserGroupsServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/users/UserGroupsServlet.java new file mode 100644 index 0000000000000000000000000000000000000000..696eb145b4e5ac577bd8a03db9c7f6aa81e0fb10 --- /dev/null +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/admin/users/UserGroupsServlet.java @@ -0,0 +1,59 @@ +package net.jami.jams.server.servlets.api.admin.users; + +import com.jsoniter.output.JsonStream; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.jami.jams.common.annotations.JsonContent; +import net.jami.jams.common.annotations.ScopedServletMethod; +import net.jami.jams.common.dao.StatementElement; +import net.jami.jams.common.dao.StatementList; +import net.jami.jams.common.objects.user.AccessLevel; +import net.jami.jams.common.objects.user.UserGroupMapping; + +import java.io.IOException; +import java.util.List; + +import static net.jami.jams.server.Server.dataStore; + +@WebServlet("/api/admin/user/groups/*") +@Slf4j +public class UserGroupsServlet extends HttpServlet { + + /** + * @apiVersion 1.0.0 + * @api {get} /api/admin/user/[username]/groups/ Get JAMS user groups + * @apiName getUserGroups + * @apiGroup User + * + * @apiSuccess (200) {body} User json user object representation + * @apiSuccessExample {json} Success-Response: + * { + * "groups": ["a5085350-3348-11eb-adc1-0242ac120002", "abde9b76-3348-11eb-adc1-0242ac120002"] + * } + * @apiError (404) {null} null could not find groups for this user + */ + @Override + @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN}) + @JsonContent + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + + String username = req.getPathInfo().replace("/",""); + + StatementList statementList = new StatementList(); + StatementElement st = new StatementElement("username", "=", username, ""); + + statementList.addStatement(st); + + List<UserGroupMapping> result = dataStore.getUserGroupMappingsDao().getObjects(statementList); + + if (result.isEmpty()) + resp.sendError(404, "No groups found for this user!"); + else { + resp.getOutputStream().write(JsonStream.serialize(result).getBytes()); + resp.setStatus(200); + } + } +} \ No newline at end of file diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/user/UserProfileServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/user/UserProfileServlet.java index 7705069ed6784b9e82b1d204aeaa5d9587754a73..fc28c2bf7e74ca3a1dd4c6f6745cb16343cdafd2 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/user/UserProfileServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/user/UserProfileServlet.java @@ -51,7 +51,6 @@ public class UserProfileServlet extends HttpServlet { if (v.getUserProfile(username) != null) profile[0] = v.getUserProfile(username); }); - dataStore.updateUserGroupMappings(profile[0]); if (profile[0] != null) { resp.getOutputStream().write(JsonStream.serialize(profile[0]).getBytes());