From e830459f1304d815fa86821a303a1558b8b8716b Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Mon, 1 May 2023 17:40:29 +0200
Subject: [PATCH] Added foreign identifier creation, fixed some interceptors in
 the frontend

---
 .../dbrepo-realm.json                         |  60 ++++++-----
 .../tuwien/endpoints/IdentifierEndpoint.java  |  67 ++----------
 .../tuwien/endpoints/PersistenceEndpoint.java | 100 ++++++++++++++++--
 dbrepo-ui/api/database.service.js             |  11 +-
 .../database/_database_id/info.vue            |  42 +++-----
 dbrepo-ui/plugins/axios.js                    |   2 +-
 docker-compose.yml                            |   2 -
 7 files changed, 158 insertions(+), 126 deletions(-)

diff --git a/dbrepo-authentication-service/dbrepo-realm.json b/dbrepo-authentication-service/dbrepo-realm.json
index 0dbcd6bb72..a1171d6bf0 100644
--- a/dbrepo-authentication-service/dbrepo-realm.json
+++ b/dbrepo-authentication-service/dbrepo-realm.json
@@ -206,6 +206,14 @@
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
       "attributes" : { }
+    }, {
+      "id" : "e4cfdc4d-2373-477b-a8df-161db99aba00",
+      "name" : "create-foreign-identifier",
+      "description" : "${create-foreign-identifier}",
+      "composite" : false,
+      "clientRole" : false,
+      "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
+      "attributes" : { }
     }, {
       "id" : "09147c48-273b-450b-8b11-7ef9b9245244",
       "name" : "export-table-data",
@@ -341,7 +349,7 @@
       "description" : "${escalated-identifier-handling}",
       "composite" : true,
       "composites" : {
-        "realm" : [ "delete-identifier", "modify-identifier-metadata" ]
+        "realm" : [ "delete-identifier", "create-foreign-identifier", "modify-identifier-metadata" ]
       },
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
@@ -900,7 +908,7 @@
   "otpPolicyLookAheadWindow" : 1,
   "otpPolicyPeriod" : 30,
   "otpPolicyCodeReusable" : false,
-  "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName", "totpAppFreeOTPName" ],
+  "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ],
   "webAuthnPolicyRpEntityName" : "keycloak",
   "webAuthnPolicySignatureAlgorithms" : [ "ES256" ],
   "webAuthnPolicyRpId" : "",
@@ -1890,7 +1898,7 @@
       "subType" : "authenticated",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper" ]
