diff --git a/opal-core-ws/src/main/java/org/obiba/opal/web/security/KeyStoreResource.java b/opal-core-ws/src/main/java/org/obiba/opal/web/security/KeyStoreResource.java index 6875db43d7..5f6f56e8a1 100644 --- a/opal-core-ws/src/main/java/org/obiba/opal/web/security/KeyStoreResource.java +++ b/opal-core-ws/src/main/java/org/obiba/opal/web/security/KeyStoreResource.java @@ -36,6 +36,7 @@ import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.obiba.opal.core.security.OpalKeyStore; import org.obiba.opal.core.service.security.KeyStoreService; +import org.obiba.opal.web.BaseResource; import org.obiba.opal.web.magma.ClientErrorDtos; import org.obiba.opal.web.model.Opal; import org.springframework.beans.factory.annotation.Autowired; @@ -52,7 +53,7 @@ @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) -public class KeyStoreResource { +public class KeyStoreResource implements BaseResource { private OpalKeyStore keyStore; diff --git a/opal-ui/src/components/admin/providers/AddIdentityProviderDialog.vue b/opal-ui/src/components/admin/providers/AddIdentityProviderDialog.vue index c32eefd306..cf78ab7383 100644 --- a/opal-ui/src/components/admin/providers/AddIdentityProviderDialog.vue +++ b/opal-ui/src/components/admin/providers/AddIdentityProviderDialog.vue @@ -17,7 +17,7 @@ :hint="$t('identity_provider.name_hint')" class="q-mb-md" lazy-rules - :rules="[validateRequiredField('validation.identity_provider.name_required')]" + :rules="[validateRequiredField('validation.name_required')]" :disable="editMode" > diff --git a/opal-ui/src/components/admin/users/AddUserDialog.vue b/opal-ui/src/components/admin/users/AddUserDialog.vue index cc37e47bad..f631ff3cd5 100644 --- a/opal-ui/src/components/admin/users/AddUserDialog.vue +++ b/opal-ui/src/components/admin/users/AddUserDialog.vue @@ -168,7 +168,7 @@ function addGroup(val: string, done: any) { } // Validation rules -const validateRequiredName = (val: string) => (val && val.trim().length > 0) || t('validation.user.name_required'); +const validateRequiredName = (val: string) => (val && val.trim().length > 0) || t('validation.name_required'); const validateRequiredCertificate = (val: string) => (editMode.value && (!val || val.length === 0)) || (val && val.trim().length > 0) || diff --git a/opal-ui/src/components/profile/AddTokenDialog.vue b/opal-ui/src/components/profile/AddTokenDialog.vue index d02d426dcd..b67bd49c77 100644 --- a/opal-ui/src/components/profile/AddTokenDialog.vue +++ b/opal-ui/src/components/profile/AddTokenDialog.vue @@ -153,7 +153,7 @@ watch( ); // Validations -const validateRequiredName = (val: string) => (val && val.trim().length > 0) || t('validation.token.name_required'); +const validateRequiredName = (val: string) => (val && val.trim().length > 0) || t('validation.name_required'); // Group options const taskGroupOptions: { label: string; value: string }[] = []; diff --git a/opal-ui/src/components/project/AddKeyPairDialog.vue b/opal-ui/src/components/project/AddKeyPairDialog.vue new file mode 100644 index 0000000000..1af21589f7 --- /dev/null +++ b/opal-ui/src/components/project/AddKeyPairDialog.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/opal-ui/src/components/project/IdMappingsList.vue b/opal-ui/src/components/project/IdMappingsList.vue index 5c87af2209..acad3e6d30 100644 --- a/opal-ui/src/components/project/IdMappingsList.vue +++ b/opal-ui/src/components/project/IdMappingsList.vue @@ -89,7 +89,8 @@ const columns = computed(() => [ label: t('project_admin.entity_type'), align: 'left', field: 'entityType', - style: 'width: 30%', + headerStyle: 'width: 30%; white-space: normal;', + style: 'width: 30%; white-space: normal;', }, { name: 'mapping', diff --git a/opal-ui/src/components/project/KeyPairsList.vue b/opal-ui/src/components/project/KeyPairsList.vue new file mode 100644 index 0000000000..2ffb2b9de5 --- /dev/null +++ b/opal-ui/src/components/project/KeyPairsList.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/opal-ui/src/i18n/en/index.js b/opal-ui/src/i18n/en/index.js index 4c06bfe1b8..6c1da84d8c 100644 --- a/opal-ui/src/i18n/en/index.js +++ b/opal-ui/src/i18n/en/index.js @@ -304,6 +304,11 @@ export default { title: 'Commit Details', }, }, + key_type: { + KEY_PAIR: 'Key Pair', + CERTIFICATE: 'Certificate', + UNRECOGNIZED: 'Unrecognized', + }, project_admin: { properties: 'Project properties', db_hint: 'Project tables (dictionaries and data) are stored in the database:', @@ -334,6 +339,12 @@ export default { id_mappings_info: 'Identifiers mappings listed below that match the entity type of the data are automatically selected during an import/export process.', id_mappings_hint: 'The name of the mapping.', id_mapping: 'Identifiers Mapping', + encryption_keys: 'Encryption Keys', + encryption_keys_info: 'Encrypted data will be automatically decrypted at importation time using the key pairs registered within the project.', + import_key: 'Import Key Pair', + import_key_info: 'Paste encryption key in PEM format.', + private_key: 'Private Key', + public_key: 'Public Key (Certificate)', }, user_profile: { title: 'My profile', @@ -389,15 +400,14 @@ export default { deleteProject: 'Delete', }, validation: { + name_required: 'Name is required', user: { - name_required: 'Name is required', password_required: 'Password is required and must be at least 8 characters long', certificate_required: 'Certificate is required', confirm_password_required: 'Confirm password is required', passwords_not_matching: 'Passwords do not match', }, identity_provider: { - name_required: 'Name is required', clientId_required: 'Client ID is required', secret_required: 'Secret is required', discovery_uri_required: 'Discovery URI is required', @@ -410,9 +420,6 @@ export default { old_password: 'Old password is required', new_password: 'New password is required and must be at least 8 characters long', }, - token: { - name_required: 'Token name is required', - }, text_required: 'A text is required', github: { org_required: 'Github organization or username is required', @@ -420,6 +427,8 @@ export default { }, project_admin: { backup_folder_required: 'Backup folder is required', + private_key_required: 'Private Key in PEM format is required', + public_key_required: 'Public Key (Certificate) in PEM format is required', }, }, main: { diff --git a/opal-ui/src/i18n/fr/index.js b/opal-ui/src/i18n/fr/index.js index 3112180adc..c965b3bde9 100644 --- a/opal-ui/src/i18n/fr/index.js +++ b/opal-ui/src/i18n/fr/index.js @@ -304,6 +304,11 @@ export default { titre: 'Détails de la validation', } }, + key_type: { + KEY_PAIR: 'Paire de clés', + CERTIFICATE: 'Certificat', + UNRECOGNIZED: 'Non reconnu', + }, project_admin: { properties: 'Propriétés du projet', db_hint: 'Les tables de projet (dictionnaires et données) sont stockées dans la base de données:', @@ -334,6 +339,12 @@ export default { id_mappings_info: 'Les mappages d\'identifiants énumérés ci-dessous qui correspondent au type d\'entité des données sont automatiquement sélectionnés au cours d\'un processus d\'importation/exportation.', id_mappings_hint: 'Le nom du mappage.', id_mapping: 'Mappage des identifiants', + encryption_keys: 'Clés de chiffrement', + encryption_keys_info: 'Les données chiffrées seront automatiquement déchiffrées au moment de l\'importation en utilisant les paires de clés enregistrées dans le projet.', + import_key: 'Importer une paire de clés', + import_key_info: 'Collez la clé de chiffrement au format PEM.', + private_key: 'Clé privée', + public_key: 'Clé publique (certificat)" ', }, user_profile: { title: 'Mon profil', @@ -389,15 +400,14 @@ export default { deleteProject : 'Supprimer', }, validation: { + name_required: 'Le nom est requis', user: { - name_required: 'Le nom est requis', password_required: 'Le mot de passe est requis et doit comporter au moins 8 caractères', certificate_required: 'Le certificat est requis', confirm_password_required: 'La confirmation du mot de passe est requise', passwords_not_matching: 'Les mots de passe ne correspondent pas', }, identity_provider: { - name_required: 'Le nom est requis', clientId_required: "L'ID client est requis", secret_required: 'Le secret est requis', discovery_uri_required: "L'URI de découverte est requis", @@ -410,9 +420,6 @@ export default { old_password: 'L\'ancien mot de passe est requis', new_password: 'Un nouveau mot de passe est requis et doit comporter au moins 8 caractères.', }, - token : { - name_required : 'Le nom du jeton est obligatoire', - }, text_required: 'Un texte est requis', github: { org_required: 'L\'organisation ou le nom d\'utilisateur Github est requis', @@ -420,7 +427,9 @@ export default { }, project_admin: { backup_folder_required: 'Le dossier de sauvegarde est requis', - }, + private_key_required: 'Clé privée au format PEM requise', + public_key_required: 'Clé publique (certificat) au format PEM requis' + }, }, main: { brand: 'Opal', diff --git a/opal-ui/src/pages/ProjectAdminPage.vue b/opal-ui/src/pages/ProjectAdminPage.vue index 1fd4936c08..e6dfd3fd42 100644 --- a/opal-ui/src/pages/ProjectAdminPage.vue +++ b/opal-ui/src/pages/ProjectAdminPage.vue @@ -43,16 +43,30 @@ + + + + + + + + - + - + - + - + - - + @@ -171,6 +189,7 @@ import BackupProjectDialog from 'src/components/project/BackupProjectDialog.vue' import RestoreProjectDialog from 'src/components/project/RestoreProjectDialog.vue'; import AddProjectDialog from 'src/components/project/AddProjectDialog.vue'; import IdMappingsList from 'src/components/project/IdMappingsList.vue'; +import KeyPairsList from 'src/components/project/KeyPairsList.vue'; const route = useRoute(); const router = useRouter(); @@ -185,7 +204,14 @@ const onConfirmed = ref(() => ({})); const state = ref(ProjectDatasourceStatusDto.NONE); const project = computed(() => projectsStore.project); const name = computed(() => route.params.id as string); -const hasAdminPermission = computed(() => projectsStore.perms.project?.canUpdate() || false); +const hasAdminPermission = computed( + () => + projectsStore.perms.project?.canCreate() || + projectsStore.perms.project?.canUpdate() || + projectsStore.perms.project?.canDelete() +); +const hasReloadPermission = computed(() => projectsStore.perms.reload?.canCreate() || false); +const hasKeystorePermission = computed(() => projectsStore.perms.keystore?.canCreate() || false); const hasDatabase = computed(() => !!project.value.database); const properties: FieldItem[] = [ @@ -295,7 +321,7 @@ function onEdit() { showEditProject.value = true; } -async function onIdMappingsUpdate() { +async function onProjectUpdate() { try { await projectsStore.refreshProject(name.value); await getState(); diff --git a/opal-ui/src/pages/ProjectPermsPage.vue b/opal-ui/src/pages/ProjectPermsPage.vue index f30cb8e5d9..097a3ad538 100644 --- a/opal-ui/src/pages/ProjectPermsPage.vue +++ b/opal-ui/src/pages/ProjectPermsPage.vue @@ -11,8 +11,8 @@
-
-
+
+
@@ -62,7 +62,7 @@