From d47f86f1c45ef6469a426b0f824736d893be230d Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Wed, 13 Dec 2023 15:49:59 +0100 Subject: [PATCH] WWIP for landingpage per application per role --- client/src/locale/en.js | 2 +- client/src/pages/InvitationForm.js | 69 ++++++++++++------- client/src/pages/InvitationForm.scss | 31 ++++++++- .../main/java/access/api/RoleController.java | 38 ++++++---- .../main/java/access/model/Application.java | 8 ++- .../java/access/model/ApplicationUsage.java | 37 ++++++++++ server/src/main/java/access/model/Role.java | 38 ++++++---- server/src/main/java/access/model/User.java | 4 +- .../ApplicationUsageRepository.java | 16 +++++ .../access/repository/RoleRepository.java | 2 +- .../java/access/teams/TeamsController.java | 19 +++-- ...e_applications_per_role_landings_page.java | 31 +++++++++ ..._0__landings_page_per_application_role.sql | 13 ++++ .../V23_0__remove_application_roles.sql | 2 + server/src/test/java/access/AbstractTest.java | 20 ++++-- .../test/java/access/WithApplicationTest.java | 5 +- .../access/api/InvitationControllerTest.java | 2 +- .../java/access/api/RoleControllerTest.java | 8 ++- .../java/access/model/ApplicationTest.java | 2 +- .../ApplicationUsageRepositoryTest.java | 42 +++++++++++ .../access/security/UserPermissionsTest.java | 6 +- 21 files changed, 319 insertions(+), 76 deletions(-) create mode 100644 server/src/main/java/access/model/ApplicationUsage.java create mode 100644 server/src/main/java/access/repository/ApplicationUsageRepository.java create mode 100644 server/src/main/java/db/mysql/migration/V22_0__migrate_applications_per_role_landings_page.java create mode 100644 server/src/main/resources/db/mysql/migration/V21_0__landings_page_per_application_role.sql create mode 100644 server/src/main/resources/db/mysql/migration/V23_0__remove_application_roles.sql create mode 100644 server/src/test/java/access/repository/ApplicationUsageRepositoryTest.java diff --git a/client/src/locale/en.js b/client/src/locale/en.js index fd0a241f..f5ab7eaf 100644 --- a/client/src/locale/en.js +++ b/client/src/locale/en.js @@ -343,7 +343,7 @@ const en = { inviter: "Send invitations to persons who will - once accepted - become guest users for the application", overrideSettingsAllowed: "If checked then invitations for this role can't override the advanced settings (e.g. email equality, eduID only and the role expiry end date)", removeUserRole: "Remove all selected user roles", - guestRoleIncludedTooltip: "Do you also want to invite the invitees as guests when they accept the invitation?", + guestRoleIncludedTooltip: "Do you also want to grant the invitees the guest role when they accept the invitation?", }, confirmationDialog: { title: "Confirm", diff --git a/client/src/pages/InvitationForm.js b/client/src/pages/InvitationForm.js index 52f1b258..070ec2d6 100644 --- a/client/src/pages/InvitationForm.js +++ b/client/src/pages/InvitationForm.js @@ -13,7 +13,7 @@ import {ReactComponent as UserIcon} from "@surfnet/sds/icons/functional-icons/id import {ReactComponent as UpIcon} from "@surfnet/sds/icons/functional-icons/arrow-up-2.svg"; import {ReactComponent as DownIcon} from "@surfnet/sds/icons/functional-icons/arrow-down-2.svg"; import {newInvitation, rolesByApplication} from "../api"; -import {Button, ButtonType, Checkbox, Loader, RadioOptions, Tooltip} from "@surfnet/sds"; +import {Button, ButtonType, Loader, RadioOptions, Switch, Tooltip} from "@surfnet/sds"; import "./InvitationForm.scss"; import {UnitHeader} from "../components/UnitHeader"; import InputField from "../components/InputField"; @@ -268,29 +268,52 @@ export const InvitationForm = () => { {I18n.t("roles.hideAdvancedSettings")} - setInvitation({...invitation, enforceEmailEquality: e.target.checked})} - info={I18n.t("invitations.enforceEmailEquality")} - readOnly={selectedRoles.some(role => !role.overrideSettingsAllowed)} - tooltip={I18n.t("tooltips.enforceEmailEqualityTooltip")} - /> - setInvitation({...invitation, eduIDOnly: e.target.checked})} - info={I18n.t("invitations.eduIDOnly")} - readOnly={selectedRoles.some(role => !role.overrideSettingsAllowed)} - tooltip={I18n.t("tooltips.eduIDOnlyTooltip")} - /> + {selectedRoles.every(role => role.overrideSettingsAllowed) && + <> +
+
+ {I18n.t("invitations.enforceEmailEquality")} + {I18n.t("tooltips.enforceEmailEqualityTooltip")} +
+ setInvitation({ + ...invitation, + enforceEmailEquality: val + })}/> +
- setInvitation({...invitation, guestRoleIncluded: e.target.checked})} - info={I18n.t("invitations.guestRoleIncluded")} - readOnly={invitation.intendedAuthority === AUTHORITIES.GUEST} - tooltip={I18n.t("tooltips.guestRoleIncludedTooltip")} - /> + } + + {selectedRoles.every(role => role.overrideSettingsAllowed) && + <> +
+
+ {I18n.t("invitations.eduIDOnly")} + {I18n.t("tooltips.eduIDOnlyTooltip")} +
+ setInvitation({...invitation, eduIDOnly: val})}/> +
+ + } + + {invitation.intendedAuthority === AUTHORITIES.GUEST && <> +
+
+ {I18n.t("invitations.guestRoleIncluded")} + {I18n.t("tooltips.guestRoleIncludedTooltip")} +
+ setInvitation({...invitation, guestRoleIncluded: val})}/> +
+ } + {selectedRoles.every(role => role.overrideSettingsAllowed) && setCustomRoleExpiryDate(!customRoleExpiryDate)} @@ -298,7 +321,7 @@ export const InvitationForm = () => { falseLabel={I18n.t("forms.no")} reverse={false} trueLabel={I18n.t("forms.specificDate")} - /> + />} {customRoleExpiryDate && setInvitation({...invitation, roleExpiryDate: e})} diff --git a/client/src/pages/InvitationForm.scss b/client/src/pages/InvitationForm.scss index f084ce1a..5aac5e65 100644 --- a/client/src/pages/InvitationForm.scss +++ b/client/src/pages/InvitationForm.scss @@ -28,8 +28,37 @@ margin-left: 15px; } } + + .switch-container { + display: flex; + margin-top: 20px; + align-items: center; + + + .sds--tooltip-parent { + margin-left: auto; + } + + .inner-switch { + display: flex; + flex-direction: column; + margin-right: 20px; + } + + .switch-label { + font-weight: 600; + } + + } + + .switch-info { + font-size: 14px; + color: var(--sds--color--gray--500); + } + + .sds--radio-options { - width: 320px; + width: 380px; .sds--text-field-container div:last-child { margin-left: auto; diff --git a/server/src/main/java/access/api/RoleController.java b/server/src/main/java/access/api/RoleController.java index ab7e4df5..81d3ff03 100644 --- a/server/src/main/java/access/api/RoleController.java +++ b/server/src/main/java/access/api/RoleController.java @@ -6,13 +6,11 @@ import access.logging.AccessLogger; import access.logging.Event; import access.manage.Manage; -import access.model.Application; -import access.model.Authority; -import access.model.Role; -import access.model.User; +import access.model.*; import access.provision.ProvisioningService; import access.provision.scim.GroupURN; import access.repository.ApplicationRepository; +import access.repository.ApplicationUsageRepository; import access.repository.RoleRepository; import access.security.UserPermissions; import access.validation.URLFormatValidator; @@ -47,17 +45,21 @@ public class RoleController { private final Config config; private final RoleRepository roleRepository; private final ApplicationRepository applicationRepository; + private final ApplicationUsageRepository applicationUsageRepository; private final Manage manage; private final ProvisioningService provisioningService; private final URLFormatValidator urlFormatValidator = new URLFormatValidator(); public RoleController(Config config, RoleRepository roleRepository, - ApplicationRepository applicationRepository, Manage manage, + ApplicationRepository applicationRepository, + ApplicationUsageRepository applicationUsageRepository, + Manage manage, ProvisioningService provisioningService) { this.config = config; this.roleRepository = roleRepository; this.applicationRepository = applicationRepository; + this.applicationUsageRepository = applicationUsageRepository; this.manage = manage; this.provisioningService = provisioningService; } @@ -73,7 +75,7 @@ public ResponseEntity> rolesByApplication(@Parameter(hidden = true) U if (user.isInstitutionAdmin()) { Set manageIdentifiers = user.getApplications().stream().map(m -> (String) m.get("id")).collect(Collectors.toSet()); //This is a shortcoming of the json_array - manageIdentifiers.forEach(manageId -> roles.addAll(roleRepository.findByApplicationsManageId(manageId))); + manageIdentifiers.forEach(manageId -> roles.addAll(roleRepository.findByApplicationUsagesApplicationManageId(manageId))); } Set manageIdentifiers = user.getUserRoles().stream() //If the user has an userRole as Inviter, then we must exclude those @@ -82,7 +84,7 @@ public ResponseEntity> rolesByApplication(@Parameter(hidden = true) U .flatMap(Collection::stream) .map(Application::getManageId) .collect(Collectors.toSet()); - manageIdentifiers.forEach(manageId -> roles.addAll(roleRepository.findByApplicationsManageId(manageId))); + manageIdentifiers.forEach(manageId -> roles.addAll(roleRepository.findByApplicationUsagesApplicationManageId(manageId))); return ResponseEntity.ok(manage.addManageMetaData(roles)); } @@ -147,10 +149,23 @@ private ResponseEntity saveOrUpdate(Role role, User user) { previousManageIdentifiersReference.set(previousRole.applicationIdentifiers()); } //This is the disadvantage of having to save references from Manage - role.setApplications(role.getApplications().stream() - .map(application -> applicationRepository.findByManageIdAndManageType(application.getManageId(), application.getManageType()) - .orElseGet(() -> applicationRepository.save(application))) - .collect(Collectors.toSet())); + Set applications = role.getApplications(); + Set applicationUsages = applications.stream() + .map(applicationFromClient -> { + Optional optionalApplication = applicationRepository + .findByManageIdAndManageType(applicationFromClient.getManageId(), applicationFromClient.getManageType()); + Application applicationFromDB = optionalApplication + .orElseGet(() -> applicationRepository.save(applicationFromClient)); + ApplicationUsage applicationUsage = applicationUsageRepository.findByRoleIdAndApplicationManageIdAndApplicationManageType( + role.getId(), + applicationFromDB.getManageId(), + applicationFromDB.getManageType() + ).orElseGet(() -> new ApplicationUsage(applicationFromDB, applicationFromClient.getLandingPage())); + applicationUsage.setLandingPage(applicationFromClient.getLandingPage()); + return applicationUsage; + }) + .collect(Collectors.toSet()); + role.setApplicationUsages(applicationUsages); Role saved = roleRepository.save(role); if (isNew) { provisioningService.newGroupRequest(saved); @@ -158,7 +173,6 @@ private ResponseEntity saveOrUpdate(Role role, User user) { provisioningService.updateGroupRequest(previousManageIdentifiersReference.get(), saved); } AccessLogger.role(LOG, isNew ? Event.Created : Event.Updated, user, role); - return ResponseEntity.ok(saved); } diff --git a/server/src/main/java/access/model/Application.java b/server/src/main/java/access/model/Application.java index fc25798c..177f55f3 100644 --- a/server/src/main/java/access/model/Application.java +++ b/server/src/main/java/access/model/Application.java @@ -31,12 +31,14 @@ public class Application implements Serializable { @NotNull private EntityType manageType; - @Column(name = "landing_page") + @Transient private String landingPage; - @ManyToMany(mappedBy = "applications") + @OneToMany(mappedBy = "application", + fetch = FetchType.LAZY, + orphanRemoval = true) @JsonIgnore - private Set roles = new HashSet<>(); + private Set applicationUsages = new HashSet<>(); public Application(String manageId, EntityType manageType) { this.manageId = manageId; diff --git a/server/src/main/java/access/model/ApplicationUsage.java b/server/src/main/java/access/model/ApplicationUsage.java new file mode 100644 index 00000000..ba6cdb39 --- /dev/null +++ b/server/src/main/java/access/model/ApplicationUsage.java @@ -0,0 +1,37 @@ +package access.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@Entity(name = "application_usages") +@NoArgsConstructor +@Getter +@Setter +public class ApplicationUsage implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "landing_page") + private String landingPage; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "role_id") + @JsonIgnore + private Role role; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "application_id") + private Application application; + + public ApplicationUsage(Application application, String landingPage) { + this.landingPage = landingPage; + this.application = application; + } +} diff --git a/server/src/main/java/access/model/Role.java b/server/src/main/java/access/model/Role.java index 95d79457..c79f9638 100644 --- a/server/src/main/java/access/model/Role.java +++ b/server/src/main/java/access/model/Role.java @@ -12,6 +12,7 @@ import java.io.Serializable; import java.util.*; +import java.util.stream.Collectors; @Entity(name = "roles") @NoArgsConstructor @@ -65,13 +66,11 @@ public class Role implements Serializable, Provisionable { @Formula(value = "(SELECT COUNT(*) FROM user_roles ur WHERE ur.role_id=id)") private Long userRoleCount; - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable( - name = "roles_applications", - joinColumns = @JoinColumn(name = "role_id"), - inverseJoinColumns = @JoinColumn(name = "application_id")) - //TODO https://www.baeldung.com/hibernate-persisting-maps, store landingpage per applications - private Set applications = new HashSet<>(); + @OneToMany(mappedBy = "role", + fetch = FetchType.EAGER, + orphanRemoval = true, + cascade = CascadeType.ALL) + private Set applicationUsages = new HashSet<>(); @OneToMany(mappedBy = "role", orphanRemoval = true, @@ -86,14 +85,17 @@ public class Role implements Serializable, Provisionable { @Transient private List> applicationMaps; + @Transient + private Set applications; + public Role(String name, String description, String landingPage, - Set applications, + Set applicationUsages, Integer defaultExpiryDays, boolean enforceEmailEquality, boolean eduIDOnly) { - this(name, GroupURN.sanitizeRoleShortName(name), description, landingPage, applications, + this(name, GroupURN.sanitizeRoleShortName(name), description, landingPage, applicationUsages, defaultExpiryDays, enforceEmailEquality, eduIDOnly, Collections.emptyList()); } @@ -101,7 +103,7 @@ public Role(@NotNull String name, @NotNull String shortName, String description, String landingPage, - Set applications, + Set applicationUsages, Integer defaultExpiryDays, boolean enforceEmailEquality, boolean eduIDOnly, @@ -113,7 +115,8 @@ public Role(@NotNull String name, this.defaultExpiryDays = defaultExpiryDays; this.enforceEmailEquality = enforceEmailEquality; this.eduIDOnly = eduIDOnly; - this.applications = applications; + this.applicationUsages = applicationUsages; + this.applicationUsages.forEach(applicationUsage -> applicationUsage.setRole(this)); this.applicationMaps = applicationMaps; this.identifier = UUID.randomUUID().toString(); } @@ -121,7 +124,18 @@ public Role(@NotNull String name, @Transient @JsonIgnore public List applicationIdentifiers() { - return applications.stream().map(Application::getManageId).toList(); + return applicationUsages.stream() + .map(applicationUsage -> applicationUsage.getApplication().getManageId()).toList(); } + @Transient + public Set getApplications() { + return applicationUsages.stream() + .map(ApplicationUsage::getApplication).collect(Collectors.toSet()); + } + + public void setApplicationUsages(Set applicationUsages) { + this.applicationUsages = applicationUsages; + this.applicationUsages.forEach(applicationUsage -> applicationUsage.setRole(this)); + } } diff --git a/server/src/main/java/access/model/User.java b/server/src/main/java/access/model/User.java index 61e42031..f7526972 100644 --- a/server/src/main/java/access/model/User.java +++ b/server/src/main/java/access/model/User.java @@ -175,9 +175,9 @@ public void removeUserRole(UserRole role) { public Set manageIdentifierSet() { return userRoles.stream() .filter(userRole -> userRole.getAuthority().equals(Authority.GUEST)) - .map(userRole -> userRole.getRole().getApplications()) + .map(userRole -> userRole.getRole().getApplicationUsages()) .flatMap(Collection::stream) - .map(application -> new ManageIdentifier(application.getManageId(), application.getManageType())) + .map(applicationUsage -> new ManageIdentifier(applicationUsage.getApplication().getManageId(),applicationUsage.getApplication().getManageType())) .collect(Collectors.toSet()); } diff --git a/server/src/main/java/access/repository/ApplicationUsageRepository.java b/server/src/main/java/access/repository/ApplicationUsageRepository.java new file mode 100644 index 00000000..8c019ff2 --- /dev/null +++ b/server/src/main/java/access/repository/ApplicationUsageRepository.java @@ -0,0 +1,16 @@ +package access.repository; + +import access.manage.EntityType; +import access.model.ApplicationUsage; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface ApplicationUsageRepository extends JpaRepository { + + Optional findByRoleIdAndApplicationManageIdAndApplicationManageType( + Long roleId, String manageId, EntityType manageType); + +} diff --git a/server/src/main/java/access/repository/RoleRepository.java b/server/src/main/java/access/repository/RoleRepository.java index 47329d89..9c2580c3 100644 --- a/server/src/main/java/access/repository/RoleRepository.java +++ b/server/src/main/java/access/repository/RoleRepository.java @@ -15,7 +15,7 @@ public interface RoleRepository extends JpaRepository { nativeQuery = true) List search(String keyWord, int limit); - List findByApplicationsManageId(String manageId); + List findByApplicationUsagesApplicationManageId(String manageId); List findByName(String name); diff --git a/server/src/main/java/access/teams/TeamsController.java b/server/src/main/java/access/teams/TeamsController.java index b154c29c..7bce99c0 100644 --- a/server/src/main/java/access/teams/TeamsController.java +++ b/server/src/main/java/access/teams/TeamsController.java @@ -2,7 +2,6 @@ import access.api.Results; import access.exception.InvalidInputException; -import access.exception.NotFoundException; import access.manage.Manage; import access.model.Role; import access.model.*; @@ -19,7 +18,10 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -79,10 +81,15 @@ public ResponseEntity> migrateTeam(@RequestBody Team team) throw new InvalidInputException(); } //This is the disadvantage of having to save references from Manage - role.setApplications(team.getApplications().stream() - .map(application -> applicationRepository.findByManageIdAndManageType(application.getManageId(), application.getManageType()) - .orElseGet(() -> applicationRepository.save(application))) - .collect(Collectors.toSet())); + Set applicationUsages = team.getApplications().stream() + .map(applicationFromTeams -> { + Application applicationFromDB = applicationRepository + .findByManageIdAndManageType(applicationFromTeams.getManageId(), applicationFromTeams.getManageType()) + .orElseGet(() -> applicationRepository.save(applicationFromTeams)); + return new ApplicationUsage(applicationFromDB, applicationFromTeams.getLandingPage()); + }) + .collect(Collectors.toSet()); + role.setApplicationUsages(applicationUsages); Role savedRole = roleRepository.save(role); provisioningService.newGroupRequest(savedRole); diff --git a/server/src/main/java/db/mysql/migration/V22_0__migrate_applications_per_role_landings_page.java b/server/src/main/java/db/mysql/migration/V22_0__migrate_applications_per_role_landings_page.java new file mode 100644 index 00000000..9a3a1a98 --- /dev/null +++ b/server/src/main/java/db/mysql/migration/V22_0__migrate_applications_per_role_landings_page.java @@ -0,0 +1,31 @@ +package db.mysql.migration; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SingleConnectionDataSource; + +import java.util.concurrent.atomic.AtomicReference; + + +public class V22_0__migrate_applications_per_role_landings_page extends BaseJavaMigration { + + public void migrate(Context context) { + JdbcTemplate jdbcTemplate = + new JdbcTemplate(new SingleConnectionDataSource(context.getConnection(), true)); + jdbcTemplate.query("SELECT role_id, application_id FROM roles_applications", rs -> { + long roleId = rs.getLong("role_id"); + long applicationId = rs.getLong("application_id"); + AtomicReference landingPageReference = new AtomicReference<>(); + //As initial value we use the landing page from the role, not the application + jdbcTemplate.query( + "select landing_page from roles where id = ?", + resultSet -> { + landingPageReference.set(resultSet.getString("landing_page")); + }, + roleId); + jdbcTemplate.update("INSERT INTO `application_usages` (`landing_page` ,`role_id`,`application_id`) VALUES (?, ?, ?)", + landingPageReference.get(), roleId, applicationId); + }); + } +} diff --git a/server/src/main/resources/db/mysql/migration/V21_0__landings_page_per_application_role.sql b/server/src/main/resources/db/mysql/migration/V21_0__landings_page_per_application_role.sql new file mode 100644 index 00000000..05a8ed35 --- /dev/null +++ b/server/src/main/resources/db/mysql/migration/V21_0__landings_page_per_application_role.sql @@ -0,0 +1,13 @@ +CREATE TABLE `application_usages` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `landing_page` varchar(255) DEFAULT NULL, + `role_id` bigint NOT NULL, + `application_id` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `application_usages_unique` (`role_id`, `application_id`), + CONSTRAINT `fk_application_usages_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_application_usages_application` FOREIGN KEY (`application_id`) REFERENCES `applications` (`id`) ON DELETE CASCADE +) ENGINE = InnoDB + AUTO_INCREMENT = 1 + DEFAULT CHARSET = utf8mb4; diff --git a/server/src/main/resources/db/mysql/migration/V23_0__remove_application_roles.sql b/server/src/main/resources/db/mysql/migration/V23_0__remove_application_roles.sql new file mode 100644 index 00000000..0daf0d22 --- /dev/null +++ b/server/src/main/resources/db/mysql/migration/V23_0__remove_application_roles.sql @@ -0,0 +1,2 @@ +DROP TABLE `roles_applications`; +ALTER TABLE `applications` DROP `landing_page`; \ No newline at end of file diff --git a/server/src/test/java/access/AbstractTest.java b/server/src/test/java/access/AbstractTest.java index a364d6ec..16283781 100644 --- a/server/src/test/java/access/AbstractTest.java +++ b/server/src/test/java/access/AbstractTest.java @@ -121,6 +121,9 @@ public abstract class AbstractTest { @Autowired protected APITokenRepository apiTokenRepository; + @Autowired + protected ApplicationUsageRepository applicationUsageRepository; + protected LocalManage localManage; @RegisterExtension @@ -497,10 +500,10 @@ protected UnaryOperator> institutionalAdminEntitlementOperat }; } - public Set application(String manageId, EntityType entityType) { + public Set application(String manageId, EntityType entityType) { Application application = applicationRepository.findByManageIdAndManageType(manageId, entityType). orElseGet(() -> applicationRepository.save(new Application(manageId, entityType))); - return Set.of(application); + return Set.of(new ApplicationUsage(application, "http://landingpage.com")); } public void doSeed() { @@ -534,12 +537,19 @@ public void doSeed() { Role network = new Role("Network", "Network desc", "https://landingpage.com", application("2", EntityType.SAML20_SP), 365, false, false); - Set applications = Set.of(new Application("3", EntityType.SAML20_SP), new Application("6", EntityType.OIDC10_RP)); + + Set applications = Set.of( + new Application("3", EntityType.SAML20_SP), + new Application("6", EntityType.OIDC10_RP)) + .stream() + .map(app -> applicationRepository.findByManageIdAndManageType(app.getManageId(), app.getManageType()). + orElseGet(() -> applicationRepository.save(new Application(app.getManageId(), app.getManageType())))) + .collect(Collectors.toSet()); + Set applicationUsages = applications.stream().map(application -> new ApplicationUsage(application, "https://landingpage.com")).collect(Collectors.toSet()); Role storage = new Role("Storage", "Storage desc", "https://landingpage.com", - applications.stream().map(application -> applicationRepository.save(application)).collect(Collectors.toSet()), - 365, false, false); + applicationUsages, 365, false, false); Role research = new Role("Research", "Research desc", "https://landingpage.com", application("4", EntityType.SAML20_SP), 365, false, false); diff --git a/server/src/test/java/access/WithApplicationTest.java b/server/src/test/java/access/WithApplicationTest.java index 8cde5314..89aaad6a 100644 --- a/server/src/test/java/access/WithApplicationTest.java +++ b/server/src/test/java/access/WithApplicationTest.java @@ -2,13 +2,14 @@ import access.manage.EntityType; import access.model.Application; +import access.model.ApplicationUsage; import java.util.Set; public class WithApplicationTest { - public Set application(String manageId, EntityType entityType) { - return Set.of(new Application(manageId, entityType)); + public Set application(String manageId, EntityType entityType) { + return Set.of(new ApplicationUsage(new Application(manageId, entityType), "https://landingpage.com")); } } diff --git a/server/src/test/java/access/api/InvitationControllerTest.java b/server/src/test/java/access/api/InvitationControllerTest.java index f60ccc66..c132edc5 100644 --- a/server/src/test/java/access/api/InvitationControllerTest.java +++ b/server/src/test/java/access/api/InvitationControllerTest.java @@ -61,7 +61,7 @@ void newInvitation() throws Exception { stubForManageProviderById(EntityType.SAML20_SP, "1"); - List roleIdentifiers = roleRepository.findByApplicationsManageId("1").stream() + List roleIdentifiers = roleRepository.findByApplicationUsagesApplicationManageId("1").stream() .map(Role::getId) .toList(); InvitationRequest invitationRequest = new InvitationRequest( diff --git a/server/src/test/java/access/api/RoleControllerTest.java b/server/src/test/java/access/api/RoleControllerTest.java index 1453a563..445210c0 100644 --- a/server/src/test/java/access/api/RoleControllerTest.java +++ b/server/src/test/java/access/api/RoleControllerTest.java @@ -4,6 +4,7 @@ import access.AccessCookieFilter; import access.manage.EntityType; import access.model.Application; +import access.model.ApplicationUsage; import access.model.RemoteProvisionedGroup; import access.model.Role; import io.restassured.common.mapper.TypeRef; @@ -111,9 +112,10 @@ void updateApplications() throws Exception { super.stubForDeleteScimRole(); Role roleDB = roleRepository.search("Network", 1).get(0); - roleDB.setApplications(Set.of( - new Application("1",EntityType.SAML20_SP), - new Application("4",EntityType.SAML20_SP))); + roleDB.setApplicationUsages(Set.of( + new ApplicationUsage(new Application("1", EntityType.SAML20_SP), "https://landingpage.com"), + new ApplicationUsage(new Application("4", EntityType.SAML20_SP), "https://landingpage.com")) + ); Role updated = given() .when() diff --git a/server/src/test/java/access/model/ApplicationTest.java b/server/src/test/java/access/model/ApplicationTest.java index 89af3922..0ab391b4 100644 --- a/server/src/test/java/access/model/ApplicationTest.java +++ b/server/src/test/java/access/model/ApplicationTest.java @@ -21,6 +21,6 @@ void testEquals() { Set applications = new HashSet<>(List.of(application, otherApplication)); assertEquals(1, applications.size()); - assertEquals(0, application.getRoles().size()); + assertEquals(0, application.getApplicationUsages().size()); } } \ No newline at end of file diff --git a/server/src/test/java/access/repository/ApplicationUsageRepositoryTest.java b/server/src/test/java/access/repository/ApplicationUsageRepositoryTest.java new file mode 100644 index 00000000..45bbf2da --- /dev/null +++ b/server/src/test/java/access/repository/ApplicationUsageRepositoryTest.java @@ -0,0 +1,42 @@ +package access.repository; + +import access.AbstractTest; +import access.model.Application; +import access.model.ApplicationUsage; +import access.model.Role; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class ApplicationUsageRepositoryTest extends AbstractTest { + + @Test + void findByRoleIdAndApplicationManageIdAndApplicationManageType() { + Role role = roleRepository.findByName("wiki").get(0); + ApplicationUsage applicationUsage = role.getApplicationUsages().iterator().next(); + Application application = applicationUsage.getApplication(); + Optional optionalApplicationUsage = applicationUsageRepository.findByRoleIdAndApplicationManageIdAndApplicationManageType( + role.getId(), + application.getManageId(), + application.getManageType() + ); + assertTrue(optionalApplicationUsage.isPresent()); + + optionalApplicationUsage = applicationUsageRepository.findByRoleIdAndApplicationManageIdAndApplicationManageType( + role.getId(), + UUID.randomUUID().toString(), + application.getManageType() + ); + assertTrue(optionalApplicationUsage.isEmpty()); + + optionalApplicationUsage = applicationUsageRepository.findByRoleIdAndApplicationManageIdAndApplicationManageType( + null, + application.getManageId(), + application.getManageType() + ); + assertTrue(optionalApplicationUsage.isEmpty()); + } +} \ No newline at end of file diff --git a/server/src/test/java/access/security/UserPermissionsTest.java b/server/src/test/java/access/security/UserPermissionsTest.java index 1f6b8c20..3bf87062 100644 --- a/server/src/test/java/access/security/UserPermissionsTest.java +++ b/server/src/test/java/access/security/UserPermissionsTest.java @@ -34,7 +34,7 @@ void assertValidInvitationSuperUser() { user.setApplications(List.of(Map.of("id", "1"))); Role role = new Role(); - role.getApplications().add(new Application("1", EntityType.SAML20_SP)); + role.getApplicationUsages().add(new ApplicationUsage(new Application("1", EntityType.SAML20_SP),"https://landing.com") ); UserPermissions.assertValidInvitation(user, Authority.MANAGER, List.of(role)); } @@ -143,9 +143,9 @@ void assertRoleAccessInstitutionAdmin() { user.setInstitutionAdmin(true); user.setApplications(List.of(Map.of("id", "1"))); - Role role = new Role(); - role.getApplications().add(new Application("1", EntityType.SAML20_SP)); + Role role = new Role(); + role.getApplicationUsages().add(new ApplicationUsage(new Application("1", EntityType.SAML20_SP),"https://landing.com") ); UserPermissions.assertRoleAccess(user, role, Authority.INSTITUTION_ADMIN); } @Test