+        "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper" ]
       }
     }, {
       "id" : "3ab11d74-5e76-408a-b85a-26bf8950f979",
@@ -1899,7 +1907,7 @@
       "subType" : "anonymous",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper" ]
+        "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper" ]
       }
     } ],
     "org.keycloak.keys.KeyProvider" : [ {
@@ -1951,7 +1959,7 @@
   "internationalizationEnabled" : false,
   "supportedLocales" : [ ],
   "authenticationFlows" : [ {
-    "id" : "2659228d-907e-4832-9478-93c1537361ad",
+    "id" : "76e548b4-1eab-41b5-9980-34d95e193a54",
     "alias" : "Account verification options",
     "description" : "Method with which to verity the existing account",
     "providerId" : "basic-flow",
@@ -1973,7 +1981,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "6f2ebe37-d1c9-4359-8516-05f7c435a09c",
+    "id" : "d77e95ec-b316-4edd-ab32-b2e28e6654fb",
     "alias" : "Authentication Options",
     "description" : "Authentication options.",
     "providerId" : "basic-flow",
@@ -2002,7 +2010,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "e8ecd6b9-5991-4a84-b52d-bc9961d05f9a",
+    "id" : "70249f64-bac3-4792-9bd3-87ed99c3e21c",
     "alias" : "Browser - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2024,7 +2032,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "91815128-e40c-4800-946d-09d2f33a1f39",
+    "id" : "64571c59-51eb-4d5f-9c7d-fa70460d52b1",
     "alias" : "Direct Grant - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2046,7 +2054,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "90383773-dec6-4dc3-a6ff-5dfdde0ecda5",
+    "id" : "397d32d5-5d22-4d18-9c09-f5642ed68d83",
     "alias" : "First broker login - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2068,7 +2076,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "ae5a2e94-c9b0-4851-b88e-4b0acacb645f",
+    "id" : "eab19975-c52a-449d-99a1-284f850e0853",
     "alias" : "Handle Existing Account",
     "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider",
     "providerId" : "basic-flow",
@@ -2090,7 +2098,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "a754b5ae-bc9d-4a98-ad59-6cc8ff27d016",
+    "id" : "e829aa33-e7d9-41fd-97c0-cdf178f6e055",
     "alias" : "Reset - Conditional OTP",
     "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
     "providerId" : "basic-flow",
@@ -2112,7 +2120,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "bdf46041-56db-445a-b4f3-3d000b239b8b",
+    "id" : "e39097e1-653c-4bf0-8f1f-1513eb3da432",
     "alias" : "User creation or linking",
     "description" : "Flow for the existing/non-existing user alternatives",
     "providerId" : "basic-flow",
@@ -2135,7 +2143,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "1915f7a6-19a4-4e54-8010-73c939f18afe",
+    "id" : "7acd4e52-1c6d-43a0-bcba-c0cbccd7ef34",
     "alias" : "Verify Existing Account by Re-authentication",
     "description" : "Reauthentication of existing account",
     "providerId" : "basic-flow",
@@ -2157,7 +2165,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "b3d8705b-5235-43da-89c6-ce55fb304ae5",
+    "id" : "4569b4fd-6b88-42f0-9f47-f7d95cb36b0d",
     "alias" : "browser",
     "description" : "browser based authentication",
     "providerId" : "basic-flow",
@@ -2193,7 +2201,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "33efb232-46c9-4482-ba8e-7e452668bafa",
+    "id" : "ceb5f811-8b55-47e6-8475-978ce5ae2572",
     "alias" : "clients",
     "description" : "Base authentication for clients",
     "providerId" : "client-flow",
@@ -2229,7 +2237,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "f94e82fa-882d-4952-88c9-3aa37d9be1b6",
+    "id" : "d53784bc-facd-4261-a283-d76dece59feb",
     "alias" : "direct grant",
     "description" : "OpenID Connect Resource Owner Grant",
     "providerId" : "basic-flow",
@@ -2258,7 +2266,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "15948c95-df50-4304-8695-f586e5351979",
+    "id" : "db59555a-b8b5-4af9-81d5-e235131f81b6",
     "alias" : "docker auth",
     "description" : "Used by Docker clients to authenticate against the IDP",
     "providerId" : "basic-flow",
@@ -2273,7 +2281,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "1983ebae-21b2-4a31-8517-c7a4746fedeb",
+    "id" : "a69ee61f-db83-43e9-adb6-9644841e1702",
     "alias" : "first broker login",
     "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
     "providerId" : "basic-flow",
@@ -2296,7 +2304,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "f9893ead-c7a1-404b-aca2-d24e03eb5c16",
+    "id" : "974f47f5-fefb-4561-ab5d-08836d9d8ef2",
     "alias" : "forms",
     "description" : "Username, password, otp and other auth forms.",
     "providerId" : "basic-flow",
@@ -2318,7 +2326,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "d830402e-475d-46b5-a9d7-1105436d9092",
+    "id" : "68b01c05-9928-4752-82fd-0f5474090a48",
     "alias" : "http challenge",
     "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes",
     "providerId" : "basic-flow",
@@ -2340,7 +2348,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "0de6040b-5167-4ab1-8e40-f633419b5890",
+    "id" : "ae8b1165-bad6-4b1c-8c72-a987263ed955",
     "alias" : "registration",
     "description" : "registration flow",
     "providerId" : "basic-flow",
@@ -2356,7 +2364,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "84a658d1-d9ca-4090-9a86-9d45844324e2",
+    "id" : "11d691cd-d529-4b45-97ca-de065d189c76",
     "alias" : "registration form",
     "description" : "registration form",
     "providerId" : "form-flow",
@@ -2392,7 +2400,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "9cc594f8-9bb0-4557-bffb-4a66b2eb0f34",
+    "id" : "8e0a9ab1-45bf-401b-b67d-b303e7a6c667",
     "alias" : "reset credentials",
     "description" : "Reset credentials for a user if they forgot their password or something",
     "providerId" : "basic-flow",
@@ -2428,7 +2436,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "d2b1442c-c5db-4e3f-94f6-48196fccd207",
+    "id" : "786a56e6-d5e5-4609-9dc1-712acafb7380",
     "alias" : "saml ecp",
     "description" : "SAML ECP Profile Authentication Flow",
     "providerId" : "basic-flow",
@@ -2444,13 +2452,13 @@
     } ]
   } ],
   "authenticatorConfig" : [ {
-    "id" : "aebc30c4-79b7-47c5-8d13-f4cfe66aba10",
+    "id" : "f82d7f9e-a21b-4a71-8c9c-908491ce2ca5",
     "alias" : "create unique user config",
     "config" : {
       "require.password.update.after.registration" : "false"
     }
   }, {
-    "id" : "5c00357d-8701-4848-a920-0ae1db86fdd0",
+    "id" : "6706f2cc-33ab-43b1-b1cd-1fd6caafc464",
     "alias" : "review profile config",
     "config" : {
       "update.profile.on.first.login" : "missing"
diff --git a/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
index eb7f335e0e..8058288729 100644
--- a/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
+++ b/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
@@ -4,6 +4,7 @@ import at.tuwien.api.error.ApiErrorDto;
 import at.tuwien.api.identifier.IdentifierCreateDto;
 import at.tuwien.api.identifier.IdentifierDto;
 import at.tuwien.api.identifier.IdentifierTypeDto;
+import at.tuwien.entities.database.AccessType;
 import at.tuwien.entities.database.DatabaseAccess;
 import at.tuwien.entities.identifier.Identifier;
 import at.tuwien.entities.user.User;
@@ -91,7 +92,7 @@ public class IdentifierEndpoint {
     @PostMapping
     @Transactional
     @Timed(value = "identifier.create", description = "Time needed to create an identifier")
-    @PreAuthorize("hasAuthority('create-identifier')")
+    @PreAuthorize("hasAuthority('create-identifier') or hasAuthority('create-foreign-identifier')")
     @Operation(summary = "Create identifier", security = @SecurityRequirement(name = "bearerAuth"))
     @ApiResponses(value = {
             @ApiResponse(responseCode = "201",
@@ -104,8 +105,8 @@ public class IdentifierEndpoint {
                     content = {@Content(
                             mediaType = "application/json",
                             schema = @Schema(implementation = ApiErrorDto.class))}),
-            @ApiResponse(responseCode = "404",
-                    description = "Query, database or user could not be found",
+            @ApiResponse(responseCode = "403",
+                    description = "Insufficient access rights or authorities",
                     content = {@Content(
                             mediaType = "application/json",
                             schema = @Schema(implementation = ApiErrorDto.class))}),
@@ -134,7 +135,7 @@ public class IdentifierEndpoint {
                                                 @NotNull @RequestHeader(name = "Authorization") String authorization,
                                                 @NotNull Principal principal)
             throws IdentifierAlreadyExistsException, QueryNotFoundException, IdentifierPublishingNotAllowedException,
-            RemoteUnavailableException, UserNotFoundException, DatabaseNotFoundException, IdentifierRequestException, AccessDeniedException {
+            RemoteUnavailableException, UserNotFoundException, DatabaseNotFoundException, IdentifierRequestException, AccessDeniedException, NotAllowedException {
         log.debug("endpoint create identifier, data={}, authorization=(hidden), principal={}", data, principal);
         if (data.getType().equals(IdentifierTypeDto.SUBSET) && data.getQid() == null) {
             log.error("Identifier of type subset need to have a qid present");
@@ -145,62 +146,12 @@ public class IdentifierEndpoint {
         }
         final User user = userService.findByUsername(principal.getName());
         final DatabaseAccess access = accessService.find(data.getDbid(), user.getId());
+        if (!User.hasRole(principal, "create-foreign-identifier") && access.getType().equals(AccessType.READ)) {
+            log.error("Failed to create identifier: insufficient access");
+            throw new NotAllowedException("Failed to create identifier: insufficient access");
+        }
         final Identifier identifier = identifierService.create(data, principal, authorization);
         return ResponseEntity.status(HttpStatus.CREATED)
                 .body(identifierMapper.identifierToIdentifierDto(identifier));
     }
-
-    @PutMapping("/{id}")
-    @Transactional
-    @Timed(value = "identifier.update", description = "Time needed to update an identifier")
-    @PreAuthorize("hasAuthority('update-identifier')")
-    @Operation(summary = "Update some identifier", security = @SecurityRequirement(name = "bearerAuth"))
-    @ApiResponses(value = {
-            @ApiResponse(responseCode = "202",
-                    description = "Updated identifier",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = IdentifierDto.class))}),
-            @ApiResponse(responseCode = "404",
-                    description = "Identifier could not be found",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
-            @ApiResponse(responseCode = "406",
-                    description = "Updating identifier not allowed",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
-    })
-    public ResponseEntity<IdentifierDto> update(@NotNull @PathVariable("id") Long id,
-                                                @NotNull @Valid @RequestBody IdentifierDto data)
-            throws IdentifierNotFoundException, IdentifierRequestException {
-        log.debug("endpoint update identifier, id={}, data={}", id, data);
-        final Identifier identifier = identifierService.update(id, data);
-        return ResponseEntity.accepted()
-                .body(identifierMapper.identifierToIdentifierDto(identifier));
-    }
-
-    @DeleteMapping("/{id}")
-    @Transactional
-    @Timed(value = "identifier.delete", description = "Time needed to delete an identifier")
-    @PreAuthorize("hasAuthority('delete-identifier')")
-    @Operation(summary = "Delete some identifier", security = @SecurityRequirement(name = "bearerAuth"))
-    @ApiResponses(value = {
-            @ApiResponse(responseCode = "202",
-                    description = "Deleted identifier",
-                    content = {@Content}),
-            @ApiResponse(responseCode = "404",
-                    description = "Identifier could not be found",
-                    content = {@Content(
-                            mediaType = "application/json",
-                            schema = @Schema(implementation = ApiErrorDto.class))}),
-    })
-    public ResponseEntity<?> delete(@NotNull @PathVariable("id") Long id)
-            throws IdentifierNotFoundException, NotAllowedException {
-        log.debug("endpoint delete identifier, id={}", id);
-        identifierService.delete(id);
-        return ResponseEntity.accepted()
-                .build();
-    }
 }
diff --git a/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java b/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java
index 602ae69e09..c25aa430ce 100644
--- a/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java
+++ b/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java
@@ -4,19 +4,22 @@ import at.tuwien.api.error.ApiErrorDto;
 import at.tuwien.api.identifier.BibliographyTypeDto;
 import at.tuwien.api.identifier.IdentifierDto;
 import at.tuwien.config.EndpointConfig;
+import at.tuwien.entities.database.AccessType;
+import at.tuwien.entities.database.DatabaseAccess;
 import at.tuwien.entities.identifier.Identifier;
-import at.tuwien.exception.IdentifierNotFoundException;
-import at.tuwien.exception.IdentifierRequestException;
-import at.tuwien.exception.QueryNotFoundException;
-import at.tuwien.exception.RemoteUnavailableException;
+import at.tuwien.entities.user.User;
+import at.tuwien.exception.*;
 import at.tuwien.mapper.IdentifierMapper;
+import at.tuwien.service.AccessService;
 import at.tuwien.service.IdentifierService;
+import at.tuwien.service.UserService;
 import io.micrometer.core.annotation.Timed;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.io.InputStreamResource;
@@ -28,6 +31,8 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.security.Principal;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -37,13 +42,17 @@ import java.util.regex.Pattern;
 @RequestMapping("/api/pid")
 public class PersistenceEndpoint {
 
+    private final UserService userService;
+    private final AccessService accessService;
     private final EndpointConfig endpointConfig;
     private final IdentifierMapper identifierMapper;
     private final IdentifierService identifierService;
 
     @Autowired
-    public PersistenceEndpoint(EndpointConfig endpointConfig, IdentifierMapper identifierMapper,
-                               IdentifierService identifierService) {
+    public PersistenceEndpoint(UserService userService, AccessService accessService, EndpointConfig endpointConfig,
+                               IdentifierMapper identifierMapper, IdentifierService identifierService) {
+        this.userService = userService;
+        this.accessService = accessService;
         this.endpointConfig = endpointConfig;
         this.identifierMapper = identifierMapper;
         this.identifierService = identifierService;
@@ -141,4 +150,83 @@ public class PersistenceEndpoint {
                 .build();
     }
 
+    @PutMapping("/{id}")
+    @Transactional
+    @Timed(value = "identifier.update", description = "Time needed to update an identifier")
+    @PreAuthorize("hasAuthority('update-identifier') or hasAuthority('create-foreign-identifier')")
+    @Operation(summary = "Update some identifier", security = @SecurityRequirement(name = "bearerAuth"))
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "202",
+                    description = "Updated identifier",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = IdentifierDto.class))}),
+            @ApiResponse(responseCode = "400",
+                    description = "Identifier data is not valid to the form",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "404",
+                    description = "Identifier or user could not be found",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "405",
+                    description = "Updating identifier not permitted",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "406",
+                    description = "Updating identifier not allowed",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+    })
+    public ResponseEntity<IdentifierDto> update(@NotNull @PathVariable("id") Long id,
+                                                @NotNull @Valid @RequestBody IdentifierDto data,
+                                                @NotNull Principal principal)
+            throws IdentifierNotFoundException, IdentifierRequestException, UserNotFoundException, NotAllowedException,
+            AccessDeniedException {
+        log.debug("endpoint update identifier, id={}, data={}", id, data);
+        final Identifier identifier = identifierService.find(id);
+        final User user = userService.findByUsername(principal.getName());
+        final DatabaseAccess access = accessService.find(identifier.getDatabaseId(), user.getId());
+        if (!User.hasRole(principal, "create-foreign-identifier") && access.getType().equals(AccessType.READ)) {
+            log.error("Failed to create identifier: insufficient access");
+            throw new NotAllowedException("Failed to create identifier: insufficient access");
+        }
+        final IdentifierDto dto = identifierMapper.identifierToIdentifierDto(identifierService.update(id, data));
+        log.debug("update identifier resulted in dto={}", dto);
+        return ResponseEntity.accepted()
+                .body(dto);
+    }
+
+    @DeleteMapping("/{id}")
+    @Transactional
+    @Timed(value = "identifier.delete", description = "Time needed to delete an identifier")
+    @PreAuthorize("hasAuthority('delete-identifier')")
+    @Operation(summary = "Delete some identifier", security = @SecurityRequirement(name = "bearerAuth"))
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "202",
+                    description = "Deleted identifier",
+                    content = {@Content}),
+            @ApiResponse(responseCode = "404",
+                    description = "Identifier could not be found",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))}),
+            @ApiResponse(responseCode = "405",
+                    description = "Deleting identifier not permitted",
+                    content = {@Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ApiErrorDto.class))})
+    })
+    public ResponseEntity<?> delete(@NotNull @PathVariable("id") Long id)
+            throws IdentifierNotFoundException, NotAllowedException {
+        log.debug("endpoint delete identifier, id={}", id);
+        identifierService.delete(id);
+        return ResponseEntity.accepted()
+                .build();
+    }
+
 }
