From 3e477c84d88771e67f2e80abd4c5b065d8c98c33 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Wed, 3 May 2023 17:39:57 +0200 Subject: [PATCH] Added groups and update + delete Pid works --- .../dbrepo-realm.json | 108 ++++++++---------- .../tuwien/endpoints/PersistenceEndpoint.java | 2 +- .../components/dialogs/DeleteIdentifier.vue | 89 ++++++++------- dbrepo-ui/components/dialogs/Persist.vue | 97 ++++++++++------ .../database/_database_id/info.vue | 4 +- dbrepo-ui/utils/index.js | 19 ++- 6 files changed, 177 insertions(+), 142 deletions(-) diff --git a/dbrepo-authentication-service/dbrepo-realm.json b/dbrepo-authentication-service/dbrepo-realm.json index ea9bf881a2..99be5195d4 100644 --- a/dbrepo-authentication-service/dbrepo-realm.json +++ b/dbrepo-authentication-service/dbrepo-realm.json @@ -288,10 +288,7 @@ "id" : "abd2d9ee-ebc4-4d0a-839e-6b588a6d442a", "name" : "default-roles-dbrepo", "description" : "${role_default-roles}", - "composite" : true, - "composites" : { - "realm" : [ "default-researcher-roles" ] - }, + "composite" : false, "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0", "attributes" : { } @@ -852,54 +849,39 @@ } }, "groups" : [ { - "id" : "16c0fda1-864b-4c27-8755-0fdffa577000", - "name" : "External", - "path" : "/External", + "id" : "f2ce17fe-7b15-47a4-bbf8-86f415298fa9", + "name" : "data-stewards", + "path" : "/data-stewards", "attributes" : { }, - "realmRoles" : [ ], + "realmRoles" : [ "default-data-steward-roles" ], "clientRoles" : { }, "subGroups" : [ ] }, { - "id" : "1d8e6a45-1c77-453b-a5a8-9096e81e8b9b", - "name" : "Internal", - "path" : "/Internal", + "id" : "124d9888-0b6e-46aa-8225-077dcedaf16e", + "name" : "developers", + "path" : "/developers", "attributes" : { }, - "realmRoles" : [ ], + "realmRoles" : [ "default-developer-roles" ], "clientRoles" : { }, - "subGroups" : [ { - "id" : "7fe5a587-d2bc-4d3d-980b-324c3336862c", - "name" : "Developers", - "path" : "/Internal/Developers", - "attributes" : { }, - "realmRoles" : [ ], - "clientRoles" : { }, - "subGroups" : [ ] - }, { - "id" : "cc357d61-bfbf-4ed7-93d3-122113f438e3", - "name" : "Researchers", - "path" : "/Internal/Researchers", - "attributes" : { }, - "realmRoles" : [ ], - "clientRoles" : { }, - "subGroups" : [ ] - }, { - "id" : "c33f23e6-f7d0-4dee-9af4-f68773bad280", - "name" : "Data Stewards", - "path" : "/Internal/Data Stewards", - "attributes" : { }, - "realmRoles" : [ ], - "clientRoles" : { }, - "subGroups" : [ ] - } ] + "subGroups" : [ ] + }, { + "id" : "f467c38e-9041-4faa-ae0b-39cec65ff4db", + "name" : "researchers", + "path" : "/researchers", + "attributes" : { }, + "realmRoles" : [ "default-researcher-roles" ], + "clientRoles" : { }, + "subGroups" : [ ] } ], "defaultRole" : { "id" : "abd2d9ee-ebc4-4d0a-839e-6b588a6d442a", "name" : "default-roles-dbrepo", "description" : "${role_default-roles}", - "composite" : true, + "composite" : false, "clientRole" : false, "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0" }, + "defaultGroups" : [ "/researchers" ], "requiredCredentials" : [ "password" ], "otpPolicyType" : "totp", "otpPolicyAlgorithm" : "HmacSHA1", @@ -908,7 +890,7 @@ "otpPolicyLookAheadWindow" : 1, "otpPolicyPeriod" : 30, "otpPolicyCodeReusable" : false, - "otpSupportedApplications" : [ "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName", "totpAppFreeOTPName" ], + "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName" ], "webAuthnPolicyRpEntityName" : "keycloak", "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], "webAuthnPolicyRpId" : "", @@ -1898,7 +1880,7 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper" ] } }, { "id" : "3ab11d74-5e76-408a-b85a-26bf8950f979", @@ -1907,7 +1889,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-full-name-mapper" ] } } ], "org.keycloak.keys.KeyProvider" : [ { @@ -1959,7 +1941,7 @@ "internationalizationEnabled" : false, "supportedLocales" : [ ], "authenticationFlows" : [ { - "id" : "901d9a10-19af-4e4a-9247-0f599f13e7fb", + "id" : "e29d33f1-c44c-4f6b-b8ca-54eb4b468f1d", "alias" : "Account verification options", "description" : "Method with which to verity the existing account", "providerId" : "basic-flow", @@ -1981,7 +1963,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "2b17a070-b094-4b0a-a2a9-3a2af949a21e", + "id" : "bc4badef-d637-4448-b345-3e79f24290b0", "alias" : "Authentication Options", "description" : "Authentication options.", "providerId" : "basic-flow", @@ -2010,7 +1992,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "de7bed52-8c78-48d5-a4b3-93932613c102", + "id" : "06ddc635-bb0d-443a-ab6b-7f56be7fd59a", "alias" : "Browser - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -2032,7 +2014,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "9fdcbe8c-2a29-4d72-a4a2-1c78ce40ff82", + "id" : "a8230646-a4a8-40c2-a37a-e7ac4daf9a67", "alias" : "Direct Grant - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -2054,7 +2036,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "38898847-37a8-4e99-a6bf-fba242bec088", + "id" : "e84a6624-232f-401e-9937-6c5865b2a341", "alias" : "First broker login - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -2076,7 +2058,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "a6130e47-dc21-47e0-ab72-b1ebe2797c01", + "id" : "00094f7f-cdcb-4670-87b0-d75ed3fa986b", "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", @@ -2098,7 +2080,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "f6480693-ec91-4bef-8822-64c43dc819e0", + "id" : "e8cea56d-d07c-4696-b389-0f70449ba26d", "alias" : "Reset - Conditional OTP", "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId" : "basic-flow", @@ -2120,7 +2102,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "6d825b60-c203-4366-811c-7afdfd9a55f5", + "id" : "505a65be-a4f8-4e6e-a1a0-21d20d3f07a5", "alias" : "User creation or linking", "description" : "Flow for the existing/non-existing user alternatives", "providerId" : "basic-flow", @@ -2143,7 +2125,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "6c64cf67-ac2c-436d-bb86-01dcdf6cd720", + "id" : "9c31dad3-3367-4f7e-9fd0-3c855e1da0b8", "alias" : "Verify Existing Account by Re-authentication", "description" : "Reauthentication of existing account", "providerId" : "basic-flow", @@ -2165,7 +2147,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "c6f6b53d-d316-4232-ba0f-5a23af03d684", + "id" : "e6fdd307-ca87-43f0-9f74-2bc567cc02db", "alias" : "browser", "description" : "browser based authentication", "providerId" : "basic-flow", @@ -2201,7 +2183,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "3fe7bf4c-56da-4751-a1bd-1cd88a5166b4", + "id" : "b42d375b-1cb9-4cc7-be7d-fae32791d0a8", "alias" : "clients", "description" : "Base authentication for clients", "providerId" : "client-flow", @@ -2237,7 +2219,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "6800db15-413f-4cd1-b97e-21aada87160a", + "id" : "3c534feb-1736-4ff4-8187-192e49f21fd9", "alias" : "direct grant", "description" : "OpenID Connect Resource Owner Grant", "providerId" : "basic-flow", @@ -2266,7 +2248,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "3a324752-6a3e-4ac5-bb2e-740a92a1b39f", + "id" : "5f3c06ca-8691-4c6c-8892-8d29563da9d0", "alias" : "docker auth", "description" : "Used by Docker clients to authenticate against the IDP", "providerId" : "basic-flow", @@ -2281,7 +2263,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "fac438f8-81be-4abf-b47f-42f9a7d6a5f3", + "id" : "76abf3cd-3635-494e-b75d-a3132a7933a5", "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", @@ -2304,7 +2286,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "4fe02d11-0e01-4b0a-8822-893ad191c903", + "id" : "d44f3e97-d11a-49ba-a3bf-7c8489de290d", "alias" : "forms", "description" : "Username, password, otp and other auth forms.", "providerId" : "basic-flow", @@ -2326,7 +2308,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "5ca6cd7c-5bb8-4674-9946-1ed07833a4c2", + "id" : "7e60e213-6272-484c-b620-d7cf4f98f950", "alias" : "http challenge", "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId" : "basic-flow", @@ -2348,7 +2330,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "29bf7f84-ed23-4a3f-acf3-bb75a560ddbe", + "id" : "cce9e0e9-82f4-499d-a6f7-fc556248fc25", "alias" : "registration", "description" : "registration flow", "providerId" : "basic-flow", @@ -2364,7 +2346,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "39b44736-f873-42c7-ace8-1d6dc598f0e4", + "id" : "61f08b26-e87b-4126-ac7a-c704d01e54dc", "alias" : "registration form", "description" : "registration form", "providerId" : "form-flow", @@ -2400,7 +2382,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "23b1886e-4fff-4616-b579-12aded1625b2", + "id" : "d9ce077f-16c2-4c96-aba5-dd60bb3aa536", "alias" : "reset credentials", "description" : "Reset credentials for a user if they forgot their password or something", "providerId" : "basic-flow", @@ -2436,7 +2418,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "dab34786-6f6f-4c06-864e-415d53b60b02", + "id" : "8d4383f0-ebd2-4e0b-8d3f-2c6e1793c05b", "alias" : "saml ecp", "description" : "SAML ECP Profile Authentication Flow", "providerId" : "basic-flow", @@ -2452,13 +2434,13 @@ } ] } ], "authenticatorConfig" : [ { - "id" : "599f9aae-4d0d-44ea-9862-2ce3df15afd4", + "id" : "1206bd7a-0525-4e21-9bc7-d9ec62c1a4bc", "alias" : "create unique user config", "config" : { "require.password.update.after.registration" : "false" } }, { - "id" : "fedc59c5-7dea-4a40-b605-e0c48c389b6b", + "id" : "f5ff277d-b059-4dff-aaf8-8c5feeca1d4c", "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/PersistenceEndpoint.java b/dbrepo-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java index dd1ab8510f..6f30f79be8 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 @@ -191,7 +191,7 @@ public class PersistenceEndpoint { try { accessService.find(identifier.getDatabaseId(), user.getId()); } catch (AccessDeniedException e) { - if (!User.hasRole(principal, "update-foreign-identifier")) { + if (!User.hasRole(principal, "modify-identifier-metadata")) { log.error("Failed to update identifier: insufficient access"); throw new NotAllowedException("Failed to update identifier: insufficient access"); } diff --git a/dbrepo-ui/components/dialogs/DeleteIdentifier.vue b/dbrepo-ui/components/dialogs/DeleteIdentifier.vue index 85f0f50795..373f2b86c6 100644 --- a/dbrepo-ui/components/dialogs/DeleteIdentifier.vue +++ b/dbrepo-ui/components/dialogs/DeleteIdentifier.vue @@ -1,42 +1,44 @@ <template> <div> - <v-card> - <v-card-title v-text="title" /> - <v-card-text> - <v-row dense> - <v-col> - This action cannot be undone! Type the identifier <strong>{{ confirmText }}</strong> below if you really want to delete it. - </v-col> - </v-row> - <v-row dense> - <v-col> - <v-text-field - id="confirm" - v-model="confirm" - name="confirm" - label="Identifier *" - autofocus - required /> - </v-col> - </v-row> - </v-card-text> - <v-card-actions> - <v-spacer /> - <v-btn - class="mb-2" - @click="cancel"> - Cancel - </v-btn> - <v-btn - class="mb-2 mr-1" - color="error" - :loading="loadingDelete" - :disabled="confirm !== confirmText" - @click="deleteIdentifier"> - Delete - </v-btn> - </v-card-actions> - </v-card> + <v-form ref="form" v-model="valid" autocomplete="off" @submit.prevent="submit"> + <v-card> + <v-card-title v-text="title" /> + <v-card-text> + <v-row dense> + <v-col> + This action cannot be undone! Type the identifier <strong>{{ confirmText }}</strong> below if you really want to delete it. + </v-col> + </v-row> + <v-row dense> + <v-col> + <v-text-field + id="confirm" + v-model="confirm" + name="confirm" + label="Identifier *" + autofocus + required /> + </v-col> + </v-row> + </v-card-text> + <v-card-actions> + <v-spacer /> + <v-btn + class="mb-2" + @click="cancel"> + Cancel + </v-btn> + <v-btn + class="mb-2 mr-1" + color="error" + :loading="loadingDelete" + :disabled="confirm !== confirmText" + @click="deleteIdentifier"> + Delete + </v-btn> + </v-card-actions> + </v-card> + </v-form> </div> </template> @@ -55,17 +57,24 @@ export default { data () { return { confirm: null, - loadingDelete: false + loadingDelete: false, + valid: false } }, computed: { title () { + if (!this.identifier || !('doi' in this.identifier)) { + return null + } if (this.identifier.doi) { return `DOI ${this.identifier.doi}` } return `Identifier with id ${this.identifier.id}` }, confirmText () { + if (!this.identifier || !('doi' in this.identifier)) { + return null + } if (this.identifier.doi) { return this.identifier.doi } @@ -73,8 +82,10 @@ export default { } }, methods: { + submit () { + this.$refs.form.validate() + }, cancel () { - this.$parent.$parent.$parent.persistQueryDialog = false this.$emit('close', { action: 'closed' }) }, deleteIdentifier () { diff --git a/dbrepo-ui/components/dialogs/Persist.vue b/dbrepo-ui/components/dialogs/Persist.vue index 9eceb29062..704a8b342c 100644 --- a/dbrepo-ui/components/dialogs/Persist.vue +++ b/dbrepo-ui/components/dialogs/Persist.vue @@ -16,6 +16,7 @@ id="title" v-model="identifier.title" name="title" + autofocus :label="`${prefix} title *`" :rules="[v => !!v || $t('Required')]" required /> @@ -92,7 +93,8 @@ <v-text-field v-model="creator.orcid" name="orcid" - label="ORCID" /> + label="ORCID" + :rules="[v => !validateOrcidInput(v) || $t('Please remove the https:// part')]" /> </v-col> <v-col v-if="i > 0" cols="1" class="mt-5"> <v-btn icon x-small @click="deleteCreator(i)"> @@ -193,6 +195,7 @@ import { formatYearUTC, formatMonthUTC, formatDayUTC } from '@/utils' import IdentifierService from '@/api/identifier.service' import DatabaseService from '@/api/database.service' + export default { props: { type: { @@ -212,7 +215,28 @@ export default { loading: false, error: false, // XXX: `error` is never changed licenses: [], - identifier: {}, + identifier: { + cid: parseInt(this.$route.params.container_id), + dbid: parseInt(this.$route.params.database_id), + qid: parseInt(this.$route.params.query_id), + title: null, + description: null, + publisher: this.$config.defaultPublisher, + publication_year: formatYearUTC(Date.now()), + publication_month: formatMonthUTC(Date.now()), + publication_day: formatDayUTC(Date.now()), + license: null, + type: this.type, + creators: [ + { + firstname: null, + lastname: null, + affiliation: null, + orcid: null + } + ], + related_identifiers: [] + }, relatedTypes: [ { value: 'DOI' }, { value: 'URL' }, @@ -288,10 +312,13 @@ export default { return this.type === 'database' }, isUpdate () { - return this.identifier.id !== null + if (!this.database.identifier) { + return false + } + return this.database.identifier.id !== null }, title () { - let title = (this.isUpdate ? 'Update' : 'Assign') + ' ' + let title = (this.isUpdate ? 'Update' : 'Get') + ' ' if (this.isSubset) { title += 'subset' } else if (this.isDatabase) { @@ -308,10 +335,18 @@ export default { return '' } }, + watch: { + database () { + if (!this.database.identifier) { + return + } + this.identifier = Object.assign(this.database.identifier, {}) + } + }, mounted () { this.loadLicenses() if (this.database.identifier) { - this.init(this.database.identifier) + this.identifier = Object.assign(this.database.identifier, {}) } }, methods: { @@ -346,7 +381,6 @@ export default { this.loading = true IdentifierService.create(this.identifier) .then(() => { - this.$store.dispatch('reloadDatabase') this.$toast.success(this.prefix + ' successfully persisted') this.$emit('close', { action: 'persisted' }) }) @@ -356,10 +390,24 @@ export default { }, update () { this.loading = true - IdentifierService.update(this.identifier.id, this.identifier) + const payload = { + cid: parseInt(this.$route.params.container_id), + dbid: parseInt(this.$route.params.database_id), + qid: parseInt(this.$route.params.query_id), + title: this.identifier.title, + description: this.identifier.description, + publisher: this.identifier.publisher, + publication_year: this.identifier.publication_year, + publication_month: this.identifier.publication_month, + publication_day: this.identifier.publication_day, + license: this.identifier.license, + type: this.identifier.type, + creators: this.identifier.creators, + related_identifiers: this.identifier.related_identifiers + } + IdentifierService.update(this.identifier.id, payload) .then(() => { - this.$store.dispatch('reloadDatabase') - this.$toast.success(this.prefix + ' successfully updated') + this.$toast.success(this.prefix + ' identifier successfully updated') this.$emit('close', { action: 'persisted' }) }) .finally(() => { @@ -379,34 +427,11 @@ export default { this.loading = false }) }, - init (identifier) { - if (identifier) { - console.debug('=====>', identifier) - this.identifier = Object.assign(identifier, {}) - return - } - this.identifier = { - cid: parseInt(this.$route.params.container_id), - dbid: parseInt(this.$route.params.database_id), - qid: parseInt(this.$route.params.query_id), - title: null, - description: null, - publisher: this.$config.defaultPublisher, - publication_year: formatYearUTC(Date.now()), - publication_month: formatMonthUTC(Date.now()), - publication_day: formatDayUTC(Date.now()), - license: null, - type: this.type, - creators: [ - { - firstname: null, - lastname: null, - affiliation: null, - orcid: null - } - ], - related_identifiers: [] + validateOrcidInput (val) { + if (!val) { + return false } + return val.startsWith('http') } } } 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 e613944602..1940e0bc08 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 @@ -223,7 +223,7 @@ <v-dialog v-model="persistDialog" persistent - max-width="860"> + max-width="1080"> <Persist type="database" :database="database" @close="closePersistDialog" /> </v-dialog> <v-dialog @@ -388,7 +388,7 @@ export default { return false }, hasDoi () { - if (!this.hasIdentifier) { + if (!this.hasIdentifier || !('doi' in this.database.identifier)) { return false } return this.database.identifier.doi !== null diff --git a/dbrepo-ui/utils/index.js b/dbrepo-ui/utils/index.js index cc1ee56b61..01ed03767c 100644 --- a/dbrepo-ui/utils/index.js +++ b/dbrepo-ui/utils/index.js @@ -83,6 +83,22 @@ function formatTimestampUTC (str) { return format(new Date(date), 'yyyy-MM-dd HH:mm:ss') } +function isOrcid (orcid) { + if (!orcid || orcid.startsWith('http')) { + return false + } + const input = orcid.replace('-', '') + let total = 0 + for (let i = 0; i < input.length; i++) { + const digit = Number(input.charAt(i)) + total = (total + digit) * 2 + } + const remainder = total % 11 + const result = (12 - remainder) % 11 + const checksum = result === 10 ? 'X' : String(result) + return orcid.charAt(orcid.length - 1) === checksum +} + module.exports = { notEmpty, formatTimestamp, @@ -92,5 +108,6 @@ module.exports = { isNonNegativeInteger, formatYearUTC, formatMonthUTC, - formatDayUTC + formatDayUTC, + isOrcid } -- GitLab