diff --git a/docker/example.env b/docker/example.env index c64ed4cc0..009a41343 100644 --- a/docker/example.env +++ b/docker/example.env @@ -5,7 +5,7 @@ # Comma seperated profiles, defaults to prod if left empty QUARKUS_PROFILE= # Sets the HTML title of the frontend -DAMAP_TITLE="DAMAP Tool" +DAMAP_TENANT_AWARE_TITLE="DAMAP Tool" # Sets the frontends mode, should always be PROD for production environment # Allowed values: # - DEV @@ -15,20 +15,23 @@ DAMAP_ENV=DEV QUARKUS_HTTP_CORS_ORIGINS=http://localhost:8085 # Sets the project service used, should be set to elsevier-pure only when pure integration is used # Allowed values: -# - default +# - default (mock service or custom, if provided) # - elsevier-pure -DAMAP_PROJECTS_SERVICE=default -# Text shown in the frontend to allow users to switch between person services -DAMAP_PERSON_SERVICES_DISPLAY_TEXT=University -# Sets which person service is used -# Allowed values: -# - UNIVERSITY -# - PURE -DAMAP_PERSON_SERVICES_QUERY_VALUE=UNIVERSITY -# Which java class is to be used as person service -# Set to org.damap.base.integration.pure.PurePersonService for PURE -# Or to your custom class, if you do use a custom integration -DAMAP_PERSON_SERVICES_CLASS_NAME=org.damap.base.integration.mock.MockUniversityPersonServiceImpl +# - disabled (no connected project service) +DAMAP_TENANT_AWARE_PROJECT_SERVICE=disabled +# DISPLAY_TEXT: Is the text shown in the frontend to allow users to switch between person services +# QUERY_VALUE: Should uniquely identify the service +# CLASS_NAME: Which java class should be used for the service implementation, set to +# "org.damap.base.integration.orcid.ORCIDPersonServiceImpl" for ORCID, set to +# "org.damap.base.integration.pure.PurePersonService" for Pure or to your custom class, if you use a custom integration +# To add further mappings, copy the three environment variable group below and increment the indexes +# Info: the two underscores `0__` are on purpose - these are necessary due to how SmallRye processes env variables +DAMAP_TENANT_AWARE_PERSON_SERVICES_0__DISPLAY_TEXT: University +DAMAP_TENANT_AWARE_PERSON_SERVICES_0__QUERY_VALUE: UNIVERSITY +DAMAP_TENANT_AWARE_PERSON_SERVICES_0__CLASS_NAME: org.damap.base.integration.mock.MockUniversityPersonServiceImpl +DAMAP_TENANT_AWARE_PERSON_SERVICES_1__DISPLAY_TEXT: ORCID +DAMAP_TENANT_AWARE_PERSON_SERVICES_1__QUERY_VALUE: ORCID +DAMAP_TENANT_AWARE_PERSON_SERVICES_1__CLASS_NAME: org.damap.base.integration.orcid.ORCIDPersonServiceImpl ## ===================== ## Authentication @@ -93,7 +96,7 @@ REST_GOTENBERG_MP_REST_URL=http://gotenberg:3000 ## ===================== # Enables input field for short ethical report description -DAMAP_FIELDS_ETHICAL_REPORT_ENABLED=true +DAMAP_TENANT_AWARE_FIELDS_ETHICAL_REPORT_ENABLED=true ## ===================== ## Elsevier PURE @@ -103,21 +106,24 @@ DAMAP_FIELDS_ETHICAL_REPORT_ENABLED=true # /dk/atira/pure, but are otherwise specific to the institutional setup in Pure. # The project description field DAMAP should use: -DAMAP_ELSEVIER_PURE_DESCRIPTION_CLASSIFICATION=/dk/atira/pure/projectdescription -# How Pure project contributor classifications map to DAMAP: -DAMAP_ELSEVIER_PURE_CONTRIBUTOR_ROLE_CLASSIFICATIONS='{"/dk/atira/pure/member":"PROJECT_MEMBER"}' +DAMAP_TENANT_AWARE_ELSEVIER_PURE_DESCRIPTION_CLASSIFICATION: /dk/atira/pure/projectdescription +# How Pure project contributor classifications map to DAMAP : +# To add further mappings, copy the two environment variables below and increment the indexes from 0 to 1 +# Info: the two underscores `0__` are on purpose - these are necessary due to how SmallRye processes env variables +DAMAP_TENANT_AWARE_ELSEVIER_PURE_CONTRIBUTOR_ROLE_CLASSIFICATIONS_0__PURE_ROLE_URI: /dk/atira/pure/member +DAMAP_TENANT_AWARE_ELSEVIER_PURE_CONTRIBUTOR_ROLE_CLASSIFICATIONS_0__CONTRIBUTOR_ROLE: PROJECT_MEMBER # Which Pure classification should be read as the project lead: -DAMAP_ELSEVIER_PURE_PROJECT_LEAD_ROLE_CLASSIFICATION=/dk/atira/pure/projectlead +DAMAP_TENANT_AWARE_ELSEVIER_PURE_PROJECT_LEAD_ROLE_CLASSIFICATION: /dk/atira/pure/projectlead # Configures if data should be fetched over http or from local files - should always be http for PROD: # Allowed values: # - http # - file -DAMAP_ELSEVIER_PURE_BACKEND=http +DAMAP_TENANT_AWARE_ELSEVIER_PURE_BACKEND: http # URL to fetch data from, only relevant when ELSEVIER_PURE_BACKEND is set to http -DAMAP_ELSEVIER_PURE_ENDPOINT_URL="https://your-pure-instance.elsevierpure.com/ws/api" +DAMAP_TENANT_AWARE_ELSEVIER_PURE_ENDPOINT_URL: "https://your-pure-instance.elsevierpure.com/ws/api" # API key used to authenticate over the PURE API, only relevant when ELSEVIER_PURE_BACKEND is set to http -DAMAP_ELSEVIER_PURE_API_KEY="your API key here" +DAMAP_TENANT_AWARE_ELSEVIER_PURE_API_KEY: "your API key here" # location of projects file, only relevant when ELSEVIER_PURE_BACKEND is set to file -DAMAP_ELSEVIER_PURE_PROJECTS_FILE="file:///path/to/projects.json" +DAMAP_TENANT_AWARE_ELSEVIER_PURE_PROJECTS_FILE: "file:///path/to/projects.json" # location of persons file, only relevant when ELSEVIER_PURE_BACKEND is set to file -DAMAP_ELSEVIER_PURE_PERSONS_FILE="file:///path/to/persons.json" +DAMAP_TENANT_AWARE_ELSEVIER_PURE_PERSONS_FILE: "file:///path/to/persons.json" diff --git a/docker/tenants.yaml b/docker/tenants.yaml index afdf8abb2..d2a764cf1 100644 --- a/docker/tenants.yaml +++ b/docker/tenants.yaml @@ -1,4 +1,53 @@ -tenants: ['tenant_1', 'tenant_2'] +damap: + tenants: + tenant-list: ['tenant_1', 'tenant_2'] + # these configs exactly mirror the damap.tenant-aware configs in application.yaml + tenant-configs: + tenant_1: + title: Tenant 1 DAMAP Tool + fields: + ethical-report-enabled: false + project-service: default + elsevier-pure-description-classification: /dk/atira/pure/projectdescription + elsevier-pure-contributor-role-classifications: + - pure-role-uri: /dk/atira/pure/member + contributor-role: PROJECT_MEMBER + elsevier-pure-project-lead-role-classification: /dk/atira/pure/projectlead + elsevier-pure-backend: http + elsevier-pure-endpoint-url: "https://your-tenant1-pure-instance.elsevierpure.com/ws/api/" + elsevier-pure-api-key: "your API key here" + elsevier-pure-projects-file: "file:///path/to/projects.json" + elsevier-pure-persons-file: "file:///path/to/persons.json" + person-services: + - display-text: 'ORCID' + query-value: 'ORCID' + class-name: 'org.damap.base.integration.orcid.ORCIDPersonServiceImpl' + - display-text: 'University' + query-value: 'UNIVERSITY' + class-name: 'org.damap.base.integration.mock.MockUniversityPersonServiceImpl' + tenant_2: + title: Tenant 2 DAMAP Tool + fields: + ethical-report-enabled: false + project-service: disabled + elsevier-pure-description-classification: /dk/atira/pure/projectdescription + elsevier-pure-contributor-role-classifications: + - pure-role-uri: /dk/atira/pure/member + contributor-role: PROJECT_MEMBER + elsevier-pure-project-lead-role-classification: /dk/atira/pure/projectlead + elsevier-pure-backend: http + elsevier-pure-endpoint-url: "https://your-tenant2-pure-instance.elsevierpure.com/ws/api/GRAZ" + elsevier-pure-api-key: "your API key here" + elsevier-pure-projects-file: "file:///path/to/projects.json" + elsevier-pure-persons-file: "file:///path/to/persons.json" + person-services: + - display-text: 'ORCID' + query-value: 'ORCID' + class-name: 'org.damap.base.integration.orcid.ORCIDPersonServiceImpl' + - display-text: 'Pure' + query-value: 'PURE' + class-name: 'org.damap.base.integration.pure.PurePersonService' + "%multitenant": quarkus: datasource: diff --git a/helm/templates/00_multitenancy.yaml b/helm/templates/00_multitenancy.yaml index 755d63132..6cbb6bfb1 100644 --- a/helm/templates/00_multitenancy.yaml +++ b/helm/templates/00_multitenancy.yaml @@ -9,7 +9,9 @@ metadata: app: damap stringData: tenants.yaml: |- - tenants: {{ .Values.damap.multitenancy.tenants | toJson }} + damap: + tenants: + tenant-list: {{ .Values.damap.multitenancy.tenants | toJson }} "%multitenant": quarkus: datasource: diff --git a/helm/templates/03_damap.yaml b/helm/templates/03_damap.yaml index b3279209d..3a7b70d0f 100644 --- a/helm/templates/03_damap.yaml +++ b/helm/templates/03_damap.yaml @@ -219,30 +219,30 @@ spec: {{- /* Additional integrations configuration */}} - - name: DAMAP_PERSON_SERVICES + - name: DAMAP_TENANT_AWARE_PERSON_SERVICES value: {{ .Values.damap.personServices | toJson | quote }} - - name: DAMAP_PROJECTS_SERVICE - value: {{ .Values.damap.projectsService }} + - name: DAMAP_TENANT_AWARE_PROJECT_SERVICE + value: {{ .Values.damap.projectService }} {{- /* Elsevier Pure integration */}} {{- if .Values.pure.enabled }} - - name: DAMAP_ELSEVIER_PURE_BACKEND + - name: DAMAP_TENANT_AWARE_ELSEVIER_PURE_BACKEND value: "{{ .Values.pure.backend }}" - - name: DAMAP_ELSEVIER_PURE_PROJECTS_FILE + - name: DAMAP_TENANT_AWARE_ELSEVIER_PURE_PROJECTS_FILE value: file:///data/projects.json - - name: DAMAP_ELSEVIER_PURE_PERSONS_FILE + - name: DAMAP_TENANT_AWARE_ELSEVIER_PURE_PERSONS_FILE value: file:///data/persons.json - - name: DAMAP_ELSEVIER_PURE_ENDPOINT_URL + - name: DAMAP_TENANT_AWARE_ELSEVIER_PURE_ENDPOINT_URL value: "{{ .Values.pure.endpoint }}" - - name: DAMAP_ELSEVIER_PURE_API_KEY + - name: DAMAP_TENANT_AWARE_ELSEVIER_PURE_API_KEY value: "{{ .Values.pure.apiKey }}" - - name: DAMAP_ELSEVIER_PURE_DESCRIPTION_CLASSIFICATION + - name: DAMAP_TENANT_AWARE_ELSEVIER_PURE_DESCRIPTION_CLASSIFICATION value: {{ .Values.pure.descriptionClassification | quote }} - - name: DAMAP_ELSEVIER_PURE_PROJECT_LEAD_ROLE_CLASSIFICATION + - name: DAMAP_TENANT_AWARE_ELSEVIER_PURE_PROJECT_LEAD_ROLE_CLASSIFICATION value: {{ .Values.pure.projectLeadClassification | quote }} - - name: DAMAP_ELSEVIER_PURE_CONTRIBUTOR_ROLE_CLASSIFICATIONS + - name: DAMAP_TENANT_AWARE_ELSEVIER_PURE_CONTRIBUTOR_ROLE_CLASSIFICATIONS value: {{ .Values.pure.roleClassifications | toJson | quote }} {{- end }} volumeMounts: diff --git a/helm/values.schema.json b/helm/values.schema.json index 5e60a5dbc..97a0440e8 100644 --- a/helm/values.schema.json +++ b/helm/values.schema.json @@ -613,37 +613,39 @@ "roleClassifications": { "title": "Role classifications", "description": "Mappings from Pure classifications to DAMAP roles. The keys should be the Pure classifications used for roles in your Pure instance, and the values should be the corresponding DAMAP role values. This is used to determine which DAMAP role to assign to a person based on their classification in Pure. The DAMAP role values should be one of the following: DATA_COLLECTOR, DATA_CURATOR, DATA_MANAGER, DISTRIBUTOR, EDITOR, HOSTING_INSTITUTION, PRODUCER, PROJECT_LEADER, PROJECT_MANAGER, PROJECT_MEMBER, REGISTRATION_AGENCY, REGISTRATION_AUTHORITY, RELATED_PERSON, RESEARCHER, RESEARCH_GROUP, RIGHTS_HOLDER, SPONSOR, SUPERVISOR, WORK_PACKAGE_LEADER, PRINCIPAL_INVESTIGATOR, PROJECT_COORDINATOR, OTHER.", - "type": "object", - "patternProperties": { - "^/dk/atira/pure/": { - "type": "string", - "enum": [ - "DATA_COLLECTOR", - "DATA_CURATOR", - "DATA_MANAGER", - "DISTRIBUTOR", - "EDITOR", - "HOSTING_INSTITUTION", - "PRODUCER", - "PROJECT_LEADER", - "PROJECT_MANAGER", - "PROJECT_MEMBER", - "REGISTRATION_AGENCY", - "REGISTRATION_AUTHORITY", - "RELATED_PERSON", - "RESEARCHER", - "RESEARCH_GROUP", - "RIGHTS_HOLDER", - "SPONSOR", - "SUPERVISOR", - "WORK_PACKAGE_LEADER", - "PRINCIPAL_INVESTIGATOR", - "PROJECT_COORDINATOR", - "OTHER" - ] + "type": "array", + "items": { + "type": "object", + "required": ["pure-role-uri", "contributor-role"], + "properties": { + "pure-role-uri": { "type": "string" }, + "contributor-role": { "type": "string", + "enum": [ + "DATA_COLLECTOR", + "DATA_CURATOR", + "DATA_MANAGER", + "DISTRIBUTOR", + "EDITOR", + "HOSTING_INSTITUTION", + "PRODUCER", + "PROJECT_LEADER", + "PROJECT_MANAGER", + "PROJECT_MEMBER", + "REGISTRATION_AGENCY", + "REGISTRATION_AUTHORITY", + "RELATED_PERSON", + "RESEARCHER", + "RESEARCH_GROUP", + "RIGHTS_HOLDER", + "SPONSOR", + "SUPERVISOR", + "WORK_PACKAGE_LEADER", + "PRINCIPAL_INVESTIGATOR", + "PROJECT_COORDINATOR", + "OTHER" + ]} } } - } } } diff --git a/helm/values.yaml b/helm/values.yaml index 4f0aa2ac5..6344326e0 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -45,7 +45,7 @@ damap: - display-text: "ORCID" query-value: "ORCID" class-name: "org.damap.base.integration.orcid.ORCIDPersonServiceImpl" - projectsService: elsevier-pure + projectService: elsevier-pure protocol: http storageClassName: "" tlsClusterIssuer: "" @@ -335,4 +335,5 @@ pure: # RESEARCH_GROUP, RIGHTS_HOLDER, SPONSOR, SUPERVISOR, WORK_PACKAGE_LEADER, PRINCIPAL_INVESTIGATOR, # PROJECT_COORDINATOR, OTHER roleClassifications: - /dk/atira/pure/...: PROJECT_MEMBER + - pure-role-uri: /dk/atira/pure/member + contributor-role: PROJECT_MEMBER diff --git a/src/main/java/org/damap/base/hibernate/CustomTenantResolver.java b/src/main/java/org/damap/base/hibernate/CustomTenantResolver.java index 23906b136..4d5b71bc8 100644 --- a/src/main/java/org/damap/base/hibernate/CustomTenantResolver.java +++ b/src/main/java/org/damap/base/hibernate/CustomTenantResolver.java @@ -18,7 +18,7 @@ public class CustomTenantResolver implements TenantResolver { private static final Logger LOG = Logger.getLogger(CustomTenantResolver.class); - @ConfigProperty(name = "tenants", defaultValue = "default") + @ConfigProperty(name = "damap.tenants.tenant-list", defaultValue = "default") List tenantIds; @Inject SecurityService securityService; @@ -33,7 +33,7 @@ public String getDefaultTenantId() { public String resolveTenantId() { String tenantId = securityService.getAffiliation(); if (!tenantIds.contains(tenantId)) { - throw new ForbiddenException("TenantId mismatch"); + throw new ForbiddenException("TenantId mismatch for tenantId: " + tenantId); } LOG.debug("TenantId = " + tenantId); return tenantId; diff --git a/src/main/java/org/damap/base/integration/disabled/DisabledProjectServiceImpl.java b/src/main/java/org/damap/base/integration/disabled/DisabledProjectServiceImpl.java new file mode 100644 index 000000000..6e1369d1e --- /dev/null +++ b/src/main/java/org/damap/base/integration/disabled/DisabledProjectServiceImpl.java @@ -0,0 +1,68 @@ +package org.damap.base.integration.disabled; + +import jakarta.enterprise.context.ApplicationScoped; +import java.util.ArrayList; +import java.util.List; +import org.damap.base.integration.ProjectServiceProvider; +import org.damap.base.rest.base.ResultList; +import org.damap.base.rest.base.Search; +import org.damap.base.rest.dmp.domain.ContributorDO; +import org.damap.base.rest.dmp.domain.ProjectDO; +import org.damap.base.rest.dmp.domain.ProjectSupplementDO; + +/** + * This class is meant to express the state of project services being explicitly disabled. + * + *