diff --git a/dbrepo-ui/api/database.service.js b/dbrepo-ui/api/database.service.js
index 50aefa4251..fdbaf1ef94 100644
--- a/dbrepo-ui/api/database.service.js
+++ b/dbrepo-ui/api/database.service.js
@@ -92,10 +92,13 @@ class DatabaseService {
           resolve(databases)
         })
         .catch((error) => {
-          const { code, message } = error
-          console.error('Failed to check database access', error)
-          Vue.$toast.error(`[${code}] Failed to check database access: ${message}`)
-          reject(error)
+          const { code, message, response } = error
+          const { status } = response
+          if (status !== 403 && status !== 405) { /* ignore no access errors */
+            console.error('Failed to check database access', error)
+            Vue.$toast.error(`[${code}] Failed to check database access: ${message}`)
+            reject(error)
+          }
         })
     })
   }
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/info.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/info.vue
index dd4a71e482..62a3aadb51 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/info.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/info.vue
@@ -231,6 +231,7 @@ import Citation from '@/components/identifier/Citation'
 import { formatTimestampUTCLabel } from '@/utils'
 import Banner from '@/components/identifier/Banner'
 import DatabaseMapper from '@/api/database.mapper'
+import IdentifierService from '@/api/identifier.service'
 
 export default {
   components: {
@@ -320,7 +321,7 @@ export default {
       if (!this.user) {
         return false
       }
-      return this.canCreateIdentifier || this.canDeleteIdentifier || this.roles.includes('modify-identifier-metadata')
+      return this.canCreateIdentifier || this.hasIdentifier
     },
     canCreateIdentifier () {
       if (!this.roles) {
@@ -329,7 +330,7 @@ export default {
       if (this.hasIdentifier) {
         return false
       }
-      return this.roles.includes('create-identifier')
+      return this.roles.includes('create-identifier') || this.roles.includes('create-foreign-identifier')
     },
     canDeleteIdentifier () {
       if (!this.user) {
@@ -385,37 +386,20 @@ export default {
       this.editDbDialog = false
       this.editVisibilityDialog = false
     },
-    async deleteIdentifier () {
+    deleteIdentifier () {
       if (!this.database.identifier.id) {
         return
       }
       this.loadingDelete = true
-      try {
-        await this.$axios.delete(`/api/identifier/${this.database.identifier.id}`, this.config)
-        console.info('Deleted identifier with id ', this.database.identifier.id)
-        this.$toast.success('Successfully deleted identifier with id ' + this.database.identifier.id)
-        await this.loadDatabase()
-      } catch (error) {
-        const { message } = error.response
-        console.error('Failed to delete identifier', error)
-        this.$toast.error('Failed to delete identifier: ' + message)
-      }
-      this.loadingDelete = false
-    },
-    async loadDatabase () {
-      if (!this.$route.params.container_id || !this.$route.params.database_id) {
-        return
-      }
-      try {
-        this.loading = true
-        const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}`, this.config)
-        this.$store.commit('SET_DATABASE', res.data)
-        console.debug('database', this.database)
-      } catch (err) {
-        console.error('Could not load database', err)
-        this.$toast.error('Could not load database')
-      }
-      this.loading = false
+      IdentifierService.delete(this.database.identifier.id)
+        .then(async () => {
+          console.info('Deleted identifier with id ', this.database.identifier.id)
+          this.$toast.success('Successfully deleted identifier with id ' + this.database.identifier.id)
+          await this.$store.dispatch('reloadDatabase')
+        })
+        .finally(() => {
+          this.loadingDelete = false
+        })
     }
   }
 }
diff --git a/dbrepo-ui/plugins/axios.js b/dbrepo-ui/plugins/axios.js
index bdfd8d1aca..8f11563b46 100644
--- a/dbrepo-ui/plugins/axios.js
+++ b/dbrepo-ui/plugins/axios.js
@@ -26,7 +26,7 @@ api.interceptors.request.use((config) => {
         return config
       })
   }
-  console.debug('interceptor inject authorization header')
+  console.debug('interceptor inject authorization header for url', config.url)
   config.headers.Authorization = `Bearer ${token}`
   return config
 })
diff --git a/docker-compose.yml b/docker-compose.yml
index 6f1c13cbb5..f4364d1bb4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -213,8 +213,6 @@ services:
       - "9096:9096"
     env_file:
       - .env
-    environment:
-      - SPRING_PROFILES_ACTIVE=doi
     depends_on:
       dbrepo-query-service:
         condition: service_healthy
-- 
GitLab