From 8868a8b484192ed69ac0aaae52f4099ff6775018 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Sun, 26 Nov 2023 16:21:32 +0100 Subject: [PATCH] =?UTF-8?q?Many-to-many=20roles-applica=E2=80=A0ions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/Logo.jsx | 3 ++ client/src/components/SelectField.scss | 2 +- client/src/icons/multi-role.svg | 5 +++ client/src/locale/en.js | 1 + client/src/locale/nl.js | 1 + client/src/pages/InvitationForm.js | 4 +- client/src/pages/Role.js | 2 +- client/src/pages/RoleForm.js | 40 ++++++++----------- client/src/tabs/Roles.js | 8 ++-- client/src/utils/Manage.js | 25 ++++++++---- client/src/utils/UserRole.js | 7 ++-- .../src/main/java/access/api/HasManage.java | 2 +- .../java/access/api/ManageController.java | 16 +++----- .../main/java/access/api/RoleController.java | 1 + .../access/config/ApplicationConverter.java | 9 ++--- ...iguration.java => ObjectMapperHolder.java} | 14 ++++--- .../main/java/access/manage/LocalManage.java | 3 +- .../src/main/java/access/manage/Manage.java | 10 ++--- .../java/access/manage/ManageIdentifier.java | 2 +- .../model/DistinctManageIdentifiers.java | 22 ++++++++++ .../model/DistinctManagerIdentifiers.java | 12 ------ .../provision/ProvisioningServiceDefault.java | 2 +- .../access/repository/RoleRepository.java | 22 +++++++--- .../V11_0__migrate_applications.java | 5 +-- server/src/main/resources/application.yml | 2 +- ...nfigurableJackson2ObjectMapperFactory.java | 4 +- server/src/test/java/access/Seed.java | 19 ++++++--- .../access/manage/ManageIdentifierTest.java | 18 ++++----- .../provision/graph/GraphClientTest.java | 3 +- .../access/repository/RoleRepositoryTest.java | 9 ++--- welcome/src/components/Logo.jsx | 3 ++ 31 files changed, 158 insertions(+), 118 deletions(-) create mode 100644 client/src/icons/multi-role.svg rename server/src/main/java/access/config/{JacksonConfiguration.java => ObjectMapperHolder.java} (59%) create mode 100644 server/src/main/java/access/model/DistinctManageIdentifiers.java delete mode 100644 server/src/main/java/access/model/DistinctManagerIdentifiers.java diff --git a/client/src/components/Logo.jsx b/client/src/components/Logo.jsx index 4695c196..8b7794d8 100644 --- a/client/src/components/Logo.jsx +++ b/client/src/components/Logo.jsx @@ -8,6 +8,9 @@ export default function Logo({src, className = "", alt = ""}) { if (isEmpty(src)) { return ; } + if (typeof src !== "string") { + return src; + } const urlSrc = srcUrl(src, "jpeg"); return {alt} diff --git a/client/src/components/SelectField.scss b/client/src/components/SelectField.scss index 4caed901..3389c878 100644 --- a/client/src/components/SelectField.scss +++ b/client/src/components/SelectField.scss @@ -25,7 +25,7 @@ border: 1px solid var(--sds--color--gray--300); border-radius: $br; font-size: 16px; - height: 48px; + min-height: 48px; &.creatable { height: auto; diff --git a/client/src/icons/multi-role.svg b/client/src/icons/multi-role.svg new file mode 100644 index 00000000..b8322143 --- /dev/null +++ b/client/src/icons/multi-role.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/src/locale/en.js b/client/src/locale/en.js index 8e07e5b8..4cb70a53 100644 --- a/client/src/locale/en.js +++ b/client/src/locale/en.js @@ -119,6 +119,7 @@ const en = { applicationName: "Application", roleDetails: "Role details", invitationDetails: "Invitation details", + multiple: "Multiple applications", accessRole: "Name", name: "Name", namePlaceHolder: "The name of the role", diff --git a/client/src/locale/nl.js b/client/src/locale/nl.js index 2ddd9bd3..14971149 100644 --- a/client/src/locale/nl.js +++ b/client/src/locale/nl.js @@ -119,6 +119,7 @@ const nl = { applicationName: "Applicatie", roleDetails: "Rol details", invitationDetails: "Uitnodiging details", + multiple: "Meerdere applicaties", accessRole: "Naam", name: "Naam", namePlaceHolder: "Naam van de rol", diff --git a/client/src/pages/InvitationForm.js b/client/src/pages/InvitationForm.js index 46d005ad..0d1642c0 100644 --- a/client/src/pages/InvitationForm.js +++ b/client/src/pages/InvitationForm.js @@ -52,13 +52,13 @@ export const InvitationForm = () => { if (isUserAllowed(AUTHORITIES.MANAGER, user)) { rolesByApplication() .then(res => { - const markedRoles = markAndFilterRoles(user, res, I18n.locale); + const markedRoles = markAndFilterRoles(user, res, I18n.locale, I18n.t("roles.multiple")); setInitialRole(markedRoles); setRoles(markedRoles); setLoading(false); }) } else { - const markedRoles = markAndFilterRoles(user, [], I18n.locale); + const markedRoles = markAndFilterRoles(user, [], I18n.locale, I18n.t("roles.multiple")); setInitialRole(markedRoles); setRoles(markedRoles) setLoading(false); diff --git a/client/src/pages/Role.js b/client/src/pages/Role.js index c12996e1..7d13a3b4 100644 --- a/client/src/pages/Role.js +++ b/client/src/pages/Role.js @@ -58,7 +58,7 @@ export const Role = () => { } Promise.all([roleByID(id, false), userRolesByRoleId(id), invitationsByRoleId(id)]) .then(res => { - deriveApplicationAttributes(res[0]) + deriveApplicationAttributes(res[0], I18n.locale, I18n.t("roles.multiple")) setRole(res[0]); setUserRole(res[1].find(userRole => userRole.role.id === res[0].id && userRole.userInfo.id === user.id)); const newTabs = [ diff --git a/client/src/pages/RoleForm.js b/client/src/pages/RoleForm.js index 21f2a67a..0db3d61f 100644 --- a/client/src/pages/RoleForm.js +++ b/client/src/pages/RoleForm.js @@ -3,7 +3,7 @@ import {useNavigate, useParams} from "react-router-dom"; import {useAppStore} from "../stores/AppStore"; import I18n from "../locale/I18n"; import {AUTHORITIES, isUserAllowed, urnFromRole} from "../utils/UserRole"; -import {allProviders, createRole, deleteRole, me, roleByID, shortNameExists, updateRole, validate} from "../api"; +import {allProviders, createRole, deleteRole, me, roleByID, updateRole, validate} from "../api"; import {Button, ButtonType, Checkbox, Loader} from "@surfnet/sds"; import "./RoleForm.scss"; import {UnitHeader} from "../components/UnitHeader"; @@ -23,6 +23,7 @@ export const RoleForm = () => { const {id} = useParams(); const nameRef = useRef(); + const required = ["name", "description", "applications"]; const {user, setFlash, config} = useAppStore(state => state); const [role, setRole] = useState({name: "", shortName: "", defaultExpiryDays: 0}); @@ -30,10 +31,9 @@ export const RoleForm = () => { const [isNewRole, setNewRole] = useState(true); const [loading, setLoading] = useState(true); const [initial, setInitial] = useState(true); - const required = ["name", "description", "manageId"]; const [alreadyExists, setAlreadyExists] = useState({}); const [invalidValues, setInvalidValues] = useState({}); - const [managementOption, setManagementOption] = useState({}); + const [managementOption, setManagementOption] = useState([]); const [confirmation, setConfirmation] = useState({}); const [confirmationOpen, setConfirmationOpen] = useState(false); @@ -70,11 +70,11 @@ export const RoleForm = () => { if (newRole) { const providerOption = singleProviderToOption(user.superUser ? res[0][0] : user.institutionAdmin ? user.applications[0] : user.userRoles[0].role.application); - setManagementOption(providerOption); - setRole({...role, manageId: providerOption.value, manageType: providerOption.type.toUpperCase()}) + setManagementOption([providerOption]); + setRole({...role, applications: [providerOption]}) } else { breadcrumbPath.push({path: `/roles/${res[0].id}`, value: name}); - setManagementOption(singleProviderToOption(res[0].application)); + setManagementOption([singleProviderToOption(res[0].application)]); } breadcrumbPath.push({value: I18n.t(`roles.${newRole ? "new" : "edit"}`, {name: name})}); useAppStore.setState({breadcrumbPath: breadcrumbPath}); @@ -89,14 +89,6 @@ export const RoleForm = () => { }, [id]); // eslint-disable-line react-hooks/exhaustive-deps - const validateShortName = (shortName, manageId) => { - if (!isEmpty(manageId) && !isEmpty(shortName)) { - shortNameExists(shortName, manageId, role.id) - .then(json => setAlreadyExists({...alreadyExists, shortName: json.exists})); - } - return true; - } - const validateValue = (type, attribute, value) => { if (!isEmpty(value)) { validate(type, value) @@ -189,11 +181,6 @@ export const RoleForm = () => { value={role.name || ""} placeholder={I18n.t("roles.namePlaceHolder")} error={alreadyExists.name || (!initial && isEmpty(role.name))} - onBlur={e => { - if (isNewRole) { - validateShortName(constructShortName(e.target.value), role.manageId); - } - }} onRef={nameRef} onChange={e => { const shortName = isNewRole ? constructShortName(e.target.value) : role.shortName; @@ -234,17 +221,22 @@ export const RoleForm = () => { provider.value !== managementOption.value)} - onChange={option => { - setManagementOption(option); - setRole({...role, manageId: option.value, manageType: option.type.toUpperCase()}); - validateShortName(constructShortName(role.name), option.value); + options={options.filter(option => !managementOption.some(provider => option.value === provider.value))} + onChange={options => { + setManagementOption(options); + setRole({...role, applications: options}); }} searchable={true} clearable={false} + isMulti={true} disabled={options.length === 1} toolTip={I18n.t("tooltips.manageService")} /> + {(!initial && isEmpty(role.applications)) && + } + { } else { rolesByApplication() .then(res => { - const newRoles = markAndFilterRoles(user, res, I18n.locale); + const newRoles = markAndFilterRoles(user, res, I18n.locale, I18n.t("roles.multiple")); setRoles(newRoles); initFilterValues(newRoles); setLoading(false); }) } } else { - const newRoles = markAndFilterRoles(user, [], I18n.locale); + const newRoles = markAndFilterRoles(user, [], I18n.locale, I18n.t("roles.multiple")); setRoles(newRoles); initFilterValues(newRoles); setLoading(false); @@ -100,7 +100,7 @@ export const Roles = () => { const delayedAutocomplete = debounce(query => { searchRoles(query) .then(res => { - setRoles(markAndFilterRoles(user, res, I18n.locale)); + setRoles(markAndFilterRoles(user, res, I18n.locale, I18n.t("roles.multiple"))); setMoreToShow(res.length === 15); setNoResults(res.length === 0); setSearching(false); @@ -151,7 +151,7 @@ export const Roles = () => { header: "", mapper: role => { return
- logo + {typeof role.logo === "string" ? logo :role.logo}
} }, diff --git a/client/src/utils/Manage.js b/client/src/utils/Manage.js index 2dd6f118..311e94d7 100644 --- a/client/src/utils/Manage.js +++ b/client/src/utils/Manage.js @@ -1,4 +1,5 @@ import {isEmpty} from "./Utils"; +import {ReactComponent as MultipleIcon} from "../icons/multi-role.svg"; export const singleProviderToOption = provider => { const organisation = provider["OrganizationName:en"]; @@ -6,7 +7,9 @@ export const singleProviderToOption = provider => { return { value: provider.id, label: `${provider["name:en"]}${organisationValue}`, - type: provider.type + type: provider.type.toUpperCase(), + manageType: provider.type.toUpperCase(), + manageId: provider.id }; } @@ -14,12 +17,20 @@ export const providersToOptions = providers => { return providers.map(provider => singleProviderToOption(provider)); } -export const deriveApplicationAttributes = (role, locale) => { - const application = role.application; - if (!isEmpty(application)) { - role.applicationName = application[`name:${locale}`] || application["name:en"] - role.applicationOrganizationName = application[`OrganizationName:${locale}`] || application["OrganizationName:en"]; - role.logo = application.logo; +export const deriveApplicationAttributes = (role, locale, multiple) => { + const applications = role.applicationMaps; + if (!isEmpty(applications)) { + if (applications.length === 1) { + role.applicationName = applications[0][`name:${locale}`] || applications[0]["name:en"]; + role.applicationOrganizationName = applications[0][`OrganizationName:${locale}`] || applications[0]["OrganizationName:en"]; + role.logo = applications[0].logo; + } else { + role.applicationName = multiple; + role.applicationOrganizationName = applications + .map(app => app[`OrganizationName:${locale}`] || app["OrganizationName:en"]) + .join(", ") + role.logo = ; + } } } diff --git a/client/src/utils/UserRole.js b/client/src/utils/UserRole.js index c414d406..08124a5d 100644 --- a/client/src/utils/UserRole.js +++ b/client/src/utils/UserRole.js @@ -1,5 +1,6 @@ import {isEmpty} from "./Utils"; import {deriveApplicationAttributes} from "./Manage"; +import I18n from "../locale/I18n"; export const INVITATION_STATUS = { OPEN: "OPEN", @@ -99,18 +100,18 @@ export const allowedToRenewUserRole = (user, userRole) => { export const urnFromRole = (groupUrnPrefix, role) => `${groupUrnPrefix}:${role.manageId}:${role.shortName}`; -export const markAndFilterRoles = (user, allRoles, locale) => { +export const markAndFilterRoles = (user, allRoles, locale, multiple) => { allRoles.forEach(role => { role.isUserRole = false; role.label = role.name; role.value = role.id; - deriveApplicationAttributes(role, locale); + deriveApplicationAttributes(role, locale, multiple); }); const userRoles = user.userRoles; userRoles.forEach(userRole => { userRole.isUserRole = true; const role = userRole.role; - deriveApplicationAttributes(role, locale); + deriveApplicationAttributes(role, locale, multiple); userRole.name = role.name; userRole.label = role.name; userRole.value = role.id; diff --git a/server/src/main/java/access/api/HasManage.java b/server/src/main/java/access/api/HasManage.java index 523de426..37a9c08a 100644 --- a/server/src/main/java/access/api/HasManage.java +++ b/server/src/main/java/access/api/HasManage.java @@ -24,7 +24,7 @@ default List getGroupedProviders(List requestedRoles) { .collect(Collectors.toSet()) .stream() .map(manageIdentifier -> { - Map provider = getManage().providerById(manageIdentifier.entityType(), manageIdentifier.id()); + Map provider = getManage().providerById(manageIdentifier.manageType(), manageIdentifier.manageId()); String id = (String) provider.get("id"); return new GroupedProviders( provider, diff --git a/server/src/main/java/access/api/ManageController.java b/server/src/main/java/access/api/ManageController.java index 512d9395..c98ae1f3 100644 --- a/server/src/main/java/access/api/ManageController.java +++ b/server/src/main/java/access/api/ManageController.java @@ -22,10 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME; @@ -76,19 +73,16 @@ public ResponseEntity>> providers(@Parameter(hidden = t @GetMapping("applications") public ResponseEntity>>> applications(@Parameter(hidden = true) User user) { UserPermissions.assertSuperUser(user); - List manageIdentifiers = roleRepository.findDistinctManageIdentifiers().stream() - .map(tuple -> new ManageIdentifier(tuple[0].replaceAll("[\"\\]\\[]", ""), - EntityType.valueOf(tuple[1].replaceAll("[\"\\]\\[]", "")))) - .toList(); - Map> groupedByManageType = manageIdentifiers.stream().collect(Collectors.groupingBy(ManageIdentifier::entityType)); + Set manageIdentifiers = roleRepository.findDistinctManageIdentifiers(); + Map> groupedByManageType = manageIdentifiers.stream().collect(Collectors.groupingBy(ManageIdentifier::manageType)); List> providers = groupedByManageType.entrySet().stream() .map(entry -> manage.providersByIdIn( entry.getKey(), - entry.getValue().stream().map(ManageIdentifier::id).collect(Collectors.toList()))) + entry.getValue().stream().map(ManageIdentifier::manageId).collect(Collectors.toList()))) .flatMap(Collection::stream) .toList(); List> provisionings = manage.provisioning(manageIdentifiers.stream() - .map(ManageIdentifier::id) + .map(ManageIdentifier::manageId) .toList()); return ResponseEntity.ok(Map.of( "providers", providers, diff --git a/server/src/main/java/access/api/RoleController.java b/server/src/main/java/access/api/RoleController.java index db05e7a9..8d665419 100644 --- a/server/src/main/java/access/api/RoleController.java +++ b/server/src/main/java/access/api/RoleController.java @@ -122,6 +122,7 @@ public ResponseEntity newRole(@Validated @RequestBody Role role, @Paramete throw new NotAllowedException( String.format("Duplicate name: '%s' for manage entity:'%s'", shortName, application.getManageId())); } + role.setIdentifier(UUID.randomUUID().toString()); return saveOrUpdate(role, user); } diff --git a/server/src/main/java/access/config/ApplicationConverter.java b/server/src/main/java/access/config/ApplicationConverter.java index 21655543..ef97b725 100644 --- a/server/src/main/java/access/config/ApplicationConverter.java +++ b/server/src/main/java/access/config/ApplicationConverter.java @@ -12,20 +12,19 @@ public class ApplicationConverter implements AttributeConverter, String> { - private final static ObjectMapper objectMapper = new ObjectMapper(); - @SneakyThrows @Override public String convertToDatabaseColumn(Set attribute) { - return objectMapper.writeValueAsString(attribute); + return ObjectMapperHolder.objectMapper.writeValueAsString(attribute); } @SneakyThrows @Override @SuppressWarnings("unchecked") public Set convertToEntityAttribute(String dbData) { - Set> set = objectMapper.readValue(dbData, Set.class); - return set.stream().map(m -> new Application(m.get("manageId"), EntityType.valueOf(m.get("manageType")))) + Set> applications = ObjectMapperHolder.objectMapper.readValue(dbData, Set.class); + return applications.stream() + .map(m -> new Application(m.get("manageId"), EntityType.valueOf(m.get("manageType")))) .collect(Collectors.toSet()); } } diff --git a/server/src/main/java/access/config/JacksonConfiguration.java b/server/src/main/java/access/config/ObjectMapperHolder.java similarity index 59% rename from server/src/main/java/access/config/JacksonConfiguration.java rename to server/src/main/java/access/config/ObjectMapperHolder.java index 9d71ac0b..cb06dcb9 100644 --- a/server/src/main/java/access/config/JacksonConfiguration.java +++ b/server/src/main/java/access/config/ObjectMapperHolder.java @@ -10,16 +10,18 @@ import org.springframework.context.annotation.Primary; @Configuration -public class JacksonConfiguration { +public class ObjectMapperHolder { + + public static final ObjectMapper objectMapper = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .registerModule(new JavaTimeModule()) + .registerModule(new Jdk8Module()); @Bean @Primary public ObjectMapper objectMapper() { - return new ObjectMapper() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .registerModule(new JavaTimeModule()) - .registerModule(new Jdk8Module()); + return objectMapper; } } diff --git a/server/src/main/java/access/manage/LocalManage.java b/server/src/main/java/access/manage/LocalManage.java index f838948a..b737533f 100644 --- a/server/src/main/java/access/manage/LocalManage.java +++ b/server/src/main/java/access/manage/LocalManage.java @@ -46,7 +46,8 @@ public List> providers(EntityType... entityTypes) { @Override public List> providersByIdIn(EntityType entityType, List identifiers) { - return transformProvider(this.allProviders.get(entityType).stream() + List> providers = this.allProviders.get(entityType); + return transformProvider(providers.stream() .filter(provider -> identifiers.contains(provider.get("_id"))) .collect(Collectors.toList())); } diff --git a/server/src/main/java/access/manage/Manage.java b/server/src/main/java/access/manage/Manage.java index 020b794c..8f84578b 100644 --- a/server/src/main/java/access/manage/Manage.java +++ b/server/src/main/java/access/manage/Manage.java @@ -40,7 +40,7 @@ default Map transformProvider(Map provider) { Map metaDataFields = (Map) data.get("metaDataFields"); //Can't use Map.of as values can be null Map application = new HashMap<>(); - //Due to the different API's we are using, the result sometimes contains an "_id" and sometimes an "id" + //Due to the different API's we are using, the result sometimes contains an "_id" and sometimes an "manageId" Object id = provider.get("id"); if (id != null) { application.put("id", id); @@ -80,17 +80,17 @@ default Map transformProvider(Map provider) { } default List addManageMetaData(List roles) { - //First get all unique remote manage entities and group them by entityType + //First get all unique remote manage entities and group them by manageType Map> groupedManageIdentifiers = roles.stream() .map(Role::getApplications) .flatMap(Collection::stream) .map(application -> new ManageIdentifier(application.getManageId(), application.getManageType())) .collect(Collectors.toSet()) .stream() - .collect(Collectors.groupingBy(ManageIdentifier::entityType)); - //Now for each entityType (hopefully one, maximum two) we call manage and create a map with as key the id in manage + .collect(Collectors.groupingBy(ManageIdentifier::manageType)); + //Now for each manageType (hopefully one, maximum two) we call manage and create a map with as key the manageId in manage Map> remoteApplications = groupedManageIdentifiers.entrySet().stream() - .map(entry -> this.providersByIdIn(entry.getKey(), entry.getValue().stream().map(ManageIdentifier::id).toList())) + .map(entry -> this.providersByIdIn(entry.getKey(), entry.getValue().stream().map(ManageIdentifier::manageId).toList())) .flatMap(List::stream) .collect(Collectors.toMap(map -> (String) map.get("id"), map -> map)); //Add the metadata to the role diff --git a/server/src/main/java/access/manage/ManageIdentifier.java b/server/src/main/java/access/manage/ManageIdentifier.java index bfcd44e0..dc333997 100644 --- a/server/src/main/java/access/manage/ManageIdentifier.java +++ b/server/src/main/java/access/manage/ManageIdentifier.java @@ -1,5 +1,5 @@ package access.manage; -public record ManageIdentifier(String id, EntityType entityType) { +public record ManageIdentifier(String manageId, EntityType manageType) { } \ No newline at end of file diff --git a/server/src/main/java/access/model/DistinctManageIdentifiers.java b/server/src/main/java/access/model/DistinctManageIdentifiers.java new file mode 100644 index 00000000..0936a6a5 --- /dev/null +++ b/server/src/main/java/access/model/DistinctManageIdentifiers.java @@ -0,0 +1,22 @@ +package access.model; + +import access.config.ObjectMapperHolder; +import access.manage.ManageIdentifier; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.SneakyThrows; + +import java.util.List; +import java.util.Set; + +public interface DistinctManageIdentifiers { + + String getApplications(); + + @SneakyThrows + default List manageIdentifiers() { + String applications = getApplications(); + return ObjectMapperHolder.objectMapper.readValue(applications, new TypeReference<>() { + }); + } + +} diff --git a/server/src/main/java/access/model/DistinctManagerIdentifiers.java b/server/src/main/java/access/model/DistinctManagerIdentifiers.java deleted file mode 100644 index 7c0b3c7a..00000000 --- a/server/src/main/java/access/model/DistinctManagerIdentifiers.java +++ /dev/null @@ -1,12 +0,0 @@ -package access.model; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class DistinctManagerIdentifiers { - - private String identifiers; - -} diff --git a/server/src/main/java/access/provision/ProvisioningServiceDefault.java b/server/src/main/java/access/provision/ProvisioningServiceDefault.java index a6cb9806..1ab4437f 100644 --- a/server/src/main/java/access/provision/ProvisioningServiceDefault.java +++ b/server/src/main/java/access/provision/ProvisioningServiceDefault.java @@ -294,7 +294,7 @@ private void updateRequest(Provisioning provisioning, private List getProvisionings(User user) { Set manageIdentifiers = user.manageIdentifierSet(); - List identifiers = manageIdentifiers.stream().map(ManageIdentifier::id).toList(); + List identifiers = manageIdentifiers.stream().map(ManageIdentifier::manageId).toList(); return manage.provisioning(identifiers).stream().map(Provisioning::new).toList(); } diff --git a/server/src/main/java/access/repository/RoleRepository.java b/server/src/main/java/access/repository/RoleRepository.java index 4f5c3907..4d21447f 100644 --- a/server/src/main/java/access/repository/RoleRepository.java +++ b/server/src/main/java/access/repository/RoleRepository.java @@ -1,14 +1,17 @@ package access.repository; -import access.model.DistinctManagerIdentifiers; +import access.manage.ManageIdentifier; +import access.model.DistinctManageIdentifiers; import access.model.Role; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; @Repository public interface RoleRepository extends JpaRepository { @@ -20,15 +23,24 @@ public interface RoleRepository extends JpaRepository { @Query(value = "SELECT *, (SELECT COUNT(*) FROM user_roles ur WHERE ur.role_id=r.id) AS userRoleCount " + - "FROM roles r WHERE json_contains(applications->'$[*].manageId', json_array(?1))", + "FROM roles r WHERE JSON_CONTAINS(applications->'$[*].manageId', json_array(?1))", nativeQuery = true) List findByApplicationsManageId(String manageId); - @Query(value = "SELECT DISTINCT JSON_EXTRACT(applications,'$[*].manageId','$[0].manageType') FROM roles", nativeQuery = true) - List findDistinctManageIdentifiers(); + @Query(value = "SELECT DISTINCT applications FROM roles", nativeQuery = true) + List doFindDistinctManageIdentifiers(); + + @Query(value = "SELECT 1", nativeQuery = true) + default Set findDistinctManageIdentifiers() { + return this.doFindDistinctManageIdentifiers() + .stream() + .map(DistinctManageIdentifiers::manageIdentifiers) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } @Query(value = "SELECT *, (SELECT COUNT(*) FROM user_roles ur WHERE ur.role_id=r.id) as userRoleCount " + - "FROM roles r WHERE json_contains(applications->'$[*].manageId', json_array(?1)) and short_name = ?2", + "FROM roles r WHERE JSON_CONTAINS(applications->'$[*].manageId', json_array(?1)) and short_name = ?2", nativeQuery = true) Optional findByShortNameIgnoreCaseAndApplicationsManageId(String managerId, String name); diff --git a/server/src/main/java/db/mysql/migration/V11_0__migrate_applications.java b/server/src/main/java/db/mysql/migration/V11_0__migrate_applications.java index 209cab84..11fb8e6e 100644 --- a/server/src/main/java/db/mysql/migration/V11_0__migrate_applications.java +++ b/server/src/main/java/db/mysql/migration/V11_0__migrate_applications.java @@ -1,5 +1,6 @@ package db.mysql.migration; +import access.config.ObjectMapperHolder; import access.manage.EntityType; import access.model.Application; import com.fasterxml.jackson.core.JsonProcessingException; @@ -20,8 +21,6 @@ public class V11_0__migrate_applications extends BaseJavaMigration { public void migrate(Context context) { JdbcTemplate jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(context.getConnection(), true)); - ObjectMapper objectMapper = new ObjectMapper(); - jdbcTemplate.query("SELECT id, manage_id, manage_type FROM roles", rs -> { long roleId = rs.getLong("id"); jdbcTemplate.update("UPDATE roles SET identifier = ? WHERE id = ?", UUID.randomUUID().toString(), roleId); @@ -31,7 +30,7 @@ public void migrate(Context context) { Set applications = Set.of(new Application(manageId, EntityType.valueOf(manageType))); String jsonNode; try { - jsonNode = objectMapper.writeValueAsString(applications); + jsonNode = ObjectMapperHolder.objectMapper.writeValueAsString(applications); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index 51629869..b1b45df7 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -124,7 +124,7 @@ email: enabled: false manage: - enabled: True + enabled: False url: "https://manage.test2.surfconext.nl" user: invite password: secret diff --git a/server/src/test/java/access/ConfigurableJackson2ObjectMapperFactory.java b/server/src/test/java/access/ConfigurableJackson2ObjectMapperFactory.java index 15f5e494..f6eee0ba 100644 --- a/server/src/test/java/access/ConfigurableJackson2ObjectMapperFactory.java +++ b/server/src/test/java/access/ConfigurableJackson2ObjectMapperFactory.java @@ -1,6 +1,6 @@ package access; -import access.config.JacksonConfiguration; +import access.config.ObjectMapperHolder; import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.path.json.mapper.factory.DefaultJackson2ObjectMapperFactory; @@ -8,7 +8,7 @@ public class ConfigurableJackson2ObjectMapperFactory extends DefaultJackson2ObjectMapperFactory { - private final static ObjectMapper objectMapper = new JacksonConfiguration().objectMapper(); + private final static ObjectMapper objectMapper = new ObjectMapperHolder().objectMapper(); @Override public ObjectMapper create(Type cls, String charset) { diff --git a/server/src/test/java/access/Seed.java b/server/src/test/java/access/Seed.java index 385b0578..afd2bfef 100644 --- a/server/src/test/java/access/Seed.java +++ b/server/src/test/java/access/Seed.java @@ -53,17 +53,24 @@ public void doSeed() { doSave(this.userRepository, superUser, institutionAdmin, manager, inviter, guest); Role wiki = - new Role("Wiki", "Wiki desc", "https://landingpage.com", application("1", EntityType.SAML20_SP), 365, false, false); + new Role("Wiki", "Wiki desc", "https://landingpage.com", + application("1", EntityType.SAML20_SP), 365, false, false); Role network = - new Role("Network", "Network desc", "https://landingpage.com", application("2", EntityType.SAML20_SP), 365, false, false); + new Role("Network", "Network desc", "https://landingpage.com", + application("2", EntityType.SAML20_SP), 365, false, false); Role storage = - new Role("Storage", "Storage desc", "https://landingpage.com", application("3", EntityType.SAML20_SP), 365, false, false); + new Role("Storage", "Storage desc", "https://landingpage.com", + Set.of(new Application("3", EntityType.SAML20_SP), new Application("6", EntityType.OIDC10_RP)), + 365, false, false); Role research = - new Role("Research", "Research desc", "https://landingpage.com", application("4", EntityType.SAML20_SP), 365, false, false); + new Role("Research", "Research desc", "https://landingpage.com", + application("4", EntityType.SAML20_SP), 365, false, false); Role calendar = - new Role("Calendar", "Calendar desc", "https://landingpage.com", application("5", EntityType.OIDC10_RP), 365, false, false); + new Role("Calendar", "Calendar desc", "https://landingpage.com", + application("5", EntityType.OIDC10_RP), 365, false, false); Role mail = - new Role("Mail", "Mail desc", "https://landingpage.com", application("5", EntityType.OIDC10_RP), 365, false, false); + new Role("Mail", "Mail desc", "https://landingpage.com", + application("5", EntityType.OIDC10_RP), 365, false, false); doSave(this.roleRepository, wiki, network, storage, research, calendar, mail); UserRole wikiManager = diff --git a/server/src/test/java/access/manage/ManageIdentifierTest.java b/server/src/test/java/access/manage/ManageIdentifierTest.java index 631e906d..8ec5b520 100644 --- a/server/src/test/java/access/manage/ManageIdentifierTest.java +++ b/server/src/test/java/access/manage/ManageIdentifierTest.java @@ -5,23 +5,21 @@ import java.util.HashSet; import java.util.Set; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; class ManageIdentifierTest { @Test void testEquals() { - assertEquals(new ManageIdentifier("id", EntityType.SAML20_SP), new ManageIdentifier("id", EntityType.SAML20_SP)); + ManageIdentifier one = new ManageIdentifier("id", EntityType.SAML20_SP); + ManageIdentifier two = new ManageIdentifier("id", EntityType.SAML20_SP); + assertEquals(one, two); + assertEquals(one.hashCode(), two.hashCode()); + Set set = new HashSet<>(); - set.add(new ManageIdentifier("id", EntityType.SAML20_SP)); - set.add(new ManageIdentifier("id", EntityType.SAML20_SP)); + set.add(one); + set.add(two); assertEquals(1, set.size()); } - @Test - void testHashCode() { - assertEquals( - new ManageIdentifier("id", EntityType.SAML20_SP).hashCode(), - new ManageIdentifier("id", EntityType.SAML20_SP).hashCode()); - } } \ No newline at end of file diff --git a/server/src/test/java/access/provision/graph/GraphClientTest.java b/server/src/test/java/access/provision/graph/GraphClientTest.java index 8c49a52c..3f1158dd 100644 --- a/server/src/test/java/access/provision/graph/GraphClientTest.java +++ b/server/src/test/java/access/provision/graph/GraphClientTest.java @@ -1,5 +1,6 @@ package access.provision.graph; +import access.config.ObjectMapperHolder; import access.exception.RemoteException; import access.manage.EntityType; import access.manage.LocalManage; @@ -16,7 +17,7 @@ class GraphClientTest { final GraphClient graphClient = new GraphClient("http://localhost:8080", "test.eduid.nl"); - final LocalManage localManage = new LocalManage(new ObjectMapper(), false); + final LocalManage localManage = new LocalManage( ObjectMapperHolder.objectMapper, false); @Test void newUserRequest() { diff --git a/server/src/test/java/access/repository/RoleRepositoryTest.java b/server/src/test/java/access/repository/RoleRepositoryTest.java index c519a9ac..047da9f2 100644 --- a/server/src/test/java/access/repository/RoleRepositoryTest.java +++ b/server/src/test/java/access/repository/RoleRepositoryTest.java @@ -1,11 +1,12 @@ package access.repository; import access.AbstractTest; -import access.model.DistinctManagerIdentifiers; +import access.manage.ManageIdentifier; import access.model.Role; import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -19,9 +20,7 @@ void search() { @Test void findDistinctByManageId() { - List manageIdentifiers = roleRepository.findDistinctManageIdentifiers().stream() - .map(tuple -> new String[] {tuple[0].replaceAll("[\"\\]\\[]",""), - tuple[1].replaceAll("[\"\\]\\[]","")}).toList(); - assertEquals(5, manageIdentifiers.size()); + Set manageIdentifiers = roleRepository.findDistinctManageIdentifiers(); + assertEquals(6, manageIdentifiers.size()); } } \ No newline at end of file diff --git a/welcome/src/components/Logo.jsx b/welcome/src/components/Logo.jsx index 4695c196..8b7794d8 100644 --- a/welcome/src/components/Logo.jsx +++ b/welcome/src/components/Logo.jsx @@ -8,6 +8,9 @@ export default function Logo({src, className = "", alt = ""}) { if (isEmpty(src)) { return ; } + if (typeof src !== "string") { + return src; + } const urlSrc = srcUrl(src, "jpeg"); return {alt}