Note: DAMAP was coded in a way, where it was assumed that a project service would always + * exist. This husk is meant to represent the state of no service being registered. This should be + * refactored in the future, if time permits. + */ +@ApplicationScoped +public class DisabledProjectServiceImpl implements ProjectServiceProvider { + @Override + public String getConfigID() { + return "disabled"; + } + + @Override + public List getProjectStaff(String projectId) { + return new ArrayList<>(); + } + + @Override + public ProjectSupplementDO getProjectSupplement(String projectId) { + return null; + } + + @Override + public ContributorDO getProjectLeader(String projectId) { + return null; + } + + @Override + public ResultList getRecommended(Search search) { + return getMockResultList(); + } + + @Override + public ProjectDO read(String id) { + return null; + } + + @Override + public ResultList search(Search query) { + return getMockResultList(); + } + + /** + * Helper method. Returns an empty ResultList that won't crash methods which use the result, since + * they expect empty lists and not null. + * + * @return Empty result list + */ + private ResultList getMockResultList() { + ResultList res = new ResultList<>(); + res.setItems(new ArrayList<>()); + return res; + } +} diff --git a/src/main/java/org/damap/base/integration/pure/FileBasedPureAPI.java b/src/main/java/org/damap/base/integration/pure/FileBasedPureAPI.java index 9e9fa7155..d1aa130df 100644 --- a/src/main/java/org/damap/base/integration/pure/FileBasedPureAPI.java +++ b/src/main/java/org/damap/base/integration/pure/FileBasedPureAPI.java @@ -1,14 +1,14 @@ package org.damap.base.integration.pure; import com.fasterxml.jackson.databind.ObjectMapper; -import io.quarkus.arc.lookup.LookupIfProperty; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Typed; +import jakarta.inject.Inject; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import lombok.extern.jbosslog.JBossLog; -import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.damap.base.rest.config.domain.TenantConfigResolver; +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; /** * This implementation for the {@link PureAPI} reads the data from files instead of a remote @@ -17,18 +17,16 @@ @JBossLog @ApplicationScoped @Typed(FileBasedPureAPI.class) -@LookupIfProperty(name = "damap.elsevier-pure-backend", stringValue = "file") +@RegisterClientHeaders(PureAuthenticationHeaderFactory.class) class FileBasedPureAPI implements PureAPI { - @ConfigProperty(name = "damap.elsevier-pure-projects-file") - URL projectsFile; - @ConfigProperty(name = "damap.elsevier-pure-persons-file") - URL personsFile; + @Inject TenantConfigResolver tenantConfigResolver; @Override public PureAPIPaginatedProjectsResponse listAllProjects(Long size, Long offset) { ObjectMapper mapper = new ObjectMapper(); - try (InputStream in = projectsFile.openStream()) { + try (InputStream in = + tenantConfigResolver.getTenantAwareConfig().elsevierPureProjectsFile().openStream()) { return mapper.readValue(in, PureAPIPaginatedProjectsResponse.class); } catch (IOException e) { throw new RuntimeException(e); @@ -46,7 +44,8 @@ public PureAPIProject getProject(String uuid) { @Override public PureAPIPaginatedPersonsResponse listAllPersons(Long size, Long offset) { ObjectMapper mapper = new ObjectMapper(); - try (InputStream in = personsFile.openStream()) { + try (InputStream in = + tenantConfigResolver.getTenantAwareConfig().elsevierPurePersonsFile().openStream()) { return mapper.readValue(in, PureAPIPaginatedPersonsResponse.class); } catch (IOException e) { throw new RuntimeException(e); diff --git a/src/main/java/org/damap/base/integration/pure/HTTPBasedPureAPI.java b/src/main/java/org/damap/base/integration/pure/HTTPBasedPureAPI.java index e4ad0da47..59fd4b59c 100644 --- a/src/main/java/org/damap/base/integration/pure/HTTPBasedPureAPI.java +++ b/src/main/java/org/damap/base/integration/pure/HTTPBasedPureAPI.java @@ -1,18 +1,14 @@ package org.damap.base.integration.pure; -import io.quarkus.arc.lookup.LookupIfProperty; import jakarta.enterprise.inject.Typed; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @Path("") -@RegisterRestClient(configKey = "elsevier-pure") -@RegisterClientHeaders(PureAuthenticationHeaderFactory.class) @Produces(MediaType.APPLICATION_JSON) @Typed(HTTPBasedPureAPI.class) -@LookupIfProperty(name = "damap.elsevier-pure-backend", stringValue = "http") +@RegisterClientHeaders(PureAuthenticationHeaderFactory.class) interface HTTPBasedPureAPI extends PureAPI { /** * List all projects using pagination. diff --git a/src/main/java/org/damap/base/integration/pure/PureAPIFactory.java b/src/main/java/org/damap/base/integration/pure/PureAPIFactory.java index bc9b8773d..2dfd079a4 100644 --- a/src/main/java/org/damap/base/integration/pure/PureAPIFactory.java +++ b/src/main/java/org/damap/base/integration/pure/PureAPIFactory.java @@ -2,28 +2,52 @@ import jakarta.annotation.Priority; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipse.microprofile.rest.client.inject.RestClient; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.damap.base.rest.config.domain.TenantConfigResolver; +import org.damap.base.security.SecurityService; +import org.eclipse.microprofile.rest.client.RestClientBuilder; @ApplicationScoped class PureAPIFactory { - @ConfigProperty(name = "damap.elsevier-pure-backend", defaultValue = "http") - String backend; + @Inject TenantConfigResolver tenantConfigResolver; + + @Inject SecurityService securityService; @Inject Instance fileAPI; - @Inject @RestClient Instance httpAPI; + private final Map httpClients = new ConcurrentHashMap<>(); @Produces + @RequestScoped @Priority(1) PureAPI create() { + String backend = tenantConfigResolver.getTenantAwareConfig().elsevierPureBackend(); return switch (backend) { case "file" -> fileAPI.get(); - case "http" -> httpAPI.get(); + case "http" -> getClient(); default -> throw new IllegalArgumentException("Pure API backend not supported: " + backend); }; } + + private HTTPBasedPureAPI getClient() { + String aff = securityService.getAffiliation(); + if (aff == null || tenantConfigResolver.isMultitenancyDisabled()) { + aff = "no-tenant-registered"; + } + return httpClients.computeIfAbsent( + aff, + missingClient -> + RestClientBuilder.newBuilder() + .baseUri( + URI.create( + tenantConfigResolver.getTenantAwareConfig().elsevierPureEndpointUrl())) + .register(PureAuthenticationHeaderFactory.class) + .build(HTTPBasedPureAPI.class)); + } } diff --git a/src/main/java/org/damap/base/integration/pure/PureAuthenticationHeaderFactory.java b/src/main/java/org/damap/base/integration/pure/PureAuthenticationHeaderFactory.java index 19f29b95e..ae007b7b7 100644 --- a/src/main/java/org/damap/base/integration/pure/PureAuthenticationHeaderFactory.java +++ b/src/main/java/org/damap/base/integration/pure/PureAuthenticationHeaderFactory.java @@ -1,23 +1,26 @@ package org.damap.base.integration.pure; +import io.quarkus.arc.Unremovable; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedMap; -import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.damap.base.rest.config.domain.TenantConfigResolver; import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; /** This class implements injecting the API key into the {@link PureAPI}. */ @ApplicationScoped -class PureAuthenticationHeaderFactory implements ClientHeadersFactory { - @ConfigProperty(name = "damap.elsevier-pure-api-key") - String apiKey; +@Unremovable +public class PureAuthenticationHeaderFactory implements ClientHeadersFactory { + + @Inject TenantConfigResolver tenantConfigResolver; @Override public MultivaluedMap update( MultivaluedMap incomingHeaders, MultivaluedMap clientOutgoingHeaders) { MultivaluedMap result = new MultivaluedHashMap<>(); - result.add("api-key", apiKey); + result.add("api-key", tenantConfigResolver.getTenantAwareConfig().elsevierPureApiKey()); return result; } } diff --git a/src/main/java/org/damap/base/integration/pure/PureProjectService.java b/src/main/java/org/damap/base/integration/pure/PureProjectService.java index f8378e540..01a80942c 100644 --- a/src/main/java/org/damap/base/integration/pure/PureProjectService.java +++ b/src/main/java/org/damap/base/integration/pure/PureProjectService.java @@ -4,15 +4,15 @@ import jakarta.inject.Inject; import java.util.*; import lombok.extern.jbosslog.JBossLog; -import org.damap.base.enums.EContributorRole; import org.damap.base.integration.ProjectServiceProvider; import org.damap.base.rest.base.ResultList; import org.damap.base.rest.base.Search; +import org.damap.base.rest.config.domain.DamapTenantAwareConfig; +import org.damap.base.rest.config.domain.TenantConfigResolver; import org.damap.base.rest.dmp.domain.ContributorDO; import org.damap.base.rest.dmp.domain.ProjectDO; import org.damap.base.rest.dmp.domain.ProjectSupplementDO; import org.damap.base.security.SecurityService; -import org.eclipse.microprofile.config.inject.ConfigProperty; /** * This class partially implements reading Elsevier Pure Project and Person objects from their API. @@ -24,22 +24,11 @@ @JBossLog @ApplicationScoped public class PureProjectService implements ProjectServiceProvider { + @Inject PureAPI pureAPI; @Inject SecurityService securityService; - /** Maps the configured Pure contributor role classification URIs to DAMAP roles. */ - @ConfigProperty( - name = "damap.elsevier-pure-contributor-role-classifications", - defaultValue = "{}") - RoleClassificationMappingConfiguration contributorRoleMapping; - - /** Describes the Pure classification URI for project descriptions. */ - @ConfigProperty(name = "damap.elsevier-pure-description-classification", defaultValue = "") - String descriptionClassification; - - /** Describes the Pure classification URI for project lead. */ - @ConfigProperty(name = "damap.elsevier-pure-project-lead-role-classification", defaultValue = "") - String projectLeadRoleClassification; + @Inject TenantConfigResolver tenantConfigResolver; @Override public List getProjectStaff(String projectId) { @@ -75,12 +64,16 @@ private ContributorDO getContributorDO(PureAPIParticipantAssociation participant private void convertContributorRoles( PureAPIParticipantAssociation participantAssociation, ContributorDO contributor) { - if (participantAssociation.role != null - && participantAssociation.role.uri != null - && contributorRoleMapping.configs.containsKey(participantAssociation.role.uri)) { - Set roles = new HashSet<>(); - roles.add(contributorRoleMapping.configs.get(participantAssociation.role.uri)); - contributor.setRoles(roles); + List elsevierPureContributorRoleClassifications = + tenantConfigResolver.getTenantAwareConfig().elsevierPureContributorRoleClassifications(); + if (participantAssociation.role != null && participantAssociation.role.uri != null) { + elsevierPureContributorRoleClassifications.stream() + .filter( + pureRoleClassification -> + pureRoleClassification.pureRoleUri().equals(participantAssociation.role.uri)) + .findFirst() + .map(DamapTenantAwareConfig.PureRoleClassification::contributorRole) + .ifPresent(role -> contributor.setRoles(Set.of(role))); } } @@ -100,7 +93,10 @@ public ContributorDO getProjectLeader(String projectId) { .filter(participantAssociation -> participantAssociation.role.uri != null) .filter( participantAssociation -> - participantAssociation.role.uri.equals(projectLeadRoleClassification)) + participantAssociation.role.uri.equals( + tenantConfigResolver + .getTenantAwareConfig() + .elsevierPureProjectLeadRoleClassification())) .map(this::getContributorDO) .findFirst() .orElse(null); @@ -116,7 +112,7 @@ public ResultList getRecommended(Search search) { ResultList res = new ResultList<>(); res.setSearch(search); res.setItems( - this.pureAPI.listAllProjects().stream() + pureAPI.listAllProjects().stream() .filter( project -> project.participants.stream() @@ -129,7 +125,12 @@ public ResultList getRecommended(Search search) { participant -> ((PureAPIInternalParticipantAssociation) participant) .person.uuid.equals(securityService.getUserId()))) - .map(project -> project.toProjectDO(descriptionClassification)) + .map( + project -> + project.toProjectDO( + tenantConfigResolver + .getTenantAwareConfig() + .elsevierPureDescriptionClassification())) .toList()); return res; } @@ -140,7 +141,8 @@ public ProjectDO read(String id) { if (project == null || project.participants == null) { return null; } - return project.toProjectDO(descriptionClassification); + return project.toProjectDO( + tenantConfigResolver.getTenantAwareConfig().elsevierPureDescriptionClassification()); } @Override @@ -150,7 +152,12 @@ public ResultList search(Search query) { res.setItems( pureAPI.listAllProjects().stream() .filter(project -> project.titleContains(query.getQuery())) - .map(project -> project.toProjectDO(descriptionClassification)) + .map( + project -> + project.toProjectDO( + tenantConfigResolver + .getTenantAwareConfig() + .elsevierPureDescriptionClassification())) .toList()); return res; } diff --git a/src/main/java/org/damap/base/integration/pure/RoleClassificationMappingConfiguration.java b/src/main/java/org/damap/base/integration/pure/RoleClassificationMappingConfiguration.java deleted file mode 100644 index 2938522d1..000000000 --- a/src/main/java/org/damap/base/integration/pure/RoleClassificationMappingConfiguration.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.damap.base.integration.pure; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.util.Map; -import lombok.Data; -import org.damap.base.enums.EContributorRole; - -/** A workaround for Quarkus configs being strictly string-string. */ -@Data -public class RoleClassificationMappingConfiguration { - Map configs; - - /** - * A workaround for Quarkus configs being strictly string-string. - * - * @param input a {@link java.lang.String} object - * @return a {@link org.damap.base.rest.config.domain.PersonServiceConfigurations} object - * @throws java.io.IOException if any. - */ - public static RoleClassificationMappingConfiguration of(String input) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - - RoleClassificationMappingConfiguration entity = new RoleClassificationMappingConfiguration(); - entity.configs = mapper.readValue(input, new TypeReference<>() {}); - - return entity; - } -} diff --git a/src/main/java/org/damap/base/rest/ConfigResource.java b/src/main/java/org/damap/base/rest/ConfigResource.java index f73835a82..3774f2f95 100644 --- a/src/main/java/org/damap/base/rest/ConfigResource.java +++ b/src/main/java/org/damap/base/rest/ConfigResource.java @@ -12,8 +12,7 @@ import lombok.extern.jbosslog.JBossLog; import org.damap.base.domain.ColorTheme; import org.damap.base.domain.Image; -import org.damap.base.rest.config.domain.ConfigDO; -import org.damap.base.rest.config.domain.PersonServiceConfigurations; +import org.damap.base.rest.config.domain.*; import org.damap.base.rest.theme.service.ColorThemeService; import org.damap.base.rest.theme.service.ImageThemeService; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -24,6 +23,7 @@ @PermitAll @JBossLog public class ConfigResource { + @ConfigProperty(name = "quarkus.oidc.token.issuer") String issuer; @@ -57,25 +57,18 @@ public class ConfigResource { @ConfigProperty(name = "damap.env") String env; - @ConfigProperty(name = "damap.person-services") - PersonServiceConfigurations personServiceConfigurations; - @ConfigProperty(name = "rest.fits/mp-rest/url") Optional fitsUrl; @ConfigProperty(name = "rest.gotenberg/mp-rest/url") Optional gotenbergUrl; - @ConfigProperty(name = "damap.fields.ethical-report-enabled") - boolean ethicalReportEnabled; - - @ConfigProperty(name = "damap.title", defaultValue = "DAMAP Tool") - String appTitle; - @Inject ColorThemeService colorThemeService; @Inject ImageThemeService imageThemeService; + @Inject TenantConfigResolver tenantConfigResolver; + /** * config. * @@ -96,11 +89,17 @@ public ConfigDO config() { configDO.setAdminRoleName(adminRoleName); configDO.setResponseType("code"); // hardcoded since DAMAP only supports this flow configDO.setEnv(env); - configDO.setAppTitle(appTitle); - configDO.setPersonSearchServiceConfigs(personServiceConfigurations.getConfigs()); + DamapTenantAwareConfig tenantAwareConfig = tenantConfigResolver.getTenantAwareConfig(); + configDO.setAppTitle(tenantAwareConfig.title()); + // The ServiceConfig runtime interface proxy cannot be marshalled and sent to the frontend, so + // we need a DO + List serviceConfigDOS = + tenantAwareConfig.personServices().stream().map(ServiceConfigDO::new).toList(); + configDO.setPersonSearchServiceConfigs(serviceConfigDOS); + configDO.setProjectSearchServiceConfig(tenantAwareConfig.projectService()); configDO.setFitsServiceAvailable(getFitsServiceAvailability()); configDO.setLivePreviewAvailable(getGotenbergServiceAvailability()); - configDO.setEthicalReportEnabled(ethicalReportEnabled); + configDO.setEthicalReportEnabled(tenantAwareConfig.fields().ethicalReportEnabled()); ColorTheme colorTheme = colorThemeService.getTheme(); configDO.setColorTheme(colorTheme); @@ -108,6 +107,7 @@ public ConfigDO config() { List images = imageThemeService.getImages(); configDO.setImages(images); + configDO.setMultitenancyEnabled(!tenantConfigResolver.isMultitenancyDisabled()); return configDO; } diff --git a/src/main/java/org/damap/base/rest/PersonServiceBroker.java b/src/main/java/org/damap/base/rest/PersonServiceBroker.java index 554345ac8..6df73bf6f 100644 --- a/src/main/java/org/damap/base/rest/PersonServiceBroker.java +++ b/src/main/java/org/damap/base/rest/PersonServiceBroker.java @@ -8,7 +8,7 @@ import java.util.List; import lombok.extern.jbosslog.JBossLog; import org.damap.base.integration.PersonService; -import org.damap.base.rest.config.domain.ServiceConfig; +import org.damap.base.rest.config.domain.ServiceConfigDO; /** PersonServiceBroker class. */ @JBossLog @@ -25,7 +25,7 @@ public class PersonServiceBroker { */ @Inject public PersonServiceBroker(ConfigResource config, @All List availableServices) { - List configuredServices = config.personServiceConfigurations.getConfigs(); + List configuredServices = config.config().getPersonSearchServiceConfigs(); configuredServices.forEach( serviceConfig -> { diff --git a/src/main/java/org/damap/base/rest/ProjectService.java b/src/main/java/org/damap/base/rest/ProjectService.java index ba280fb29..4a0d9c1d8 100644 --- a/src/main/java/org/damap/base/rest/ProjectService.java +++ b/src/main/java/org/damap/base/rest/ProjectService.java @@ -12,10 +12,10 @@ import org.damap.base.integration.ProjectServiceProvider; import org.damap.base.rest.base.ResultList; import org.damap.base.rest.base.Search; +import org.damap.base.rest.config.domain.TenantConfigResolver; import org.damap.base.rest.dmp.domain.ContributorDO; import org.damap.base.rest.dmp.domain.ProjectDO; import org.damap.base.rest.dmp.domain.ProjectSupplementDO; -import org.eclipse.microprofile.config.inject.ConfigProperty; /** * This class is an overlay for {@link ProjectServiceProvider} that automatically determines which @@ -29,19 +29,18 @@ public final class ProjectService implements ProjectServiceProvider { @Inject @All List services; - @ConfigProperty(name = "damap.projects-service") - String selectedService; + @Inject TenantConfigResolver tenantConfigResolver; ProjectServiceProvider backingServiceCache; public ProjectService() {} - public ProjectService(List services, String selectedService) { + public ProjectService(List services) { this.services = services; - this.selectedService = selectedService; } private ProjectServiceProvider getProjectService() { + String selectedService = tenantConfigResolver.getTenantAwareConfig().projectService(); log.info("Getting projects service " + selectedService); if (backingServiceCache == null) { if (services.isEmpty()) { @@ -72,7 +71,7 @@ private ProjectServiceProvider getProjectService() { .map(configId -> configId == null ? "default" : configId) .collect(Collectors.toSet()); throw new RuntimeException( - "The configured damap.projects-service (" + "The configured damap.tenant-aware.project-service (" + selectedService + ") was not found. " + "Please select one of " diff --git a/src/main/java/org/damap/base/rest/config/domain/ConfigDO.java b/src/main/java/org/damap/base/rest/config/domain/ConfigDO.java index 5a9c2bd0d..f2d58b165 100644 --- a/src/main/java/org/damap/base/rest/config/domain/ConfigDO.java +++ b/src/main/java/org/damap/base/rest/config/domain/ConfigDO.java @@ -23,11 +23,13 @@ public class ConfigDO { private String emailClaim; private String adminRoleName; private String env; - private List personSearchServiceConfigs; + private List personSearchServiceConfigs; + private String projectSearchServiceConfig; private boolean fitsServiceAvailable; private boolean livePreviewAvailable; private boolean ethicalReportEnabled; private String appTitle; private ColorTheme colorTheme; private List images; + private boolean multitenancyEnabled; } diff --git a/src/main/java/org/damap/base/rest/config/domain/DamapMultiTenantConfig.java b/src/main/java/org/damap/base/rest/config/domain/DamapMultiTenantConfig.java new file mode 100644 index 000000000..dcf88a9b5 --- /dev/null +++ b/src/main/java/org/damap/base/rest/config/domain/DamapMultiTenantConfig.java @@ -0,0 +1,19 @@ +package org.damap.base.rest.config.domain; + +import io.smallrye.config.ConfigMapping; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Holds a map of all configuration options which need to be configured per tenant. In case of + * single tenant mode, this is empty and unused. In case of multitenancy enabled, this configuration + * object can be used to fetch correct configuration dependent on the current tenant and to check if + * multitenancy is active using tenants(). + */ +@ConfigMapping(prefix = "damap.tenants") +public interface DamapMultiTenantConfig { + Optional> tenants(); + + Map tenantConfigs(); +} diff --git a/src/main/java/org/damap/base/rest/config/domain/DamapSingleTenantConfig.java b/src/main/java/org/damap/base/rest/config/domain/DamapSingleTenantConfig.java new file mode 100644 index 000000000..32c1ff5f7 --- /dev/null +++ b/src/main/java/org/damap/base/rest/config/domain/DamapSingleTenantConfig.java @@ -0,0 +1,10 @@ +package org.damap.base.rest.config.domain; + +import io.smallrye.config.ConfigMapping; + +/** + * Holds all configuration options which need to be configured per tenant. In case of multitenancy, + * this is unused. In single tenant mode, it acts as a normal configuration. + */ +@ConfigMapping(prefix = "damap.tenant-aware") +public interface DamapSingleTenantConfig extends DamapTenantAwareConfig {} diff --git a/src/main/java/org/damap/base/rest/config/domain/DamapTenantAwareConfig.java b/src/main/java/org/damap/base/rest/config/domain/DamapTenantAwareConfig.java new file mode 100644 index 000000000..fe5117b0b --- /dev/null +++ b/src/main/java/org/damap/base/rest/config/domain/DamapTenantAwareConfig.java @@ -0,0 +1,50 @@ +package org.damap.base.rest.config.domain; + +import java.net.URL; +import java.util.List; +import org.damap.base.enums.EContributorRole; + +/** Represents all config options that need to be uniquely configured per tenant */ +public interface DamapTenantAwareConfig { + String title(); + + Fields fields(); + + String projectService(); + + List personServices(); + + String elsevierPureDescriptionClassification(); + + List elsevierPureContributorRoleClassifications(); + + String elsevierPureProjectLeadRoleClassification(); + + String elsevierPureBackend(); + + String elsevierPureEndpointUrl(); + + String elsevierPureApiKey(); + + URL elsevierPureProjectsFile(); + + URL elsevierPurePersonsFile(); + + interface Fields { + boolean ethicalReportEnabled(); + } + + interface ServiceConfig { + String className(); + + String displayText(); + + String queryValue(); + } + + interface PureRoleClassification { + String pureRoleUri(); + + EContributorRole contributorRole(); + } +} diff --git a/src/main/java/org/damap/base/rest/config/domain/PersonServiceConfigurations.java b/src/main/java/org/damap/base/rest/config/domain/PersonServiceConfigurations.java deleted file mode 100644 index 4d369db78..000000000 --- a/src/main/java/org/damap/base/rest/config/domain/PersonServiceConfigurations.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.damap.base.rest.config.domain; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.TextNode; -import java.io.IOException; -import java.util.List; -import lombok.Data; - -/** PersonServiceConfigurations class. */ -@Data -public class PersonServiceConfigurations { - List configs; - - /** - * Decodes a JSON string value into the current configuration. - * - * @param input a JSON {@link java.lang.String} value in the format of - * {"person-services": [...]} or directly as a person services configuration list. - * @return a {@link org.damap.base.rest.config.domain.PersonServiceConfigurations} the decoded - * object - * @throws java.io.IOException if any. - */ - public static PersonServiceConfigurations of(String input) throws IOException { - // We have to do a two-step decoding here because the - // input value comes as {"person-services": [...]}. - ObjectMapper mapper = new ObjectMapper(); - var jsonTree = mapper.readTree(input); - String stringValue; - if (jsonTree.has("person-services")) { - // Config file, with prefix. - var jsonConfigs = jsonTree.get("person-services"); - if (jsonConfigs instanceof TextNode) { - stringValue = jsonConfigs.textValue(); - } else { - stringValue = jsonConfigs.toString(); - } - } else { - // Value from environment variables, no prefix. - stringValue = input; - } - - PersonServiceConfigurations entity = new PersonServiceConfigurations(); - entity.configs = mapper.readValue(stringValue, new TypeReference<>() {}); - - return entity; - } -} diff --git a/src/main/java/org/damap/base/rest/config/domain/ServiceConfig.java b/src/main/java/org/damap/base/rest/config/domain/ServiceConfig.java deleted file mode 100644 index f6796278a..000000000 --- a/src/main/java/org/damap/base/rest/config/domain/ServiceConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.damap.base.rest.config.domain; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** ServiceConfig class. */ -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class ServiceConfig { - - @JsonAlias({"display-text"}) - String displayText; - - @JsonAlias({"query-value"}) - String queryValue; - - @JsonProperty(value = "class-name") - String className; -} diff --git a/src/main/java/org/damap/base/rest/config/domain/ServiceConfigDO.java b/src/main/java/org/damap/base/rest/config/domain/ServiceConfigDO.java new file mode 100644 index 000000000..19736db9e --- /dev/null +++ b/src/main/java/org/damap/base/rest/config/domain/ServiceConfigDO.java @@ -0,0 +1,25 @@ +package org.damap.base.rest.config.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * ServiceConfigDO class. Since the ServiceConfig runtime interface proxy cannot be marshalled and + * sent to the frontend, we need this DO + */ +@Data +@AllArgsConstructor +public class ServiceConfigDO { + + String displayText; + + String queryValue; + + String className; + + public ServiceConfigDO(DamapTenantAwareConfig.ServiceConfig serviceConfig) { + this.displayText = serviceConfig.displayText(); + this.queryValue = serviceConfig.queryValue(); + this.className = serviceConfig.className(); + } +} diff --git a/src/main/java/org/damap/base/rest/config/domain/TenantConfigResolver.java b/src/main/java/org/damap/base/rest/config/domain/TenantConfigResolver.java new file mode 100644 index 000000000..32bbd50eb --- /dev/null +++ b/src/main/java/org/damap/base/rest/config/domain/TenantConfigResolver.java @@ -0,0 +1,29 @@ +package org.damap.base.rest.config.domain; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.util.List; +import org.damap.base.security.SecurityService; + +@ApplicationScoped +public class TenantConfigResolver { + + @Inject DamapSingleTenantConfig defaultConfig; + @Inject DamapMultiTenantConfig multiTenantConfig; + + @Inject SecurityService securityService; + + public DamapTenantAwareConfig getTenantAwareConfig() { + String aff = securityService.getAffiliation(); + if (aff == null || isMultitenancyDisabled()) { + return defaultConfig; + } else { + return multiTenantConfig.tenantConfigs().get(aff); + } + } + + public boolean isMultitenancyDisabled() { + List tenants = multiTenantConfig.tenants().orElse(null); + return tenants == null || tenants.isEmpty(); + } +} diff --git a/src/main/java/org/damap/base/security/DamapSecurityAugmentor.java b/src/main/java/org/damap/base/security/DamapSecurityAugmentor.java index 487f3c827..c341ddfbf 100644 --- a/src/main/java/org/damap/base/security/DamapSecurityAugmentor.java +++ b/src/main/java/org/damap/base/security/DamapSecurityAugmentor.java @@ -3,7 +3,6 @@ import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal; import io.quarkus.security.identity.AuthenticationRequestContext; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.identity.SecurityIdentityAugmentor; import io.smallrye.mutiny.Uni; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -18,7 +17,7 @@ * delegates to {@link UserSyncService} to ensure the user record exists and is up-to-date. */ @ApplicationScoped -public class DamapSecurityAugmentor implements SecurityIdentityAugmentor { +public class DamapSecurityAugmentor { @Inject UserSyncService userSyncService; @@ -37,7 +36,6 @@ public class DamapSecurityAugmentor implements SecurityIdentityAugmentor { @ConfigProperty(name = "damap.auth.family-name-claim", defaultValue = "family_name") String familyNameClaim; - @Override public Uni augment( SecurityIdentity identity, AuthenticationRequestContext context) { if (identity.isAnonymous()) { diff --git a/src/main/java/org/damap/base/security/SecurityService.java b/src/main/java/org/damap/base/security/SecurityService.java index 671276dc1..501f465ef 100644 --- a/src/main/java/org/damap/base/security/SecurityService.java +++ b/src/main/java/org/damap/base/security/SecurityService.java @@ -14,6 +14,7 @@ import jakarta.ws.rs.core.HttpHeaders; import java.security.Principal; import java.util.List; +import java.util.Objects; import java.util.Optional; import lombok.extern.jbosslog.JBossLog; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -37,7 +38,7 @@ public class SecurityService { @ConfigProperty(name = "damap.auth.affiliations-claim") String affiliationsClaim; - @ConfigProperty(name = "tenants", defaultValue = "") + @ConfigProperty(name = "damap.tenants.tenant-list", defaultValue = "") Optional> tenants; @ConfigProperty(name = "invenio.shared-secret") @@ -102,12 +103,22 @@ public boolean isAdmin() { return securityIdentity.hasRole(adminRoleName); } + /** + * Returns a String representing the affiliation of the logged-in user. + * + * @return a {@link String} representing the tenants' affiliation. Returns null if no affiliation + * could be read. + */ public String getAffiliation() { final Principal principal = securityIdentity.getPrincipal(); if (!(principal instanceof OidcJwtCallerPrincipal oidcPrincipal)) { return null; } + if (Objects.isNull(oidcPrincipal.getClaims().getClaimValue(affiliationsClaim))) { + return null; + } + // affiliations come from the eduPersonScopedAffiliation attribute - in EduID they are // structured like role@institution // EduGain does not have this convention - currently we just throw an error if the affiliation @@ -132,7 +143,7 @@ public String getAffiliation() { List tenantList = tenants.orElse(List.of()); - // check if affiliations are actual tenants and if exactly one unique affiliation is present + // check if affiliations are actual tenants and if exactly one unique affiliation is present // currently DAMAP cannot handle multiple affiliations List validAffiliations = affiliations.stream().filter(tenantList::contains).toList(); if (validAffiliations.size() == 1) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 5609a29d7..ebe8bd1ba 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,7 +1,6 @@ # custom config settings # replace these in the config of your custom project or by overriding these variables damap: - title: DAMAP Tool env: DEV # override in your custom project with PROD for production deployment auth: # Scopes to request during login: @@ -24,48 +23,55 @@ damap: # If DAMAP is locally deployed ADMIN_ROLE constant in the DefaultProfile test profile needs to be # the same as here, otherwise the tests fail admin-role-name: Damap Admin - fields: - ethical-report-enabled: true - - projects-service: default - ## Elsevier Pure integration: - # projects-service: elsevier-pure - # The following settings map Pure classification URIs to DAMAP properties. Pure classification URIs start with - # /dk/atira/pure, but are otherwise specific to the institutional setup in Pure. - - # The project description field DAMAP should use: - elsevier-pure-description-classification: /dk/atira/pure/projectdescription - # How Pure project contributor classifications map to DAMAP: - elsevier-pure-contributor-role-classifications: - # DAMAP supports the following roles: - # DATA_COLLECTOR, DATA_CURATOR, DATA_MANAGER, DISTRIBUTOR, EDITOR, HOSTING_INSTITUTION, PRODUCER, PROJECT_LEADER, - # PROJECT_MANAGER, PROJECT_MEMBER, REGISTRATION_AGENCY, REGISTRATION_AUTHORITY, RELATED_PERSON, RESEARCHER, - # RESEARCH_GROUP, RIGHTS_HOLDER, SPONSOR, SUPERVISOR, WORK_PACKAGE_LEADER, PRINCIPAL_INVESTIGATOR, - # PROJECT_COORDINATOR, OTHER - /dk/atira/pure/member: PROJECT_MEMBER - # Which Pure classification should be read as the project lead: - elsevier-pure-project-lead-role-classification: /dk/atira/pure/projectlead - ## HTTP backend: - elsevier-pure-backend: http - elsevier-pure-endpoint-url: "https://your-pure-instance.elsevierpure.com/ws/api" - elsevier-pure-api-key: "your API key here" - ## Alternatively, the file-based backend: - # elsevier-pure-backend: file - elsevier-pure-projects-file: "file:///path/to/projects.json" - elsevier-pure-persons-file: "file:///path/to/persons.json" - - ## Set up person lookups here: - person-services: + + # tenant-aware here means that in case of multitenancy, these configs need to be set per tenant, and not per instance + # for more information, see the multitenancy documentation on the website + tenant-aware: + title: DAMAP Tool + + fields: + ethical-report-enabled: true + + project-service: default ## Elsevier Pure integration: - # - display-text: 'Pure' - # query-value: 'PURE' - # class-name: 'org.damap.base.integration.pure.PurePersonService' - - display-text: 'University' - query-value: 'UNIVERSITY' - class-name: 'org.damap.base.integration.mock.MockUniversityPersonServiceImpl' - - display-text: 'ORCID' - query-value: 'ORCID' - class-name: 'org.damap.base.integration.orcid.ORCIDPersonServiceImpl' + # project-service: elsevier-pure + # The following settings map Pure classification URIs to DAMAP properties. Pure classification URIs start with + # /dk/atira/pure, but are otherwise specific to the institutional setup in Pure. + + # The project description field DAMAP should use: + elsevier-pure-description-classification: /dk/atira/pure/projectdescription + # How Pure project contributor classifications map to DAMAP: + elsevier-pure-contributor-role-classifications: + # DAMAP supports the following roles: + # DATA_COLLECTOR, DATA_CURATOR, DATA_MANAGER, DISTRIBUTOR, EDITOR, HOSTING_INSTITUTION, PRODUCER, PROJECT_LEADER, + # PROJECT_MANAGER, PROJECT_MEMBER, REGISTRATION_AGENCY, REGISTRATION_AUTHORITY, RELATED_PERSON, RESEARCHER, + # RESEARCH_GROUP, RIGHTS_HOLDER, SPONSOR, SUPERVISOR, WORK_PACKAGE_LEADER, PRINCIPAL_INVESTIGATOR, + # PROJECT_COORDINATOR, OTHER + - pure-role-uri: /dk/atira/pure/member + contributor-role: PROJECT_MEMBER + # Which Pure classification should be read as the project lead: + elsevier-pure-project-lead-role-classification: /dk/atira/pure/projectlead + ## HTTP backend: + elsevier-pure-backend: http + elsevier-pure-endpoint-url: "https://your-pure-instance.elsevierpure.com/ws/api" + elsevier-pure-api-key: "your API key here" + ## Alternatively, the file-based backend: + # elsevier-pure-backend: file + elsevier-pure-projects-file: "file:///path/to/projects.json" + elsevier-pure-persons-file: "file:///path/to/persons.json" + + ## Set up person lookups here: + person-services: + ## Elsevier Pure integration: + # - display-text: 'Pure' + # query-value: 'PURE' + # class-name: 'org.damap.base.integration.pure.PurePersonService' + - display-text: 'University' + query-value: 'UNIVERSITY' + class-name: 'org.damap.base.integration.mock.MockUniversityPersonServiceImpl' + - display-text: 'ORCID' + query-value: 'ORCID' + class-name: 'org.damap.base.integration.orcid.ORCIDPersonServiceImpl' invenio: disabled: true @@ -127,12 +133,7 @@ quarkus: always-include: true # set to false if swagger-ui should only be available in dev mode oauth-client-id: ${quarkus.oidc.client-id} - rest-client: - elsevier-pure: - url: ${damap.elsevier-pure-endpoint-url} - rest: - elsevier-pure/mp-rest/url: ${damap.elsevier-pure-endpoint-url} mock-projects/mp-rest/url: http://api-mock:80 mock-persons/mp-rest/url: http://api-mock:80 fits/mp-rest/url: http://fits-service:8080/fits @@ -189,8 +190,6 @@ rest: url: jdbc:h2:mem:test oidc: enabled: false - rest: - elsevier-pure/mp-rest/url: "http://localhost:9999/fake-pure-api" "%multitenant": quarkus: diff --git a/src/test/java/org/damap/base/TestProfiles.java b/src/test/java/org/damap/base/TestProfiles.java index 53bc040d5..215d1fa5d 100644 --- a/src/test/java/org/damap/base/TestProfiles.java +++ b/src/test/java/org/damap/base/TestProfiles.java @@ -30,7 +30,7 @@ public static class DefaultProfile implements QuarkusTestProfile { @Override public Map getConfigOverrides() { Map overrides = new HashMap<>(); - overrides.put("damap.projects-service", "default"); + overrides.put("damap.tenant-aware.project-service", "default"); return overrides; } diff --git a/src/test/java/org/damap/base/integration/pure/PureAPIConnectionTest.java b/src/test/java/org/damap/base/integration/pure/PureAPIConnectionTest.java index 7faf6bf30..079ee0305 100644 --- a/src/test/java/org/damap/base/integration/pure/PureAPIConnectionTest.java +++ b/src/test/java/org/damap/base/integration/pure/PureAPIConnectionTest.java @@ -16,10 +16,10 @@ public class PureAPIConnectionTest { @Inject PureAPI pureAPI; - @ConfigProperty(name = "damap.elsevier-pure-api-key") + @ConfigProperty(name = "damap.tenant-aware.elsevier-pure-api-key") String apiKey; - @ConfigProperty(name = "damap.elsevier-pure-endpoint-url") + @ConfigProperty(name = "damap.tenant-aware.elsevier-pure-endpoint-url") String endpointUrl; // Example test UUIDs - replace these with actual project UUIDs from your PURE instance diff --git a/src/test/java/org/damap/base/integration/pure/PureIntegrationTestProfile.java b/src/test/java/org/damap/base/integration/pure/PureIntegrationTestProfile.java index fa0028696..171251188 100644 --- a/src/test/java/org/damap/base/integration/pure/PureIntegrationTestProfile.java +++ b/src/test/java/org/damap/base/integration/pure/PureIntegrationTestProfile.java @@ -22,13 +22,11 @@ public Map getConfigOverrides() { overrides.put("quarkus.http.test-port", "0"); - overrides.put("damap.elsevier-pure-backend", "http"); + overrides.put("damap.tenant-aware.elsevier-pure-backend", "http"); overrides.put( - "damap.elsevier-pure-endpoint-url", "https://your-pure-instance.elsevierpure.com/ws/api"); - overrides.put("damap.elsevier-pure-api-key", "your-pure-api-key-here"); - overrides.put( - "quarkus.rest-client.elsevier-pure.url", + "damap.tenant-aware.elsevier-pure-endpoint-url", "https://your-pure-instance.elsevierpure.com/ws/api"); + overrides.put("damap.tenant-aware.elsevier-pure-api-key", "your-pure-api-key-here"); System.out.println("PURE API configuration loaded for testing"); diff --git a/src/test/java/org/damap/base/integration/pure/PurePersonsServiceTest.java b/src/test/java/org/damap/base/integration/pure/PurePersonsServiceTest.java index a6a767294..2e0663012 100644 --- a/src/test/java/org/damap/base/integration/pure/PurePersonsServiceTest.java +++ b/src/test/java/org/damap/base/integration/pure/PurePersonsServiceTest.java @@ -1,7 +1,11 @@ package org.damap.base.integration.pure; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.google.common.io.Resources; import io.quarkus.arc.All; +import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; import jakarta.inject.Inject; @@ -11,22 +15,48 @@ import org.damap.base.rest.base.Pagination; import org.damap.base.rest.base.ResultList; import org.damap.base.rest.base.Search; +import org.damap.base.rest.config.domain.DamapTenantAwareConfig; +import org.damap.base.rest.config.domain.TenantConfigResolver; import org.damap.base.rest.dmp.domain.ContributorDO; import org.damap.base.rest.dmp.domain.IdentifierDO; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @QuarkusTest @TestProfile(PureTestProfile.class) public class PurePersonsServiceTest { - private PersonService createPersonsService() { - FileBasedPureAPI api = new FileBasedPureAPI(); - api.personsFile = Resources.getResource("org/damap/base/integration/pure/persons.json"); - api.projectsFile = Resources.getResource("org/damap/base/integration/pure/projects.json"); - - PurePersonService personService = new PurePersonService(); - personService.pureAPI = api; - return personService; + + @InjectMock TenantConfigResolver tenantConfigResolver; + + @Inject PureAPI pureAPI; + + @Inject @All List personsServices; + + @BeforeEach + public void setup() { + + DamapTenantAwareConfig tenantAwareConfig = mock(DamapTenantAwareConfig.class); + + try { + when(tenantAwareConfig.elsevierPurePersonsFile()) + .thenReturn(Resources.getResource("org/damap/base/integration/pure/persons.json")); + when(tenantAwareConfig.elsevierPureProjectsFile()) + .thenReturn(Resources.getResource("org/damap/base/integration/pure/projects.json")); + } catch (Exception e) { + throw new RuntimeException("File URLs are not valid"); + } + + when(tenantAwareConfig.elsevierPureBackend()).thenReturn("file"); + + when(tenantConfigResolver.getTenantAwareConfig()).thenReturn(tenantAwareConfig); + } + + private PersonService getPersonsService() { + return personsServices.stream() + .filter(item -> item instanceof PurePersonService) + .findFirst() + .orElse(null); } private ContributorDO getFirstPerson() { @@ -57,7 +87,7 @@ private ContributorDO getSecondPerson() { @Test public void testRead() { - PersonService svc = createPersonsService(); + PersonService svc = this.getPersonsService(); ContributorDO expected = getFirstPerson(); Assertions.assertEquals(expected, svc.read(expected.getUniversityId())); @@ -71,7 +101,7 @@ public void testRead() { @Test public void testSearch() { - PersonService svc = createPersonsService(); + PersonService svc = getPersonsService(); ContributorDO expected = getSecondPerson(); Search search = new Search(); @@ -82,14 +112,12 @@ public void testSearch() { Assertions.assertEquals(expected, results.getItems().get(0)); } - @Inject PureAPI pureAPI; - - @Inject @All List personsServices; + @Inject PureAPIFactory pureAPIFactory; @Test public void testWiring() { Assertions.assertNotNull(pureAPI); - Assertions.assertTrue(pureAPI instanceof FileBasedPureAPI); + Assertions.assertTrue(pureAPIFactory.create() instanceof FileBasedPureAPI); PersonService svc = personsServices.stream() diff --git a/src/test/java/org/damap/base/integration/pure/PurePersonsWireMockIntegrationTest.java b/src/test/java/org/damap/base/integration/pure/PurePersonsWireMockIntegrationTest.java index afc7d1144..2d9452098 100644 --- a/src/test/java/org/damap/base/integration/pure/PurePersonsWireMockIntegrationTest.java +++ b/src/test/java/org/damap/base/integration/pure/PurePersonsWireMockIntegrationTest.java @@ -8,7 +8,6 @@ import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; import java.net.URI; -import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -22,9 +21,6 @@ @TestProfile(WireMockPureTestProfile.class) public class PurePersonsWireMockIntegrationTest { - @ConfigProperty(name = "damap.elsevier-pure-api-key") - String apiKey; - private WireMockServer wireMockServer; private HTTPBasedPureAPI pureAPI; diff --git a/src/test/java/org/damap/base/integration/pure/PureProjectsServiceTest.java b/src/test/java/org/damap/base/integration/pure/PureProjectsServiceTest.java index 7cdf39bdc..c40db42c6 100644 --- a/src/test/java/org/damap/base/integration/pure/PureProjectsServiceTest.java +++ b/src/test/java/org/damap/base/integration/pure/PureProjectsServiceTest.java @@ -1,7 +1,11 @@ package org.damap.base.integration.pure; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.fasterxml.jackson.annotation.JsonFormat; import com.google.common.io.Resources; +import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; import jakarta.inject.Inject; @@ -10,36 +14,64 @@ import lombok.SneakyThrows; import org.damap.base.enums.EContributorRole; import org.damap.base.enums.EIdentifierType; -import org.damap.base.integration.ProjectServiceProvider; import org.damap.base.rest.ProjectService; +import org.damap.base.rest.config.domain.DamapTenantAwareConfig; +import org.damap.base.rest.config.domain.TenantConfigResolver; import org.damap.base.rest.dmp.domain.ContributorDO; import org.damap.base.rest.dmp.domain.IdentifierDO; import org.damap.base.rest.dmp.domain.ProjectDO; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @QuarkusTest @TestProfile(PureTestProfile.class) public class PureProjectsServiceTest { - private ProjectService createPersonsService() { - FileBasedPureAPI api = new FileBasedPureAPI(); - api.personsFile = Resources.getResource("org/damap/base/integration/pure/persons.json"); - api.projectsFile = Resources.getResource("org/damap/base/integration/pure/projects.json"); - - PureProjectService projectService = new PureProjectService(); - projectService.contributorRoleMapping = new RoleClassificationMappingConfiguration(); - HashMap roleMappings = new HashMap<>(); - roleMappings.put("/dk/atira/pure/member", EContributorRole.PROJECT_MEMBER); - roleMappings.put("/dk/atira/pure/test/projectlead", EContributorRole.PROJECT_LEADER); - projectService.contributorRoleMapping.configs = roleMappings; - projectService.projectLeadRoleClassification = "/dk/atira/pure/test/projectlead"; - projectService.descriptionClassification = "/dk/atira/pure/projectdescription"; - projectService.pureAPI = api; - - List services = new ArrayList<>(); - services.add(projectService); - this.projectService = new ProjectService(services, "elsevier-pure"); - return this.projectService; + + @InjectMock TenantConfigResolver tenantConfigResolver; + + @Inject PureAPI pureAPI; + + @Inject ProjectService projectService; + + @BeforeEach + public void setup() { + + DamapTenantAwareConfig tenantAwareConfig = mock(DamapTenantAwareConfig.class); + + DamapTenantAwareConfig.PureRoleClassification m1 = + mock(DamapTenantAwareConfig.PureRoleClassification.class); + when(m1.pureRoleUri()).thenReturn("/dk/atira/pure/member"); + when(m1.contributorRole()).thenReturn(EContributorRole.PROJECT_MEMBER); + + DamapTenantAwareConfig.PureRoleClassification m2 = + mock(DamapTenantAwareConfig.PureRoleClassification.class); + when(m2.pureRoleUri()).thenReturn("/dk/atira/pure/test/projectlead"); + when(m2.contributorRole()).thenReturn(EContributorRole.PROJECT_LEADER); + + when(tenantAwareConfig.elsevierPureContributorRoleClassifications()) + .thenReturn(List.of(m1, m2)); + + when(tenantAwareConfig.elsevierPureProjectLeadRoleClassification()) + .thenReturn("/dk/atira/pure/test/projectlead"); + + when(tenantAwareConfig.elsevierPureDescriptionClassification()) + .thenReturn("/dk/atira/pure/projectdescription"); + + try { + when(tenantAwareConfig.elsevierPurePersonsFile()) + .thenReturn(Resources.getResource("org/damap/base/integration/pure/persons.json")); + when(tenantAwareConfig.elsevierPureProjectsFile()) + .thenReturn(Resources.getResource("org/damap/base/integration/pure/projects.json")); + } catch (Exception e) { + throw new RuntimeException("File URLs are not valid"); + } + + when(tenantAwareConfig.projectService()).thenReturn("elsevier-pure"); + + when(tenantAwareConfig.elsevierPureBackend()).thenReturn("file"); + + when(tenantConfigResolver.getTenantAwareConfig()).thenReturn(tenantAwareConfig); } @SneakyThrows @@ -110,7 +142,7 @@ private ContributorDO getSecondContributor() { @Test public void testRead() { - ProjectService svc = createPersonsService(); + ProjectService svc = this.projectService; ProjectDO expected1 = getFirstProject(); Assertions.assertEquals(expected1, svc.read(expected1.getUniversityId())); @@ -123,7 +155,7 @@ public void testRead() { @Test public void testGetProjectStaff() { - ProjectService svc = createPersonsService(); + ProjectService svc = this.projectService; ProjectDO project1 = getFirstProject(); ContributorDO expectedContributor1 = getFirstContributor(); @@ -142,7 +174,7 @@ public void testGetProjectStaff() { @Test public void testGetProjectLead() { - ProjectService svc = createPersonsService(); + ProjectService svc = this.projectService; ProjectDO project1 = getFirstProject(); ContributorDO expectedContributor1 = getFirstContributor(); @@ -154,14 +186,12 @@ public void testGetProjectLead() { Assertions.assertNull(svc.getProjectLeader("asdf")); } - @Inject PureAPI pureAPI; - - @Inject ProjectService projectService; + @Inject PureAPIFactory pureAPIFactory; @Test public void testWiring() { Assertions.assertNotNull(pureAPI); - Assertions.assertTrue(pureAPI instanceof FileBasedPureAPI); + Assertions.assertTrue(pureAPIFactory.create() instanceof FileBasedPureAPI); Assertions.assertNotNull(projectService); diff --git a/src/test/java/org/damap/base/integration/pure/PureProjectsWireMockIntegrationTest.java b/src/test/java/org/damap/base/integration/pure/PureProjectsWireMockIntegrationTest.java index bdfa921b0..53dc33b32 100644 --- a/src/test/java/org/damap/base/integration/pure/PureProjectsWireMockIntegrationTest.java +++ b/src/test/java/org/damap/base/integration/pure/PureProjectsWireMockIntegrationTest.java @@ -19,7 +19,7 @@ @TestProfile(WireMockPureTestProfile.class) public class PureProjectsWireMockIntegrationTest { - @ConfigProperty(name = "damap.elsevier-pure-api-key") + @ConfigProperty(name = "damap.tenant-aware.elsevier-pure-api-key") String apiKey; private WireMockServer wireMockServer; diff --git a/src/test/java/org/damap/base/integration/pure/PureServicesIntegrationTest.java b/src/test/java/org/damap/base/integration/pure/PureServicesIntegrationTest.java index e0712a795..439fa765c 100644 --- a/src/test/java/org/damap/base/integration/pure/PureServicesIntegrationTest.java +++ b/src/test/java/org/damap/base/integration/pure/PureServicesIntegrationTest.java @@ -17,15 +17,12 @@ public class PureServicesIntegrationTest { @Inject PureAPI pureAPI; - @ConfigProperty(name = "damap.elsevier-pure-endpoint-url") + @ConfigProperty(name = "damap.tenant-aware.elsevier-pure-endpoint-url") String pureEndpointUrl; - @ConfigProperty(name = "damap.elsevier-pure-backend", defaultValue = "http") + @ConfigProperty(name = "damap.tenant-aware.elsevier-pure-backend", defaultValue = "http") String pureBackend; - @ConfigProperty(name = "quarkus.rest-client.elsevier-pure.url") - String restClientUrl; - // Example test UUIDs - replace these with actual project UUIDs from your PURE instance private static final String[] TEST_PROJECT_UUIDS = { "example-uuid-1", "example-uuid-2", "example-uuid-3" @@ -45,7 +42,6 @@ public void testConfigurationAccess() { System.out.println("Profile: " + getConfigProfile()); System.out.println("Backend Type: " + pureBackend); System.out.println("Endpoint URL: " + pureEndpointUrl); - System.out.println("REST Client URL: " + restClientUrl); Assertions.assertNotNull(pureEndpointUrl, "PURE endpoint URL should be configured"); Assertions.assertTrue( diff --git a/src/test/java/org/damap/base/integration/pure/PureTestProfile.java b/src/test/java/org/damap/base/integration/pure/PureTestProfile.java index 70633c0b4..c8a85dcd6 100644 --- a/src/test/java/org/damap/base/integration/pure/PureTestProfile.java +++ b/src/test/java/org/damap/base/integration/pure/PureTestProfile.java @@ -9,34 +9,45 @@ public class PureTestProfile implements QuarkusTestProfile { @Override public Map getConfigOverrides() { HashMap result = new HashMap<>(); - result.put("damap.projects-service", "elsevier-pure"); + result.put("damap.tenant-aware.project-service", "elsevier-pure"); + result.put("damap.tenant-aware.person-services[0].display-text", "Pure"); + result.put("damap.tenant-aware.person-services[0].query-value", "PURE"); result.put( - "damap.person-services", - "[{\"display-text\": \"Pure\", \"query-value\": \"PURE\", \"class-name\": \"org.damap.base.integration.pure.PurePersonService\"}]"); + "damap.tenant-aware.person-services[0].class-name", + "org.damap.base.integration.pure.PurePersonService"); - result.put("damap.elsevier-pure-backend", "file"); + result.put("damap.tenant-aware.elsevier-pure-backend", "file"); result.put( - "damap.elsevier-pure-projects-file", + "damap.tenant-aware.elsevier-pure-projects-file", Resources.getResource("org/damap/base/integration/pure/projects.json").toString()); result.put( - "damap.elsevier-pure-persons-file", + "damap.tenant-aware.elsevier-pure-persons-file", Resources.getResource("org/damap/base/integration/pure/persons.json").toString()); - result.put("damap.pure-endpoint-url", "http://localhost:12345/"); - result.put("damap.elsevier-pure-endpoint-url", "http://localhost:12345/"); - result.put("damap.elsevier-pure-api-key", "test-api-key"); - result.put("quarkus.rest-client.elsevier-pure.url", "http://localhost:12345/"); + result.put("damap.tenant-aware.elsevier-pure-endpoint-url", "http://localhost:12345/"); + result.put("damap.tenant-aware.elsevier-pure-api-key", "test-api-key"); result.put("quarkus.http.test-port", "0"); - result.put("damap.project-service", "org.damap.base.integration.pure.PureProjectService"); result.put( - "damap.elsevier-pure-description-classification", "/dk/atira/pure/projectdescription"); + "damap.tenant-aware.elsevier-pure-description-classification", + "/dk/atira/pure/projectdescription"); result.put( - "damap.elsevier-pure-project-lead-role-classification", "/dk/atira/pure/projectlead"); + "damap.tenant-aware.elsevier-pure-project-lead-role-classification", + "/dk/atira/pure/projectlead"); + + result.put( + "damap.tenant-aware.elsevier-pure-contributor-role-classifications[0].pure-role-uri", + "/dk/atira/pure/member"); + result.put( + "damap.tenant-aware.elsevier-pure-contributor-role-classifications[0].contributor-role", + "PROJECT_MEMBER"); + result.put( + "damap.tenant-aware.elsevier-pure-contributor-role-classifications[1].pure-role-uri", + "/dk/atira/pure/test/projectlead"); result.put( - "damap.elsevier-pure-contributor-role-classifications", - "{\"/dk/atira/pure/member\":\"PROJECT_MEMBER\", \"/dk/atira/pure/test/projectlead\":\"PROJECT_LEADER\"}"); + "damap.tenant-aware.elsevier-pure-contributor-role-classifications[1].contributor-role", + "PROJECT_LEADER"); return result; } diff --git a/src/test/java/org/damap/base/integration/pure/PureWireMockIntegrationTest.java b/src/test/java/org/damap/base/integration/pure/PureWireMockIntegrationTest.java index 508010b94..37552376a 100644 --- a/src/test/java/org/damap/base/integration/pure/PureWireMockIntegrationTest.java +++ b/src/test/java/org/damap/base/integration/pure/PureWireMockIntegrationTest.java @@ -15,7 +15,7 @@ public class PureWireMockIntegrationTest { @Inject PureAPI pureAPI; - @ConfigProperty(name = "damap.elsevier-pure-api-key") + @ConfigProperty(name = "damap.tenant-aware.elsevier-pure-api-key") String apiKey; @Test diff --git a/src/test/java/org/damap/base/integration/pure/WireMockPureTestProfile.java b/src/test/java/org/damap/base/integration/pure/WireMockPureTestProfile.java index 4345573aa..d92c2ca8d 100644 --- a/src/test/java/org/damap/base/integration/pure/WireMockPureTestProfile.java +++ b/src/test/java/org/damap/base/integration/pure/WireMockPureTestProfile.java @@ -21,25 +21,37 @@ public Map getConfigOverrides() { overrides.put("quarkus.hibernate-orm.database.generation", "none"); overrides.put("quarkus.http.test-port", "0"); - overrides.put("damap.projects-service", "elsevier-pure"); - overrides.put("damap.elsevier-pure-backend", "http"); + overrides.put("damap.tenant-aware.project-service", "elsevier-pure"); + overrides.put("damap.tenant-aware.elsevier-pure-backend", "http"); - overrides.put("damap.elsevier-pure-endpoint-url", "http://localhost:8089"); - overrides.put("quarkus.rest-client.elsevier-pure.url", "http://localhost:8089"); - overrides.put("damap.elsevier-pure-api-key", "test-api-key"); + overrides.put("damap.tenant-aware.elsevier-pure-endpoint-url", "http://localhost:8089"); + overrides.put("damap.tenant-aware.elsevier-pure-api-key", "test-api-key"); overrides.put( - "damap.elsevier-pure-contributor-role-classifications", - "{\"/dk/atira/pure/member\":\"PROJECT_MEMBER\", \"/dk/atira/pure/projectlead\":\"PROJECT_LEADER\"}"); + "damap.tenant-aware.elsevier-pure-contributor-role-classifications[0].pure-role-uri", + "/dk/atira/pure/member"); + overrides.put( + "damap.tenant-aware.elsevier-pure-contributor-role-classifications[0].contributor-role", + "PROJECT_MEMBER"); + overrides.put( + "damap.tenant-aware.elsevier-pure-contributor-role-classifications[1].pure-role-uri", + "/dk/atira/pure/test/projectlead"); + overrides.put( + "damap.tenant-aware.elsevier-pure-contributor-role-classifications[1].contributor-role", + "PROJECT_LEADER"); overrides.put( - "damap.elsevier-pure-description-classification", "/dk/atira/pure/projectdescription"); + "damap.tenant-aware.elsevier-pure-description-classification", + "/dk/atira/pure/projectdescription"); overrides.put( - "damap.elsevier-pure-project-lead-role-classification", "/dk/atira/pure/projectlead"); + "damap.tenant-aware.elsevier-pure-project-lead-role-classification", + "/dk/atira/pure/projectlead"); + overrides.put("damap.tenant-aware.person-services[0].display-text", "Pure"); + overrides.put("damap.tenant-aware.person-services[0].query-value", "PURE"); overrides.put( - "damap.person-services", - "[{\"display-text\": \"Pure\", \"query-value\": \"PURE\", \"class-name\": \"org.damap.base.integration.pure.PurePersonService\"}]"); + "damap.tenant-aware.person-services[0].class-name", + "org.damap.base.integration.pure.PurePersonService"); System.out.println("WireMock PURE API configuration"); diff --git a/src/test/java/org/damap/base/service/DmpServiceTest.java b/src/test/java/org/damap/base/service/DmpServiceTest.java index 312f499eb..95cfb1bf4 100644 --- a/src/test/java/org/damap/base/service/DmpServiceTest.java +++ b/src/test/java/org/damap/base/service/DmpServiceTest.java @@ -35,7 +35,7 @@ class DmpServiceTest extends TestSetup { @Inject DmpService dmpService; - @ConfigProperty(name = "damap.projects-service") + @ConfigProperty(name = "damap.tenant-aware.project-service") String activeProfile; @Test