From 0d5ee8887ea0cc94be57fc1972845a66f7d21145 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Tue, 20 Aug 2024 09:08:58 +0200 Subject: [PATCH 01/19] pom.xml: set version to 1.0.1-SNAPSHOT --- module-core/pom.xml | 4 ++-- module-exchange/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/module-core/pom.xml b/module-core/pom.xml index afa69e7..f82f309 100644 --- a/module-core/pom.xml +++ b/module-core/pom.xml @@ -10,7 +10,7 @@ io.goobi.vocabulary vocabulary-server-core - 1.0.0 + 1.0.1-SNAPSHOT Vocabulary-Server-Core Spring Boot based RESTful web service for vocabulary management jar @@ -27,7 +27,7 @@ io.goobi.vocabulary vocabulary-server-exchange - 1.0.0 + 1.0.1-SNAPSHOT compile diff --git a/module-exchange/pom.xml b/module-exchange/pom.xml index 3d63b5b..ea56158 100644 --- a/module-exchange/pom.xml +++ b/module-exchange/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.goobi.vocabulary vocabulary-server-exchange - 1.0.0 + 1.0.1-SNAPSHOT Vocabulary Exchange Vocabulary data exchange classes jar diff --git a/pom.xml b/pom.xml index 878b5c8..73f8c74 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.goobi.vocabulary vocabulary-server - 1.0.0 + 1.0.1-SNAPSHOT Vocabulary-Server pom RESTful webservice for vocabulary management From 235f617e60dd7df866a71cf70a0b6503d0406f3c Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Wed, 21 Aug 2024 13:53:31 +0200 Subject: [PATCH 02/19] task: add dry mode to mets migrator --- migration/lib/mets_context.py | 3 ++- migration/lib/mets_manipulator.py | 12 ++++++++++-- migration/metadata-migrator.py | 3 ++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/migration/lib/mets_context.py b/migration/lib/mets_context.py index c8be2e7..56a4f02 100644 --- a/migration/lib/mets_context.py +++ b/migration/lib/mets_context.py @@ -6,8 +6,9 @@ RECORD_PATTERN = re.compile('^(\\d+).*$') class Context: - def __init__(self, api, verbose, continue_on_error, metadata_directory, mapping_file, preferred_mets_main_value_language, manual_id_fix): + def __init__(self, api, dry, verbose, continue_on_error, metadata_directory, mapping_file, preferred_mets_main_value_language, manual_id_fix): self.api = api + self.dry = dry self.verbose = verbose self.continue_on_error = continue_on_error self.metadata_directory = metadata_directory diff --git a/migration/lib/mets_manipulator.py b/migration/lib/mets_manipulator.py index 25ab52b..ca3dcb0 100644 --- a/migration/lib/mets_manipulator.py +++ b/migration/lib/mets_manipulator.py @@ -32,7 +32,7 @@ def process_mets_file(self): root = tree.getroot() self.process_node(root) - if self.changed: + if self.changed and not self.ctx.dry: self.create_backup() tree.write(self.file_path, encoding='utf-8', xml_declaration=True) self.ctx.log_processed(self.file_path) @@ -40,8 +40,12 @@ def process_mets_file(self): def process_node(self, node): if self.is_vocabulary_reference(node) and not self.is_already_migrated(node): self.process_vocabulary_reference(node) + if self.ctx.dry: + dump_node(node) if self.is_manual_id_reference(node): self.process_manual_id_reference(node) + if self.ctx.dry: + dump_node(node) for child in node: self.process_node(child) @@ -129,4 +133,8 @@ def generate_vocabulary_uri(vocabulary_id): return VOCABULARY_ENDPOINT.replace('{{ID}}', str(vocabulary_id)) def generate_record_uri(record_id): - return RECORD_ENDPOINT.replace('{{ID}}', str(record_id)) \ No newline at end of file + return RECORD_ENDPOINT.replace('{{ID}}', str(record_id)) + +def dump_node(node): + attributes = ' '.join(f'{k}="{v}"' for k, v in node.attrib.items()) + logging.info(f'<{node.tag} {attributes} />') \ No newline at end of file diff --git a/migration/metadata-migrator.py b/migration/metadata-migrator.py index ea9fd55..a0afb5a 100644 --- a/migration/metadata-migrator.py +++ b/migration/metadata-migrator.py @@ -13,7 +13,7 @@ def main(): args.vocabulary_server_host, args.vocabulary_server_port ) - ctx = Context(api, args.verbose, args.continue_on_error, args.metadata_directory, args.mapping_file, args.preferred_mets_main_value_language, args.manual_id_fix) + ctx = Context(api, args.dry, args.verbose, args.continue_on_error, args.metadata_directory, args.mapping_file, args.preferred_mets_main_value_language, args.manual_id_fix) try: migrator = MetsMigrator(ctx) @@ -31,6 +31,7 @@ class RawTextDefaultsHelpFormatter(argparse.RawTextHelpFormatter, argparse.Argum def parse_args(): parser = argparse.ArgumentParser(prog='metadata-migrator.py', formatter_class=RawTextDefaultsHelpFormatter, description='Metadata migration tool.') + parser.add_argument('--dry', required=False, default=False, action='store_const', const=True, help='Don\'t persist changes but only print replacements to the console') parser.add_argument('--metadata-directory', '-d', required=True, help='directory to recursively scan for metadata to update') parser.add_argument('--mapping-file', '-m', required=True, help='vocabulary and record mapping file') parser.add_argument('--vocabulary-server-host', type=str, default='localhost', help='vocabulary server host') From 8632f45b1c5c5cbd9da6e8bd6129c4060622c7fe Mon Sep 17 00:00:00 2001 From: Sebastian Kutsch Date: Wed, 21 Aug 2024 17:57:18 +0200 Subject: [PATCH 03/19] doc: Update README --- docs/de/README.md | 4 ++-- docs/en/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/de/README.md b/docs/de/README.md index 5ee57a3..750786c 100644 --- a/docs/de/README.md +++ b/docs/de/README.md @@ -1,8 +1,8 @@ # Dokumentation -Dieses Dokument beschreibt alles Wichtige rund um den neuen Vokabularserver. Früher waren Vokabulare Teil von Goobi Workflow und wurden in der Datenbank `goobi` gespeichert. Jetzt ist alles, was mit Vokabularen zu tun hat, in eine neue, eigenständige Anwendung, den Vokabularserver, umgezogen. Der Vokabularserver benötigt eine eigene Datenbank, um alle Daten zu speichern, und ermöglicht den Zugriff auf die Vokabulare und Datensätze über eine REST-API. Goobi Workflow wurde aktualisiert, um den neuen Vokabularserver anstelle seiner eigenen, eingebetteten Vokabulare zu verwenden. Falls gewünscht, kann der Vokabularserver im Gegensatz zur Goobi Workflow-Instanz öffentlich zugänglich sein. Wenn Sie bereits vorher Vokabulare verwendet haben, lesen Sie bitte die Migrationsanleitung in dieser Dokumentation, um Ihre Daten auf den neuen Vokabularserver zu übertragen. +Dieses Dokument beschreibt den neuen Vokabularserver. Bis zur Version 24.06 waren Vokabulare Teil von Goobi Workflow und wurden in der Goobi-Datenbank gespeichert. Ab Version 24.07 ist alles, was mit Vokabularen zu tun hat, in eine eigenständige Anwendung, den Vokabularserver, umgezogen. Der Vokabularserver benötigt eine eigene Datenbank, um alle Daten zu speichern, und ermöglicht den Zugriff auf die Vokabulare und Datensätze über eine REST-API. Goobi Workflow wurde aktualisiert, um den neuen Vokabularserver anstelle seiner eigenen, eingebetteten Vokabulare zu verwenden. Falls gewünscht, kann der Vokabularserver öffentlich zugänglich sein. Wenn Sie bereits vorher Vokabulare verwendet haben, lesen Sie bitte die Migrationsanleitung in dieser Dokumentation, um Ihre Daten auf den neuen Vokabularserver zu übertragen. ## Installation -Bevor Sie den neuen Vokabularserver nutzen können, folgen Sie den [Installations Anweisungen](setup.md). +Bevor Sie den Vokabularserver nutzen können, folgen Sie den [Installations Anweisungen](setup.md). ## Vokabularerstellung Vokabulare und Vokabularschemata sind ein komplexes Thema für sich, daher wird die [Dokumentation der Erstellung von Vokabularen, Schemata und Feldtypen](creation.md) separat behandelt. diff --git a/docs/en/README.md b/docs/en/README.md index f26dd6f..4bb1a9c 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -1,8 +1,8 @@ # Documentation -This document describes everything important regarding the new vocabulary server. Earlier, vocabularies were part of Goobi Workflow and saved in the `goobi` database. Now, everything related to vocabularies moved to a new stand-alone application, the vocabulary server. The vocabulary server requires its own database to store all its data and provides access to the vocabularies and records through a REST API. Goobi Workflow has been updated to use the new vocabulary server instead of its own, embedded vocabularies. If desired, the vocabulary server could be publicly available in contrast to the Goobi Workflow instance. If you already used vocabularies before, check out the migration guide in this documentation to transfer your data to the new vocabulary server. +This document describes the new vocabulary server. Until version 24.06, vocabularies were part of Goobi Workflow and saved in Goobi's database. Since version 24.07, everything related to vocabularies moved to a stand-alone application, the vocabulary server. The vocabulary server requires its own database to store all its data and provides access to the vocabularies and records through a REST API. Goobi Workflow has been updated to use the new vocabulary server instead of its own, embedded vocabularies. If desired, the vocabulary server could be publicly available. If you already used vocabularies before, check out the migration guide in this documentation to transfer your data to the new vocabulary server. ## Setup -Before you can start using the new vocabulary server, follow the [setup instructions](setup.md). +Before you can start using the vocabulary server, follow the [setup instructions](setup.md). ## Vocabulary Creation Vocabularies and vocabulary schemas are a complex topic on their own, therefore the [documentation of the creation of vocabularies, schemas and field types](creation.md) is covered separately. From 0a4be19a69a19bc839fe1141cabc6830f8719133 Mon Sep 17 00:00:00 2001 From: Sebastian Kutsch Date: Tue, 27 Aug 2024 08:57:52 +0200 Subject: [PATCH 04/19] docs: extend setup and migration instructions, wip. --- docs/de/migration.md | 53 ++++++++++++++++---- docs/de/setup.md | 114 ++++++++++++++++++++++++++++++++----------- docs/en/setup.md | 4 +- 3 files changed, 131 insertions(+), 40 deletions(-) diff --git a/docs/de/migration.md b/docs/de/migration.md index 1c4722e..584d71d 100644 --- a/docs/de/migration.md +++ b/docs/de/migration.md @@ -9,13 +9,13 @@ Für alle folgenden Anweisungen muss der Vokabularserver bereits laufen. Erstellen Sie zunächst eine virtuelle Python-Umgebung, aktivieren Sie diese und installieren Sie alle erforderlichen Python-Abhängigkeiten. Alle folgenden Anweisungen in dieser Dokumentation setzen immer eine aktivierte Python-Umgebung voraus, in der alle diese Abhängigkeiten vorhanden sind. ```bash -python -m venv vmenv +python3 -m venv vmenv . vmenv/bin/activate pip install requests mysql-connector-python==8.4.0 alive_progress lxml ``` ## Migration der Vokabulardaten durchführen -Laden Sie das [Vocabulary Migration Tool] herunter und entpacken Sie es (https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/migration/*zip*/migration.zip). +Laden Sie das [Vocabulary Migration Tool](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/migration/*zip*/migration.zip) herunter und entpacken Sie es, falls nicht schon bei der Installation geschehen. **Hinweis** Bevor Sie einen der folgenden Schritte durchführen, lesen Sie diese Dokumentation bitte zuerst vollständig durch. Es gibt keine einfache "Nur diese Schritte ausführen"-Lösung für jeden Anwendungsfall. @@ -32,6 +32,18 @@ Wenn Sie keine Feldtypen erstellen wollen, können Sie die Datenmigration mit de python vocabulary-migrator.py --vocabulary-server-host localhost --vocabulary-server-port 8081 --goobi-database-host localhost --goobi-database-port 3306 --goobi-database-name goobi --goobi-database-user goobi --goobi-database-password goobi --continue-on-error --fallback-language eng ``` +### Skript +Die obigen beiden Puntke, die virtuelle Python-Umgebung und die Migration der Vokabulardaten in einer typischen Installation: +```bash +cd /opt/digiverso/vocabulary/migration +python3 -m venv vmenv +. vmenv/bin/activate +pip install requests mysql-connector-python==8.4.0 alive_progress lxml +VOC_PORT=$(sudo grep -oP '^server.port=\K.*' /opt/digiverso/vocabulary/application.properties) +DB_GOOBI_PW=$(sudo xmlstarlet sel -t -v '//Resource/@password' -n /etc/tomcat9/Catalina/localhost/goobi.xml) +python vocabulary-migrator.py --vocabulary-server-host localhost --vocabulary-server-port "${VOC_PORT}" --goobi-database-host localhost --goobi-database-port 3306 --goobi-database-name goobi --goobi-database-user goobi --goobi-database-password "${DB_GOOBI_PW}" --continue-on-error --fallback-language ger +``` + **Hinweis** Ändern Sie die Parameter entsprechend Ihrer Konfiguration. Der Parameter `fallback-language` definiert die Standardsprache, die für ein mehrsprachiges Vokabularfeld verwendet wird, für das keine Standardsprache abgeleitet werden konnte. Die Option `continue-on-error` verhindert, dass das Migrationstool bei Fehlern bei der Datenmigration anhält. Diese Fehler können auftreten, wenn die Daten nicht in den neuen Vokabularserver eingefügt werden konnten. Mögliche Gründe dafür könnten sein: - Der Vokabulardatensatz ist leer. - Der Vokabulardatensatz enthält Daten, die mit einigen Typbeschränkungen nicht kompatibel sind. @@ -48,7 +60,7 @@ python vocabulary-migrator.py --vocabulary-server-host localhost --vocabulary-se - Führen Sie die Ersteinrichtung durch. - Führen Sie die Vokabular-Migration wie oben beschrieben durch. - Speichern Sie nach Abschluss der Migration die Datei `migration.csv` an einem sicheren Ort. -Diese Datei enthält alle Informationen über die migrierten Datensätze mit ihren alten und neuen IDs. +Diese Datei enthält alle Informationen über die migrierten Datensätze mit ihren alten und neuen IDs. Diese Informationen werden benötigt, um später die Verweise auf die Vokabulardatensätze in den Metadaten-Dateien zu aktualisieren. - Sie sollten versuchen, die Migration am Ende ohne den Parameter `--continue-on-error` durchzuführen. Wenn dies funktioniert, dann wissen Sie dass alles problemslos migriert werden konnte. @@ -72,7 +84,7 @@ Dieses Problem zeigt an, dass eines der Vokabulardatensatzfelder den Wert `Geona Während der Migration wurde dieses Feld (auf der Grundlage vorhandener Daten) mit einem neuen Typ konfiguriert, der die folgenden auswählbaren Werte `geonames` und `viaf` enthält. Wie Sie sehen können, wird der aktuelle Wert mit einem großen `G` geschrieben, aber nur die klein geschriebene Version von `geonames` ist einer der auswählbaren Werte. Daher schlägt die Validierung dieses Vokabeldatensatzes fehl und das Skript ist nicht in der Lage, diesen Datensatz zu importieren. -In diesem speziellen Fall könnten Sie alle Vorkommen in der alten Datenbank aktualisieren und anschließend einen erneuten Import der Daten durchführen. +In diesem speziellen Fall könnten Sie alle Vorkommen in der alten Datenbank aktualisieren und anschließend einen erneuten Import der Daten durchführen. Leeren Sie dazu zunächst die Vokabular-(SQL)-Datenbank. Stellen Sie sicher, dass Sie die Datei `migration_issues.log` vor einem Re-Import umbenennen oder entfernen, da das Migrationsscript immer an diese Datei anhängt und so bereits gelöste Probleme enthalten würde. Jeder Vokabulardatensatz, der in dieser Datei gemeldet wird, wurde nicht in den Vokabularserver importiert. Wenn dies gewünscht ist (weil der Vokabeldatensatz fehlerhafte Daten enthält und nicht beibehalten werden soll), können Sie dieses Problem einfach ignorieren und weitermachen. @@ -83,7 +95,7 @@ Mit `python vocabulary-migrator.py --help` können Sie sich alle verfügbaren Op Wenn Sie vorhaben, bestehende Feldtypen wiederzuverwenden oder andere Vokabulare für Vokabularreferenzen während einer Migration zu verwenden, erstellen Sie die folgenden drei Dateien: `reference_type_lookup.csv`, `reference_value_lookup.csv` und `type_definition_lookup.csv`. Legen Sie alle drei Dateien in einem neuen Verzeichnis ab und übergeben Sie diesen Verzeichnispfad als Parameter `--lookup-file-directory` an das Migrationstool. -Um die folgende Konfiguration besser zu verstehen, geben wir ein Beispiel. +Um die folgende Konfiguration besser zu verstehen, geben wir ein Beispiel. Stellen Sie sich vor, Sie haben derzeit ein Vokabular mit einem Feld mit den folgenden auswählbaren Werten: `red`, `blue`. Datensätze können jeden dieser beiden Werte enthalten. Die Unterstützung mehrerer Sprachen wird derzeit erreicht, indem eine neue Felddefinition mit einem anderen Sprachwert erstellt wird und die gleiche Anzahl von auswählbaren Werten, diesmal in der anderen Sprache, bereitgestellt wird: `rot`, `blau` (auf Deutsch). @@ -113,7 +125,7 @@ rot|blau,2 Diese Datei ordnet allen auswählbaren Werten (in allen Sprachen, eine Sprache pro Zeile) die ID des Vokabulars zu, welches die Farbdatensätze enthält. Achten Sie bitte darauf, immer eine Sprache pro Zeile zu definieren. Der Grund dafür ist, dass verschiedene Sprachen vorher als mehrere Felddefinitionen vorhanden waren und die Migrationsverarbeitung diese Art der Trennung erfordert. -Der Wert für die Spalten `values` muss mit der Spalte `selection` in der Tabelle `vocabulary_structure` der bestehenden Datenbank übereinstimmen. +Der Wert für die Spalten `values` muss mit der Spalte `selection` in der Tabelle `vocabulary_structure` der bestehenden Datenbank übereinstimmen. Die Datei `reference_value_lookup.csv` sieht wie folgt aus: ```csv @@ -125,11 +137,32 @@ blau,123 ``` Diese Datei ordnet allen Datensatzwerten die entsprechende Datensatz-IDs im Referenzvokabular zu (also die ID des Farbdatensatzes im Farbvokabular). +### Test der Vokabulardaten-Migration +- Wenn eine Datenmigration stattgefunden hat, prüfen Sie, ob alle Vokabulare migriert wurden: +```bash +curl -s http://localhost:8081/api/v1/vocabularies | jq -r '._embedded.vocabularyList[] .name' +``` +- Prüfen Sie, ob die Links korrekt aufgelöst werden (siehe Konfiguration): +```bash +curl http://localhost:8081/api/v1/records/1 | jq +``` +Das JSON-Element `_links` sollte Verweise auf andere Ressourcen enthalten. +Diese URLs sollten gültig und auflösbar sein. +Wenn Sie keinen dieser Verweise öffnen können, überprüfen Sie die Konfiguration des Vokabularservers (Konfigurationsoption `vocabulary-server.base-url`). +Bei Problemen mit diesen URLs müssen die Daten nicht neu importiert werden. +Aktualisieren Sie einfach die Konfigurationsdatei und starten Sie den Vokabularserver neu, damit die Änderungen wirksam werden. + ## Migration der Mets-Datei Dieser Schritt kann nur durchgeführt werden, wenn die Migration der Vokabulardaten erfolgreich abgeschlossen wurde! Wenn die Datei `migration.csv` vorhanden ist, führen Sie den folgenden Befehl in der aktivierten Python-Umgebung aus: ```bash +cd /opt/digiverso/vocabulary/migration +sudo -s +. vmenv/bin/activate +# dry-run: +python metadata-migrator.py --verbose --log INFO -m migration.csv -d /opt/digiverso/goobi/metadata --dry +# metadata migration python metadata-migrator.py -m migration.csv -d /opt/digiverso/goobi/metadata ``` @@ -138,7 +171,7 @@ Wann immer das Script eine Vokabularreferenz in der Mets-Datei findet, wird es v Wenn etwas geändert wird, wird zuvor ein Backup der Mets-Datei erstellt. Wenn die Mets-Dateien zusätzliche Referenzen auf Datensätze in separaten XML-Elementen enthalten (z. B. `5661`), kann das `metadata-migrator.py` Script diese Referenzen auch mit dem zusätzlichen Parameter `--manual-id-fix SourceID` aktualisieren. Der Wert des Parameters muss mit dem Attribut `Name` eines `Metadaten`-Elements übereinstimmen, damit dessen Datensatz-ID durch die neue Datensatz-ID ersetzt werden kann. Dieser Schritt darf nicht zweimal ausgeführt werden, da dies die IDs verfälschen würde! - + ## Datenbereinigung Wenn die Datenmigration erfolgreich abgeschlossen ist und Sie sicher sind, dass Sie die alten Daten nicht mehr benötigen, können Sie alle Vokabulartabellen manuell aus der Datenbank `goobi` Ihrer Goobi-Instanz entfernen: - `vocabulary` @@ -146,7 +179,7 @@ Wenn die Datenmigration erfolgreich abgeschlossen ist und Sie sicher sind, dass - `vocabulary_record_data` - `vocabulary_structure` -**Achtung** Die Datenbereinigung kann nicht rückgängig gemacht werden. -Wenn Sie sich nicht sicher sind, führen Sie die Bereinigungsschritte nicht durch. -Die alten Vokabulardaten haben keinen Einfluss auf neuere Versionen von Goobi Workflow. +**Achtung** Die Datenbereinigung kann nicht rückgängig gemacht werden. +Wenn Sie sich nicht sicher sind, führen Sie die Bereinigungsschritte nicht durch. +Die alten Vokabulardaten haben keinen Einfluss auf neuere Versionen von Goobi Workflow. Wir empfehlen, diese Daten für den Fall der Fälle für einige Zeit aufzubewahren. diff --git a/docs/de/setup.md b/docs/de/setup.md index 733109d..0091c0f 100644 --- a/docs/de/setup.md +++ b/docs/de/setup.md @@ -2,21 +2,12 @@ Diese Dokumentation beschreibt den Prozess der Installation und Ersteinrichtung des Vokabularservers. ## Download und Installation -- Laden Sie die [Neuste Version](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/module-core/target/) des Vokabularservers herunter. -- Laden Sie die [Konfigurationsdatei](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/module-core/src/main/resources/application.properties) des Vokabularservers herunter. +- Laden Sie die [Neuste Version](https://github.com/intranda/goobi-vocabulary-server/releases/latest) des Vokabularservers herunter. +- Laden Sie die [Konfigurationsdatei](https://github.com/intranda/goobi-vocabulary-server/releases/latest/download/application.properties) des Vokabularservers herunter. - Passen Sie die Konfigurationsdatei entsprechend Ihrer Konfiguration an und entfernen Sie nicht geänderte Zeilen. - Datenbankanmeldeinformationen und Datenbankname. - Basis-URL und Port. -- **TODO** *Installieren Sie die `vocabulary-server.jar` und die Konfigurationsdatei `application.properties` direkt in einen neuen Ordner (z. B. `/opt/digiverso/vocabulary/`)* - -## Als systemd-Dienst starten -- **TODO** *Erstellen einer systemd Service Unit für den Vokabularserver (Die Anwendung sollte bei SIGTERM korrekt herunterfahren können)* -- **TODO** *Admin-Dokumentation hier* -- Führen Sie `java -jar vocabulary-server-VERSION.jar` aus. -- Wenn der Start erfolgreich war, werden Sie nach ein paar Sekunden eine Zeile wie diese sehen: -```bash -Started VocabularyServerApplication in 4.244 seconds (process running for 4.581) -``` +- Erstellen Sie ein Systemd-Service, um den Dienst automatisch zu starten. ## Einrichtung von Goobi Workflow zur Kommunikation mit dem Vokabularserver - Goobi Workflow verwendet seit Version `24.07` den neuen Vokabularserver. @@ -32,9 +23,89 @@ Started VocabularyServerApplication in 4.244 seconds (process running for 4.581) - Ändern Sie die Variable `HOST` am Anfang entsprechend der Konfiguration des Vokabularservers, lassen Sie das Suffix `/api/v1` unverändert. - Führen Sie das Skript aus. -## Sicherheit -- Sie können Apache-URL-Beschränkungen einrichten, um den Vokabularserver vor unberechtigtem Zugriff zu schützen. -- **TODO** *Admins, bitte finden Sie heraus, was und wie man es im Detail macht.* +## Installationsskript +Für die obigen drei Punkte unter Ubuntu: +```bash +export VOC_PORT=8081 +export VOC_PATH=/opt/digiverso/vocabulary +export VOC_USER=vocabulary +export VOC_SQL_USER=${VOC_USER} +export VOC_SQL_DB=${VOC_USER} +export PW_SQL_VOC=$(/dev/null +sudo ln -rs ${VOC_PATH}/vocabulary-server*.jar ${VOC_PATH}/vocabulary-server.jar + +# create system user which will run the service +sudo adduser --system --home ${VOC_PATH}/home --shell /usr/sbin/nologin --no-create-home --disabled-login ${VOC_USER} + +# download the vocabulary migration tools +git clone --depth=1 https://github.com/intranda/goobi-vocabulary-server.git /tmp/goobi-vocabulary-server +sudo cp -ait ${VOC_PATH} /tmp/goobi-vocabulary-server/migration + +# download and set up the config file +wget https://github.com/intranda/goobi-vocabulary-server/releases/latest/download/application.properties -O - | sudo tee ${VOC_PATH}/application.properties >/dev/null +sudo sed -re "s|^(server.port=).*|\1${VOC_PORT}|" \ + -e "s|^(spring.datasource.username=).*|\1${VOC_SQL_USER}|" \ + -e "s|^(spring.datasource.password=).*|\1${PW_SQL_VOC}|" \ + -e "s|^(spring.datasource.url=).*|\1jdbc:mariadb://localhost:3306/${VOC_SQL_DB}|" \ + -i ${VOC_PATH}/application.properties +sudo chown ${VOC_USER}: ${VOC_PATH}/application.properties +sudo chmod 600 ${VOC_PATH}/application.properties + +# install a systemd service unit file +cat << EOF | sudo tee /etc/systemd/system/vocabulary.service +[Unit] +Description=Goobi Vocabulary Server +After=mysql.service remote-fs.target +Requires=mysql.service remote-fs.target + +[Service] +WorkingDirectory=${VOC_PATH} +Restart=always +RestartSec=20s +StartLimitInterval=100s +StartLimitBurst=4 +ExecStart=/usr/bin/java -jar vocabulary-server.jar +User=${VOC_USER} +NoNewPrivileges=true +ProtectSystem=true +PrivateTmp=yes + +[Install] +WantedBy=default.target tomcat9.service +EOF +sudo systemctl daemon-reload +sudo systemctl enable vocabulary.service + +# create and configure the database +sudo mysql -e "CREATE DATABASE ${VOC_SQL_DB}; + CREATE USER '${VOC_SQL_USER}'@'localhost' IDENTIFIED BY '${PW_SQL_VOC}'; + GRANT ALL PRIVILEGES ON ${VOC_SQL_DB}.* TO '${VOC_SQL_USER}'@'localhost' WITH GRANT OPTION; + FLUSH PRIVILEGES;" + +# append vocabulary server address to the Goobi workflow config +grep ^vocabularyServerHost= /opt/digiverso/goobi/config/goobi_config.properties || echo "vocabularyServerHost=localhost" | sudo tee -a /opt/digiverso/goobi/config/goobi_config.properties +grep ^vocabularyServerPort= /opt/digiverso/goobi/config/goobi_config.properties || echo "vocabularyServerPort=${VOC_PORT}" | sudo tee -a /opt/digiverso/goobi/config/goobi_config.properties + +# start the vocabulary server +sudo systemctl start vocabulary.service +## check startup +journalctl -u vocabulary.service -f + +# initial set up +wget https://github.com/intranda/goobi-vocabulary-server/releases/latest/download/default_setup.sh -O - | sudo tee ${VOC_PATH}/default_setup.sh >/dev/null +bash ${VOC_PATH}/default_setup.sh +## test +curl -s http://localhost:${VOC_PORT}/api/v1/types | jq -r '._embedded.fieldTypeList[] .name' +``` + + +## Erreichbarkeit +- Sie können den Vokabularserver von außen erreichbar machen, indem Sie einen Proxy samt Zugriffskontrolle davorschalten. ## Installationstest - Ändern Sie für alle Befehle Host und Port entsprechend. @@ -56,16 +127,3 @@ skos:related skos:closeMatch skos:exactMatch ``` -- Wenn eine Datenmigration stattgefunden hat, prüfen Sie, ob alle Vokabulare migriert wurden: -```bash -curl http://localhost:8081/api/v1/vocabularies/all | jq -r '._embedded.vocabularyList[] .name' -``` -- Prüfen Sie, ob die Links korrekt aufgelöst werden (siehe Konfiguration): -```bash -curl http://localhost:8081/api/v1/records/1 | jq -``` -Das JSON-Element `_links` sollte Verweise auf andere Ressourcen enthalten. -Diese URLs sollten gültig und auflösbar sein. -Wenn Sie keinen dieser Verweise öffnen können, überprüfen Sie die Konfiguration des Vokabularservers (Konfigurationsoption `vocabulary-server.base-url`). -Bei Problemen mit diesen URLs müssen die Daten nicht neu importiert werden. -Aktualisieren Sie einfach die Konfigurationsdatei und starten Sie den Vokabularserver neu, damit die Änderungen wirksam werden. diff --git a/docs/en/setup.md b/docs/en/setup.md index 3a1683b..5490737 100644 --- a/docs/en/setup.md +++ b/docs/en/setup.md @@ -2,8 +2,8 @@ This documentation describes the process of bootstrapping the vocabulary server. ## Download and Installation -- Download [Latest Build](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/module-core/target/) of vocabulary server. -- Download [Configuration File](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/module-core/src/main/resources/application.properties) of the vocabulary server. +- Download [Latest Build](https://github.com/intranda/goobi-vocabulary-server/releases/latest) of vocabulary server. +- Download [Configuration File](https://github.com/intranda/goobi-vocabulary-server/releases/latest/download/application.properties) of the vocabulary server. - Adapt configuration file properly and remove unmodified lines. - Database credentials and database name. - Base URL and port. From 5ad361fa796d49c652dd5bf57b882c70ade289b6 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Tue, 27 Aug 2024 09:38:21 +0200 Subject: [PATCH 05/19] ci: create archives for setup scripts and migration tool --- .github/workflows/develop-build.yml | 12 ++++++++++-- .github/workflows/release-build.yml | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/develop-build.yml b/.github/workflows/develop-build.yml index 15a2b59..2005d1f 100644 --- a/.github/workflows/develop-build.yml +++ b/.github/workflows/develop-build.yml @@ -27,6 +27,14 @@ jobs: ${{ runner.os }}-maven- - name: Build with Maven run: mvn clean verify -U -P snapshot-build + - name: Create ZIP archive for migration tool + uses: montudor/action-zip@v1 + with: + args: zip -qq -r migration-tool.zip migration + - name: Create ZIP archive for setup scripts + uses: montudor/action-zip@v1 + with: + args: zip -qq -r setup-scripts.zip install - name: Get current date id: date run: echo "::set-output name=date::$(date +'%Y-%m-%d %H:%M:%S %Z')" @@ -45,5 +53,5 @@ jobs: files: | module-*/target/*.jar module-core/src/main/resources/application.properties - install/* - migration/** + setup-scripts.zip + migration-tool.zip diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index f89d868..1611dae 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -27,6 +27,14 @@ jobs: ${{ runner.os }}-maven- - name: Build with Maven run: mvn clean verify -U -P release-build + - name: Create ZIP archive for migration tool + uses: montudor/action-zip@v1 + with: + args: zip -qq -r migration-tool.zip migration + - name: Create ZIP archive for setup scripts + uses: montudor/action-zip@v1 + with: + args: zip -qq -r setup-scripts.zip install - name: Release id: create_release uses: softprops/action-gh-release@v2 @@ -38,5 +46,5 @@ jobs: files: | module-*/target/*.jar module-core/src/main/resources/application.properties - install/* - migration/** + setup-scripts.zip + migration-tool.zip From cd74265271d289cb69de24eb286379fa0f72f3a0 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Tue, 27 Aug 2024 10:02:34 +0200 Subject: [PATCH 06/19] pom.xml: remove version from artifact --- module-core/pom.xml | 1 + module-exchange/pom.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/module-core/pom.xml b/module-core/pom.xml index f82f309..1d12c17 100644 --- a/module-core/pom.xml +++ b/module-core/pom.xml @@ -139,6 +139,7 @@ + ${project.artifactId} org.springframework.boot diff --git a/module-exchange/pom.xml b/module-exchange/pom.xml index ea56158..aaac968 100644 --- a/module-exchange/pom.xml +++ b/module-exchange/pom.xml @@ -40,6 +40,7 @@ + ${project.artifactId} From 5926bca03f5dee38ad7cd45eb17363e0d0eee99c Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Wed, 28 Aug 2024 10:09:30 +0200 Subject: [PATCH 07/19] fix: remove RDF export for languages --- .../vocabulary/api/LanguageController.java | 40 +------------------ .../vocabulary/service/rdf/RDFMapper.java | 5 --- .../vocabulary/service/rdf/RDFMapperImpl.java | 25 ------------ 3 files changed, 1 insertion(+), 69 deletions(-) diff --git a/module-core/src/main/java/io/goobi/vocabulary/api/LanguageController.java b/module-core/src/main/java/io/goobi/vocabulary/api/LanguageController.java index 7bc067b..cd13f3d 100644 --- a/module-core/src/main/java/io/goobi/vocabulary/api/LanguageController.java +++ b/module-core/src/main/java/io/goobi/vocabulary/api/LanguageController.java @@ -7,14 +7,11 @@ import io.goobi.vocabulary.model.jpa.LanguageEntity; import io.goobi.vocabulary.service.manager.LanguageDTOManager; import io.goobi.vocabulary.service.manager.Manager; -import io.goobi.vocabulary.service.rdf.RDFMapper; -import org.apache.commons.io.IOUtils; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.PagedModel; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -23,7 +20,6 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -33,13 +29,11 @@ public class LanguageController { private final LanguageDTOManager manager; private final Manager managerEntity; private final LanguageAssembler assembler; - private final RDFMapper rdfMapper; - public LanguageController(LanguageDTOManager managerDTO, Manager managerEntity, LanguageAssembler assembler, RDFMapper rdfMapper) { + public LanguageController(LanguageDTOManager managerDTO, Manager managerEntity, LanguageAssembler assembler) { this.manager = managerDTO; this.managerEntity = managerEntity; this.assembler = assembler; - this.rdfMapper = rdfMapper; } @GetMapping("/languages") @@ -57,38 +51,6 @@ public EntityModel findByAbbreviation(@PathVariable String abbreviatio return assembler.toModel(manager.find(abbreviation)); } - @GetMapping(value = "/languages/{id}", produces = {"application/rdf+xml"}) - public String oneAsRdfXml(@PathVariable long id) { - return rdfMapper.toRDFXML(managerEntity.get(id)); - } - - @GetMapping( - value = "/languages/{id}/export/rdfxml", - produces = MediaType.APPLICATION_OCTET_STREAM_VALUE - ) - public @ResponseBody ResponseEntity exportAsRdfXml(@PathVariable long id) { - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType("application/octet-stream")) - .header("Content-disposition", "attachment; filename=\"language_" + id + ".rdf\"") - .body(IOUtils.toByteArray(rdfMapper.toRDFXML(managerEntity.get(id)))); - } - - @GetMapping(value = "/languages/{id}", produces = {"application/n-triples", "text/turtle"}) - public String oneAsRdfTurtle(@PathVariable long id) { - return rdfMapper.toRDFTurtle(managerEntity.get(id)); - } - - @GetMapping( - value = "/languages/{id}/export/turtle", - produces = MediaType.APPLICATION_OCTET_STREAM_VALUE - ) - public @ResponseBody ResponseEntity exportAsRdfTurtle(@PathVariable long id) { - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType("application/octet-stream")) - .header("Content-disposition", "attachment; filename=\"language_" + id + ".ttl\"") - .body(IOUtils.toByteArray(rdfMapper.toRDFTurtle(managerEntity.get(id)))); - } - @PostMapping("/languages") @ResponseStatus(HttpStatus.CREATED) public EntityModel create(@RequestBody Language newLanguage) throws VocabularyException { diff --git a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapper.java b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapper.java index e50c3b3..d473fae 100644 --- a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapper.java +++ b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapper.java @@ -1,16 +1,11 @@ package io.goobi.vocabulary.service.rdf; -import io.goobi.vocabulary.model.jpa.LanguageEntity; import io.goobi.vocabulary.model.jpa.VocabularyEntity; public interface RDFMapper { boolean isRDFCompatible(VocabularyEntity entity); - String toRDFXML(LanguageEntity entity); - String toRDFXML(VocabularyEntity entity); - String toRDFTurtle(LanguageEntity entity); - String toRDFTurtle(VocabularyEntity entity); } diff --git a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java index d2669c9..9d8db80 100644 --- a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java +++ b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java @@ -1,6 +1,5 @@ package io.goobi.vocabulary.service.rdf; -import io.goobi.vocabulary.api.LanguageController; import io.goobi.vocabulary.api.VocabularyController; import io.goobi.vocabulary.api.VocabularyRecordController; import io.goobi.vocabulary.exception.MappingException; @@ -9,10 +8,8 @@ import io.goobi.vocabulary.model.jpa.FieldTranslationEntity; import io.goobi.vocabulary.model.jpa.FieldTypeEntity; import io.goobi.vocabulary.model.jpa.FieldValueEntity; -import io.goobi.vocabulary.model.jpa.LanguageEntity; import io.goobi.vocabulary.model.jpa.VocabularyEntity; import io.goobi.vocabulary.model.jpa.VocabularyRecordEntity; -import io.goobi.vocabulary.service.rdf.vocabulary.LANGUAGE; import org.apache.jena.datatypes.xsd.XSDDatatype; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; @@ -104,21 +101,11 @@ public boolean isRDFCompatible(VocabularyEntity entity) { } } - @Override - public String toRDFXML(LanguageEntity entity) { - return transform(entity, this::generateLanguageModel, RDF_XML_SYNTAX); - } - @Override public String toRDFXML(VocabularyEntity entity) { return transform(entity, this::generateVocabularyModel, RDF_XML_SYNTAX); } - @Override - public String toRDFTurtle(LanguageEntity entity) { - return transform(entity, this::generateLanguageModel, RDF_TURTLE_SYNTAX); - } - @Override public String toRDFTurtle(VocabularyEntity entity) { return transform(entity, this::generateVocabularyModel, RDF_TURTLE_SYNTAX); @@ -292,16 +279,4 @@ private void createSkosInSchemePropertyIfApplicable(Model model, List !r.isMetadata() && r.getId() != topRecordId) .forEach(r -> recordMap.get(r.getId()).addProperty(SKOS.inScheme, topElement)); } - - private Model generateLanguageModel(LanguageEntity entity) { - String uri = generateURIForId(LanguageController.class, entity.getId()); - - Model model = ModelFactory.createDefaultModel(); - - Resource resource = model.createResource(uri) - .addProperty(LANGUAGE.NAME, entity.getName()) - .addProperty(LANGUAGE.ABBREVIATION, entity.getAbbreviation()); - - return model; - } } From 721fa1ab0792abf37df57d1a1f61a9a12a4c780d Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Wed, 28 Aug 2024 10:10:09 +0200 Subject: [PATCH 08/19] fix: always auto generate URI based on incoming request --- .../vocabulary/service/rdf/RDFMapperImpl.java | 20 +++++++++++-------- .../src/main/resources/application.properties | 2 -- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java index 9d8db80..99dc8d9 100644 --- a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java +++ b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java @@ -22,10 +22,11 @@ import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.SKOS; import org.apache.jena.vocabulary.XSD; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponents; import java.io.StringWriter; import java.lang.reflect.Method; @@ -36,12 +37,6 @@ @Service public class RDFMapperImpl implements RDFMapper { - @Value("${vocabulary-server.base-url}") - private String host; - - @Value("${server.port}") - private int port; - public static final RDFFormat RDF_XML_SYNTAX = RDFFormat.RDFXML; public static final RDFFormat RDF_TURTLE_SYNTAX = RDFFormat.TURTLE_BLOCKS; @@ -53,15 +48,24 @@ private String transform(T entity, EntityToModelMappingFunction function, private String generateURIForId(Class clazz, long id) { try { + String baseUrl = extractBaseURI(); String classRoute = extractClassEndpoint(clazz); String methodRoute = extractMethodEndpoint(clazz.getMethod("one", long.class)); String endpoint = classRoute + methodRoute.replace("{id}", Long.toString(id)); - return host + ':' + port + endpoint; + return baseUrl + endpoint; } catch (NoSuchMethodException e) { throw new MappingException(clazz, String.class, e); } } + private String extractBaseURI() { + UriComponents uriComponents = ServletUriComponentsBuilder.fromCurrentRequest().build(); + String scheme = uriComponents.getScheme(); + String host = uriComponents.getHost(); + String port = String.valueOf(uriComponents.getPort()); + return scheme + "://" + host + ':' + port; + } + private static String extractClassEndpoint(Class clazz) throws NoSuchMethodException { String[] values = clazz.getAnnotation(RequestMapping.class).value(); assert (values.length == 1); diff --git a/module-core/src/main/resources/application.properties b/module-core/src/main/resources/application.properties index b4d56cc..82ae64f 100644 --- a/module-core/src/main/resources/application.properties +++ b/module-core/src/main/resources/application.properties @@ -7,8 +7,6 @@ server.address=127.0.0.1 # Basic configuration -# The base url of the vocabulary server (which depends on external factors), it is used it generate valid API reference links -vocabulary-server.base-url=http://localhost # The port the vocabulary server should listen on server.port=8081 # The name of the application, can be customized but shouldn't affect anything From 76b8d651a8a4ef0eae386035b3ced2da7b78ddd5 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Wed, 28 Aug 2024 10:15:45 +0200 Subject: [PATCH 09/19] doc: removed base-url configuration option --- docs/de/migration.md | 4 +--- docs/en/setup.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/de/migration.md b/docs/de/migration.md index 584d71d..cedb11e 100644 --- a/docs/de/migration.md +++ b/docs/de/migration.md @@ -148,9 +148,7 @@ curl http://localhost:8081/api/v1/records/1 | jq ``` Das JSON-Element `_links` sollte Verweise auf andere Ressourcen enthalten. Diese URLs sollten gültig und auflösbar sein. -Wenn Sie keinen dieser Verweise öffnen können, überprüfen Sie die Konfiguration des Vokabularservers (Konfigurationsoption `vocabulary-server.base-url`). -Bei Problemen mit diesen URLs müssen die Daten nicht neu importiert werden. -Aktualisieren Sie einfach die Konfigurationsdatei und starten Sie den Vokabularserver neu, damit die Änderungen wirksam werden. +Der Host-Teil dieser URLs wird aus der Anfrage generiert. ## Migration der Mets-Datei Dieser Schritt kann nur durchgeführt werden, wenn die Migration der Vokabulardaten erfolgreich abgeschlossen wurde! diff --git a/docs/en/setup.md b/docs/en/setup.md index 5490737..a769a46 100644 --- a/docs/en/setup.md +++ b/docs/en/setup.md @@ -66,6 +66,4 @@ curl http://localhost:8081/api/v1/records/1 | jq ``` The `_links` JSON element should contain references to other resources. These URLs should be valid and resolvable. -If you are unable to open any of these references, check the configuration of the vocabulary server (`vocabulary-server.base-url` configuration option). -Any issues regarding these URLs doesn't require a re-import of the data. -Just update the configuration file and restart the vocabulary server for the changes to take effect. +The host part of these URLs is generated from the request. From 24fd27e773c482ae709271b55dcfe2d6f4fc224c Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Wed, 28 Aug 2024 14:02:07 +0200 Subject: [PATCH 10/19] update: Spring Boot 3.3.2 -> 3.3.3 --- module-core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module-core/pom.xml b/module-core/pom.xml index 1d12c17..5baca47 100644 --- a/module-core/pom.xml +++ b/module-core/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.2 + 3.3.3 io.goobi.vocabulary From 9fcc63b6f80f736d1c66cf8412691f95b467b6d6 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Wed, 28 Aug 2024 15:18:08 +0200 Subject: [PATCH 11/19] draft: first working version of security --- module-core/pom.xml | 9 +++ .../security/BearerTokenAuthFilter.java | 65 +++++++++++++++++++ .../security/SecurityConfiguration.java | 38 +++++++++++ .../io/goobi/vocabulary/security/User.java | 19 ++++++ .../src/main/resources/application.properties | 1 + 5 files changed, 132 insertions(+) create mode 100644 module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java create mode 100644 module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java create mode 100644 module-core/src/main/java/io/goobi/vocabulary/security/User.java diff --git a/module-core/pom.xml b/module-core/pom.xml index 5baca47..bd8769b 100644 --- a/module-core/pom.xml +++ b/module-core/pom.xml @@ -46,6 +46,15 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + com.fasterxml.jackson.dataformat jackson-dataformat-xml diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java new file mode 100644 index 0000000..d715536 --- /dev/null +++ b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java @@ -0,0 +1,65 @@ +package io.goobi.vocabulary.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class BearerTokenAuthFilter extends OncePerRequestFilter { + @Value("${security.bearer.token}") + private String secretToken; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + final String authHeader = request.getHeader("Authorization"); + + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + try { + final String jwt = authHeader.substring(7); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null) { + User user = new User(); + + if (isTokenValid(jwt) || isPublic(request)) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + user, + null, + user.getAuthorities() + ); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + + filterChain.doFilter(request, response); + } catch (Exception exception) { +// handlerExceptionResolver.resolveException(request, response, null, exception); + exception.printStackTrace(); + } + } + + private boolean isPublic(HttpServletRequest request) { + return "GET".equals(request.getMethod()); + } + + private boolean isTokenValid(String accessToken) { + return secretToken.equals(accessToken); + } +} diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java b/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java new file mode 100644 index 0000000..6a7aace --- /dev/null +++ b/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java @@ -0,0 +1,38 @@ +package io.goobi.vocabulary.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + private final BearerTokenAuthFilter bearerTokenAuthFilter; + + public SecurityConfiguration(BearerTokenAuthFilter bearerTokenAuthFilter) { + this.bearerTokenAuthFilter = bearerTokenAuthFilter; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.csrf() + .disable() + .authorizeHttpRequests() + .requestMatchers("/auth/**") + .permitAll() + .anyRequest() + .authenticated() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() +// .authenticationProvider(authenticationProvider) + .addFilterBefore(bearerTokenAuthFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/User.java b/module-core/src/main/java/io/goobi/vocabulary/security/User.java new file mode 100644 index 0000000..1a71e90 --- /dev/null +++ b/module-core/src/main/java/io/goobi/vocabulary/security/User.java @@ -0,0 +1,19 @@ +package io.goobi.vocabulary.security; + +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + +@Data +public class User implements UserDetails { + private String username; + private String password; + + @Override + public Collection getAuthorities() { + return List.of(); + } +} diff --git a/module-core/src/main/resources/application.properties b/module-core/src/main/resources/application.properties index 82ae64f..503e1c5 100644 --- a/module-core/src/main/resources/application.properties +++ b/module-core/src/main/resources/application.properties @@ -5,6 +5,7 @@ # Only listen on local address. Remove this line, if you want to open the vocabulary server to the public. # ATTENTION: Currently, there is no security in the vocabulary server. Every caller of the API can do anything! server.address=127.0.0.1 +security.bearer.token=secret # Basic configuration # The port the vocabulary server should listen on From 14194dd04629a41e88e660f249ef25b918d67264 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Mon, 2 Sep 2024 10:12:25 +0200 Subject: [PATCH 12/19] task: finish draft token implementation --- docs/de/setup.md | 4 ++++ .../security/BearerTokenAuthFilter.java | 21 ++++++++++--------- .../src/main/resources/application.properties | 5 +++-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/de/setup.md b/docs/de/setup.md index 0091c0f..d7c0909 100644 --- a/docs/de/setup.md +++ b/docs/de/setup.md @@ -7,6 +7,7 @@ Diese Dokumentation beschreibt den Prozess der Installation und Ersteinrichtung - Passen Sie die Konfigurationsdatei entsprechend Ihrer Konfiguration an und entfernen Sie nicht geänderte Zeilen. - Datenbankanmeldeinformationen und Datenbankname. - Basis-URL und Port. + - Sicherheitstoken (dieses muss identisch auch in Goobi konfiguriert werden). - Erstellen Sie ein Systemd-Service, um den Dienst automatisch zu starten. ## Einrichtung von Goobi Workflow zur Kommunikation mit dem Vokabularserver @@ -27,6 +28,7 @@ Diese Dokumentation beschreibt den Prozess der Installation und Ersteinrichtung Für die obigen drei Punkte unter Ubuntu: ```bash export VOC_PORT=8081 +export VOC_TOKEN=supersecret export VOC_PATH=/opt/digiverso/vocabulary export VOC_USER=vocabulary export VOC_SQL_USER=${VOC_USER} @@ -49,6 +51,7 @@ sudo cp -ait ${VOC_PATH} /tmp/goobi-vocabulary-server/migration # download and set up the config file wget https://github.com/intranda/goobi-vocabulary-server/releases/latest/download/application.properties -O - | sudo tee ${VOC_PATH}/application.properties >/dev/null sudo sed -re "s|^(server.port=).*|\1${VOC_PORT}|" \ + -e "s|^(security.token=).*|\1${VOC_TOKEN}|" \ -e "s|^(spring.datasource.username=).*|\1${VOC_SQL_USER}|" \ -e "s|^(spring.datasource.password=).*|\1${PW_SQL_VOC}|" \ -e "s|^(spring.datasource.url=).*|\1jdbc:mariadb://localhost:3306/${VOC_SQL_DB}|" \ @@ -90,6 +93,7 @@ sudo mysql -e "CREATE DATABASE ${VOC_SQL_DB}; # append vocabulary server address to the Goobi workflow config grep ^vocabularyServerHost= /opt/digiverso/goobi/config/goobi_config.properties || echo "vocabularyServerHost=localhost" | sudo tee -a /opt/digiverso/goobi/config/goobi_config.properties grep ^vocabularyServerPort= /opt/digiverso/goobi/config/goobi_config.properties || echo "vocabularyServerPort=${VOC_PORT}" | sudo tee -a /opt/digiverso/goobi/config/goobi_config.properties +grep ^vocabularyServerToken= /opt/digiverso/goobi/config/goobi_config.properties || echo "vocabularyServerToken=${VOC_TOKEN}" | sudo tee -a /opt/digiverso/goobi/config/goobi_config.properties # start the vocabulary server sudo systemctl start vocabulary.service diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java index d715536..95b0d40 100644 --- a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java +++ b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java @@ -13,30 +13,30 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.Optional; @Component public class BearerTokenAuthFilter extends OncePerRequestFilter { - @Value("${security.bearer.token}") + @Value("${security.token}") private String secretToken; + @Value("${security.anonymous.read-allowed}") + private boolean anonymousReadAllowed; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - filterChain.doFilter(request, response); - return; - } - try { - final String jwt = authHeader.substring(7); + final String token = authHeader != null && authHeader.startsWith("Bearer ") ? authHeader.substring(7) : null; + final Optional jwt = Optional.ofNullable(token); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { User user = new User(); - if (isTokenValid(jwt) || isPublic(request)) { + if (isPublic(request) || (jwt.isPresent() && isTokenValid(jwt.get()))) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( user, null, @@ -51,12 +51,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse filterChain.doFilter(request, response); } catch (Exception exception) { // handlerExceptionResolver.resolveException(request, response, null, exception); - exception.printStackTrace(); +// exception.printStackTrace(); } } private boolean isPublic(HttpServletRequest request) { - return "GET".equals(request.getMethod()); + // TODO: Vocabulary filtering + return anonymousReadAllowed && "GET".equals(request.getMethod()); } private boolean isTokenValid(String accessToken) { diff --git a/module-core/src/main/resources/application.properties b/module-core/src/main/resources/application.properties index 503e1c5..74cbf28 100644 --- a/module-core/src/main/resources/application.properties +++ b/module-core/src/main/resources/application.properties @@ -4,8 +4,9 @@ # Security # Only listen on local address. Remove this line, if you want to open the vocabulary server to the public. # ATTENTION: Currently, there is no security in the vocabulary server. Every caller of the API can do anything! -server.address=127.0.0.1 -security.bearer.token=secret +#server.address=127.0.0.1 +security.token=secret +security.anonymous.read-allowed=false # Basic configuration # The port the vocabulary server should listen on From 7e00458860086e53adf7bdd4167608a58879b582 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Mon, 2 Sep 2024 10:33:10 +0200 Subject: [PATCH 13/19] fix: test configuration --- module-core/src/test/resources/application.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module-core/src/test/resources/application.properties b/module-core/src/test/resources/application.properties index 58223d9..2bed3c5 100644 --- a/module-core/src/test/resources/application.properties +++ b/module-core/src/test/resources/application.properties @@ -1,4 +1,5 @@ -vocabulary-server.base-url=http://localhost +security.token=secret +security.anonymous.read-allowed=false spring.application.name=Vocabulary-Server server.port=8081 spring.datasource.username=goobi From 21d3becf2eaf982a000e8be3ec6dcfb15722495b Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Mon, 2 Sep 2024 12:05:13 +0200 Subject: [PATCH 14/19] task: add token logic to default script and migration tool --- install/default_setup.sh | 3 ++- migration/lib/api.py | 4 +++- migration/metadata-migrator.py | 4 +++- migration/vocabulary-migrator.py | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/install/default_setup.sh b/install/default_setup.sh index a32b4ee..93d8967 100755 --- a/install/default_setup.sh +++ b/install/default_setup.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash HOST='localhost:8081/api/v1' +TOKEN='secret' curl_call() { - curl --location "$HOST/$1" --header 'Content-Type: application/json' --data "$2" + curl --location "$HOST/$1" --header 'Content-Type: application/json' --header "Authorization: Bearer $TOKEN" --data "$2" } create_language() { diff --git a/migration/lib/api.py b/migration/lib/api.py index 9dcfa9c..fd8cd69 100644 --- a/migration/lib/api.py +++ b/migration/lib/api.py @@ -30,7 +30,7 @@ SCHEMA_LOOKUP = 9 class API: - def __init__(self, host, port): + def __init__(self, host, port, token): self.urls = {} self.urls[SCHEMA_INSERTION] = SCHEMA_INSERTION_URL self.urls[VOCABULARY_INSERTION] = VOCABULARY_INSERTION_URL @@ -45,6 +45,8 @@ def __init__(self, host, port): self.type_counter = 1 for u in self.urls: self.urls[u] = self.urls[u].replace('{{HOST}}', host).replace('{{PORT}}', port) + if token != None: + HEADERS['Authorization'] = f'Bearer {token}' def set_context(self, ctx): self.ctx = ctx diff --git a/migration/metadata-migrator.py b/migration/metadata-migrator.py index a0afb5a..643ba90 100644 --- a/migration/metadata-migrator.py +++ b/migration/metadata-migrator.py @@ -11,7 +11,8 @@ def main(): api = API( args.vocabulary_server_host, - args.vocabulary_server_port + args.vocabulary_server_port, + args.vocabulary_server_token ) ctx = Context(api, args.dry, args.verbose, args.continue_on_error, args.metadata_directory, args.mapping_file, args.preferred_mets_main_value_language, args.manual_id_fix) @@ -36,6 +37,7 @@ def parse_args(): parser.add_argument('--mapping-file', '-m', required=True, help='vocabulary and record mapping file') parser.add_argument('--vocabulary-server-host', type=str, default='localhost', help='vocabulary server host') parser.add_argument('--vocabulary-server-port', type=str, default='8081', help='vocabulary server port') + parser.add_argument('--vocabulary-server-token', type=str, default=None, help='vocabulary server security token') parser.add_argument('--preferred-mets-main-value-language', type=str, default='eng', help='Default language to use for mets value writing, if present and prior value invalid') parser.add_argument('--manual-id-fix', type=str, default=None, help='Manually fix the record ID of elements whose name attribute matches this parameter. Caution, this must not be executed twice!') parser.add_argument('--log', required=False, default='INFO', help='logger level (possible values are: NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL)') diff --git a/migration/vocabulary-migrator.py b/migration/vocabulary-migrator.py index 1fffe11..99c8d9a 100644 --- a/migration/vocabulary-migrator.py +++ b/migration/vocabulary-migrator.py @@ -19,7 +19,8 @@ def main(): ) api = API( args.vocabulary_server_host, - args.vocabulary_server_port + args.vocabulary_server_port, + args.vocabulary_server_token ) ctx = Context(db, api, args.dry, args.fallback_language, args.continue_on_error, args.lookup_file_directory) api.set_context(ctx) @@ -45,6 +46,7 @@ def parse_args(): parser.add_argument('--dry', type=str, required=False, help='Don\'t call the API but instead write the API calls to a file, provide the filename as a parameter') parser.add_argument('--vocabulary-server-host', type=str, default='localhost', help='vocabulary server host') parser.add_argument('--vocabulary-server-port', type=str, default='8081', help='vocabulary server port') + parser.add_argument('--vocabulary-server-token', type=str, default=None, help='vocabulary server security token') parser.add_argument('--goobi-database-host', type=str, default='localhost', help='Goobi database host') parser.add_argument('--goobi-database-port', type=str, default='3306', help='Goobi database port') parser.add_argument('--goobi-database-name', type=str, default='goobi', help='Goobi database name') From 5394b31ef24ac5f865043c75227030559f849295 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Thu, 5 Sep 2024 08:26:51 +0200 Subject: [PATCH 15/19] fix: excel export security bug --- .../security/BearerTokenAuthFilter.java | 43 ++++++++++--------- .../security/SecurityConfiguration.java | 24 +++++------ 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java index 95b0d40..69335ba 100644 --- a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java +++ b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java @@ -5,6 +5,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -23,36 +25,37 @@ public class BearerTokenAuthFilter extends OncePerRequestFilter { @Value("${security.anonymous.read-allowed}") private boolean anonymousReadAllowed; + @Bean + public FilterRegistrationBean bearerTokenAuthFilterFilterRegistrationBean(BearerTokenAuthFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); + registration.setEnabled(false); + return registration; + } + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); - try { - final String token = authHeader != null && authHeader.startsWith("Bearer ") ? authHeader.substring(7) : null; - final Optional jwt = Optional.ofNullable(token); + final String token = authHeader != null && authHeader.startsWith("Bearer ") ? authHeader.substring(7) : null; + final Optional jwt = Optional.ofNullable(token); - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null) { - User user = new User(); + if (authentication == null) { + User user = new User(); - if (isPublic(request) || (jwt.isPresent() && isTokenValid(jwt.get()))) { - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - user, - null, - user.getAuthorities() - ); + if (isPublic(request) || (jwt.isPresent() && isTokenValid(jwt.get()))) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + user, + null, + user.getAuthorities() + ); - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authToken); - } + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); } - - filterChain.doFilter(request, response); - } catch (Exception exception) { -// handlerExceptionResolver.resolveException(request, response, null, exception); -// exception.printStackTrace(); } + filterChain.doFilter(request, response); } private boolean isPublic(HttpServletRequest request) { diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java b/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java index 6a7aace..b1027cc 100644 --- a/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java +++ b/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java @@ -1,9 +1,11 @@ package io.goobi.vocabulary.security; +import jakarta.servlet.DispatcherType; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -19,18 +21,16 @@ public SecurityConfiguration(BearerTokenAuthFilter bearerTokenAuthFilter) { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http.csrf() - .disable() - .authorizeHttpRequests() - .requestMatchers("/auth/**") - .permitAll() - .anyRequest() - .authenticated() - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() -// .authenticationProvider(authenticationProvider) + http.csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth + .dispatcherTypeMatchers(DispatcherType.ASYNC) + .permitAll() + .anyRequest() + .authenticated() + ) + .sessionManagement(sess -> sess + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) .addFilterBefore(bearerTokenAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); From b8024391b0eb1200a519f405abdeac32f9d97d1e Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Thu, 5 Sep 2024 08:27:12 +0200 Subject: [PATCH 16/19] debug: add debugging output for security messages --- module-core/src/main/resources/application.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/module-core/src/main/resources/application.properties b/module-core/src/main/resources/application.properties index 74cbf28..e419564 100644 --- a/module-core/src/main/resources/application.properties +++ b/module-core/src/main/resources/application.properties @@ -32,6 +32,7 @@ springdoc.swagger-ui.path=/docs/ui #spring.jpa.show-sql=true #logging.level.org.hibernate.orm.jdbc.bind=TRACE #logging.level.org.hibernate.SQL=TRACE +#logging.level.org.springframework.security=TRACE # DO NOT TOUCH THESE UNLESS YOU KNOW WHAT TO DO spring.datasource.driver-class-name=org.mariadb.jdbc.Driver From 338ea61612c918ebf190e5e7af63c2246fc7dad1 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Thu, 5 Sep 2024 11:54:34 +0200 Subject: [PATCH 17/19] fix: deny access by default and remove default access token --- .../goobi/vocabulary/security/BearerTokenAuthFilter.java | 8 ++++++-- module-core/src/main/resources/application.properties | 8 +++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java index 69335ba..62b0f4c 100644 --- a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java +++ b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java @@ -19,10 +19,10 @@ @Component public class BearerTokenAuthFilter extends OncePerRequestFilter { - @Value("${security.token}") + @Value("${security.token:#{null}") private String secretToken; - @Value("${security.anonymous.read-allowed}") + @Value("${security.anonymous.read-allowed:false}") private boolean anonymousReadAllowed; @Bean @@ -64,6 +64,10 @@ private boolean isPublic(HttpServletRequest request) { } private boolean isTokenValid(String accessToken) { + // If secret token is not set, deny + if (secretToken == null) { + return false; + } return secretToken.equals(accessToken); } } diff --git a/module-core/src/main/resources/application.properties b/module-core/src/main/resources/application.properties index e419564..d368c74 100644 --- a/module-core/src/main/resources/application.properties +++ b/module-core/src/main/resources/application.properties @@ -3,10 +3,12 @@ # Security # Only listen on local address. Remove this line, if you want to open the vocabulary server to the public. -# ATTENTION: Currently, there is no security in the vocabulary server. Every caller of the API can do anything! #server.address=127.0.0.1 -security.token=secret -security.anonymous.read-allowed=false + +# Set a security token! If not set, you won't be able to make modifying API calls +#security.token=secret +# Control anonymous read operations. If set to false or not set, anonymous readers will not get access +#security.anonymous.read-allowed=true # Basic configuration # The port the vocabulary server should listen on From bcdaec474fed54ccffdf33125987bd8bb1d9ad70 Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Thu, 5 Sep 2024 12:02:46 +0200 Subject: [PATCH 18/19] fix: typo in value binding --- .../io/goobi/vocabulary/security/BearerTokenAuthFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java index 62b0f4c..0ef7193 100644 --- a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java +++ b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java @@ -19,7 +19,7 @@ @Component public class BearerTokenAuthFilter extends OncePerRequestFilter { - @Value("${security.token:#{null}") + @Value("${security.token:#{null}}") private String secretToken; @Value("${security.anonymous.read-allowed:false}") From e6e407fda11b9fee7b59d9378b81aa36fac1e12a Mon Sep 17 00:00:00 2001 From: Dominick Leppich Date: Thu, 5 Sep 2024 13:33:26 +0200 Subject: [PATCH 19/19] release v1.1.0 --- module-core/pom.xml | 6 ++---- module-exchange/pom.xml | 6 ++---- pom.xml | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/module-core/pom.xml b/module-core/pom.xml index bd8769b..20563fb 100644 --- a/module-core/pom.xml +++ b/module-core/pom.xml @@ -10,13 +10,11 @@ io.goobi.vocabulary vocabulary-server-core - 1.0.1-SNAPSHOT + 1.1.0 Vocabulary-Server-Core Spring Boot based RESTful web service for vocabulary management jar - 17 - 17 17 UTF-8 @@ -27,7 +25,7 @@ io.goobi.vocabulary vocabulary-server-exchange - 1.0.1-SNAPSHOT + 1.1.0 compile diff --git a/module-exchange/pom.xml b/module-exchange/pom.xml index aaac968..47af30c 100644 --- a/module-exchange/pom.xml +++ b/module-exchange/pom.xml @@ -4,14 +4,12 @@ 4.0.0 io.goobi.vocabulary vocabulary-server-exchange - 1.0.1-SNAPSHOT + 1.1.0 Vocabulary Exchange Vocabulary data exchange classes jar - 11 - 11 - 11 + 17 UTF-8 diff --git a/pom.xml b/pom.xml index 73f8c74..5e51df1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.goobi.vocabulary vocabulary-server - 1.0.1-SNAPSHOT + 1.1.0 Vocabulary-Server pom RESTful webservice for vocabulary management