diff --git a/.gitignore b/.gitignore index 5c33d5d87..cac4e5c65 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,7 @@ hs_err_pid* # node.js / frontend node_modules/ dist/ +cache/ +/frontend/datasafe-ui/package-lock.json +/datasafe-rest-impl/ROOT_BUCKET/ diff --git a/datasafe-encryption/datasafe-encryption-impl/pom.xml b/datasafe-encryption/datasafe-encryption-impl/pom.xml index dc95d9b60..fac2cbe00 100644 --- a/datasafe-encryption/datasafe-encryption-impl/pom.xml +++ b/datasafe-encryption/datasafe-encryption-impl/pom.xml @@ -98,24 +98,29 @@ test-jar test + com.fasterxml.jackson.core jackson-databind + ${jackson.version} test com.fasterxml.jackson.dataformat jackson-dataformat-yaml + ${jackson.version} test com.fasterxml.jackson.core jackson-annotations + ${jackson.version} test com.fasterxml.jackson.core jackson-core + ${jackson.version} test diff --git a/datasafe-rest-impl/1.createDockerimage.sh b/datasafe-rest-impl/1.createDockerimage.sh index ef7f3d866..539b68814 100755 --- a/datasafe-rest-impl/1.createDockerimage.sh +++ b/datasafe-rest-impl/1.createDockerimage.sh @@ -13,4 +13,4 @@ npm install ng build --deploy-url /static/ --base-href /static/ mv dist ../../datasafe-rest-impl/target/dist cd ../../datasafe-rest-impl -docker build . -t datasafe-rest-test:latest --build-arg JAR_FILE=datasafe-rest-impl-*.jar +docker build . -t datasafe-rest-test:latest --build-arg JAR_FILE=datasafe-rest-impl.jar diff --git a/datasafe-rest-impl/DEMO.md b/datasafe-rest-impl/DEMO.md index 9c6cf7dfd..e9095bf9e 100644 --- a/datasafe-rest-impl/DEMO.md +++ b/datasafe-rest-impl/DEMO.md @@ -19,7 +19,29 @@ To **run** demo: ## Building and running demo -### Building +### Run it with Docker Compose +The easiest way to run Datasafe Rest Application is using Docker Compose. By default, it works with filesystem with root +directory `datasafe-rest-impl/ROOT_BUCKET`. +Build it with: +``` bash +# build backend +mvn clean package + +# build frontend +cd frontend/datasafe-ui +npm i +ng build --deploy-url /static/ --base-href /static/ +mv dist ../../datasafe-rest-impl/target/dist + +# build image and start datasafe in docker +docker compose up datasafe +``` + +Frontend is available at http://localhost:8080/static/index.html + +### Alternatively you can use shell scripts to build and run datasafe with different types of storages + +#### Building - Build from sources @@ -34,7 +56,7 @@ cd datasafe-rest-impl docker pull adorsys/datasafe && docker tag adorsys/datasafe datasafe-rest-test:latest ``` -### Running +#### Running Run using local filesystem, all data will be stored in `target/ROOT_BUCKET` folder: ```bash diff --git a/datasafe-rest-impl/Dockerfile b/datasafe-rest-impl/Dockerfile index 272e36b06..e4aaeddf0 100644 --- a/datasafe-rest-impl/Dockerfile +++ b/datasafe-rest-impl/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:8-jre-alpine +FROM ibm-semeru-runtimes:open-21-jre ARG JAR_FILE ENV JAR_FILE ${JAR_FILE} diff --git a/datasafe-rest-impl/compose.yml b/datasafe-rest-impl/compose.yml new file mode 100644 index 000000000..84345fe71 --- /dev/null +++ b/datasafe-rest-impl/compose.yml @@ -0,0 +1,31 @@ +services: + datasafe: + build: + args: + JAR_FILE: "datasafe-rest-impl.jar" +# image: adorsys/datasafe + environment: + EXPOSE_API_CREDS: true + DEFAULT_USER: username + DEFAULT_PASSWORD: password + USE_FILESYSTEM: file:///usr/app/ROOT_BUCKET + ports: + - 8080:8080 + volumes: + - "./ROOT_BUCKET:/usr/app/ROOT_BUCKET" + db: + image: mysql:5.7 + restart: always + environment: + MYSQL_DATABASE: 'test_db' + MYSQL_USER: 'test' + MYSQL_PASSWORD: 'test' + MYSQL_ROOT_PASSWORD: 'password' + ports: + - '3306:3306' + expose: + - '3306' + volumes: + - my-db:/var/lib/mysql +volumes: + my-db: diff --git a/datasafe-rest-impl/datasafe-deployment.yaml b/datasafe-rest-impl/datasafe-deployment.yaml deleted file mode 100644 index 4fc6b48ac..000000000 --- a/datasafe-rest-impl/datasafe-deployment.yaml +++ /dev/null @@ -1,40 +0,0 @@ -kind: "DeploymentConfig" -apiVersion: "v1" -metadata: - name: "datasafe-rest-service" -spec: - template: - metadata: - labels: - name: "datasafe-rest-service" - spec: - containers: - - name: "datasafe-rest-service" - image: "datasafe-rest-service:latest" - ports: - - containerPort: 8080 - protocol: "TCP" - - replicas: 1 - triggers: - - type: "ConfigChange" - - type: "ImageChange" - imageChangeParams: - automatic: true - containerNames: - - "datasafe-rest-service" - from: - kind: "ImageStreamTag" - name: "datasafe-rest-service:latest" - - type: "ImageChange" - imageChangeParams: - automatic: true - containerNames: - - "datasafe-rest-service" - from: - kind: "ImageStreamTag" - name: "datasafe:latest" - strategy: - type: "Rolling" - paused: false - revisionHistoryLimit: 2 \ No newline at end of file diff --git a/datasafe-rest-impl/datasafe-image.yaml b/datasafe-rest-impl/datasafe-image.yaml deleted file mode 100644 index bc6450fff..000000000 --- a/datasafe-rest-impl/datasafe-image.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: "v1" -kind: "ImageStream" -metadata: - name: "datasafe-rest-service" -spec: - dockerImageRepository: "datasafe-rest-service" \ No newline at end of file diff --git a/datasafe-rest-impl/datasafe.postman_collection.json b/datasafe-rest-impl/datasafe.postman_collection.json index 524b1dcfb..47698332a 100644 --- a/datasafe-rest-impl/datasafe.postman_collection.json +++ b/datasafe-rest-impl/datasafe.postman_collection.json @@ -1,690 +1,1170 @@ { - "info": { - "_postman_id": "54d63510-b402-4a2c-b518-50e79a04d569", - "name": "Datasafe", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "authenticate", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\"userName\":\"username\",\"password\":\"password\"}" - }, - "url": { - "raw": "{{host}}/api/authenticate", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "authenticate" - ] - } - }, - "response": [] - }, - { - "name": "create user", - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"userName\":\"ver\",\n\t\"password\":\"ver\"\n}" - }, - "url": { - "raw": "{{host}}/user", - "host": [ - "{{host}}" - ], - "path": [ - "user" - ] - } - }, - "response": [] - }, - { - "name": "delete user", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "user", - "value": "Max", - "type": "text" - }, - { - "key": "password", - "value": "123", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/user/", - "host": [ - "{{host}}" - ], - "path": [ - "user", - "" - ] - } - }, - "response": [] - }, - { - "name": "store document", - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/octet-stream", - "type": "text" - }, - { - "key": "user", - "value": "ver", - "type": "text" - }, - { - "key": "password", - "value": "ver", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "body": { - "mode": "file", - "file": { - "src": "/Users/maxim/tx/store/a.txt" - } - }, - "url": { - "raw": "{{host}}/document/test11.txt", - "host": [ - "{{host}}" - ], - "path": [ - "document", - "test11.txt" - ] - } - }, - "response": [] - }, - { - "name": "list documents", - "request": { - "method": "GET", - "header": [ - { - "key": "user", - "value": "vvv", - "type": "text" - }, - { - "key": "password", - "value": "vvv", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/documents/", - "host": [ - "{{host}}" - ], - "path": [ - "documents", - "" - ] - } - }, - "response": [] - }, - { - "name": "read document", - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/octet-stream", - "type": "text" - }, - { - "key": "user", - "value": "ver", - "type": "text" - }, - { - "key": "password", - "value": "ver", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/document/test11.txt", - "host": [ - "{{host}}" - ], - "path": [ - "document", - "test11.txt" - ] - } - }, - "response": [ - { - "name": "read document", - "originalRequest": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/octet-stream", - "type": "text" - }, - { - "key": "user", - "value": "ddd", - "type": "text" - }, - { - "key": "password", - "value": "ddd", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/document/deep/path/test.txt", - "host": [ - "{{host}}" - ], - "path": [ - "document", - "deep", - "path", - "test.txt" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "plain", - "header": [ - { - "key": "Transfer-Encoding", - "value": "chunked" - }, - { - "key": "Date", - "value": "Fri, 24 May 2019 11:50:22 GMT" - } - ], - "cookie": [], - "body": "test" - } - ] - }, - { - "name": "delete document", - "request": { - "method": "DELETE", - "header": [ - { - "key": "user", - "value": "iii", - "type": "text" - }, - { - "key": "password", - "value": "iii", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/document/deep/path/test2.txt", - "host": [ - "{{host}}" - ], - "path": [ - "document", - "deep", - "path", - "test2.txt" - ] - } - }, - "response": [] - }, - { - "name": "send to inbox", - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/octet-stream", - "type": "text" - }, - { - "key": "user", - "value": "ver", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "body": { - "mode": "file", - "file": { - "src": "/Users/maxim/test.txt" - } - }, - "url": { - "raw": "{{host}}/inbox/test2.txt", - "host": [ - "{{host}}" - ], - "path": [ - "inbox", - "test2.txt" - ] - } - }, - "response": [] - }, - { - "name": "inbox read", - "request": { - "method": "GET", - "header": [ - { - "key": "user", - "value": "ver", - "type": "text" - }, - { - "key": "password", - "value": "ver", - "type": "text" - }, - { - "key": "Accept", - "value": "application/octet-stream", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/inbox/test2.txt", - "host": [ - "{{host}}" - ], - "path": [ - "inbox", - "test2.txt" - ] - } - }, - "response": [] - }, - { - "name": "inbox delete", - "request": { - "method": "DELETE", - "header": [ - { - "key": "user", - "value": "iii", - "type": "text" - }, - { - "key": "password", - "value": "iii", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/inbox/test2.txt", - "host": [ - "{{host}}" - ], - "path": [ - "inbox", - "test2.txt" - ] - } - }, - "response": [] - }, - { - "name": "inbox list", - "request": { - "method": "GET", - "header": [ - { - "key": "user", - "value": "ver", - "type": "text" - }, - { - "key": "password", - "value": "ver", - "type": "text" - }, - { - "key": "Accept", - "value": "application/json", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/inbox/", - "host": [ - "{{host}}" - ], - "path": [ - "inbox", - "" - ] - } - }, - "response": [] - }, - { - "name": "version list", - "request": { - "method": "GET", - "header": [ - { - "key": "user", - "type": "text", - "value": "ver" - }, - { - "key": "password", - "type": "text", - "value": "ver" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - }, - { - "key": "Accept", - "value": "application/json", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/versions/list/", - "host": [ - "{{host}}" - ], - "path": [ - "versions", - "list", - "" - ] - } - }, - "response": [] - }, - { - "name": "versioned read", - "request": { - "method": "GET", - "header": [ - { - "key": "user", - "value": "ver", - "type": "text" - }, - { - "key": "password", - "value": "ver", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - }, - { - "key": "Accept", - "value": "application/octet-stream", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/versioned/test10.txt", - "host": [ - "{{host}}" - ], - "path": [ - "versioned", - "test10.txt" - ] - } - }, - "response": [] - }, - { - "name": "versioned delete", - "request": { - "method": "DELETE", - "header": [ - { - "key": "user", - "value": "ver", - "type": "text" - }, - { - "key": "password", - "value": "ver", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - }, - { - "key": "Accept", - "value": "application/json", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/versioned/test4.txt", - "host": [ - "{{host}}" - ], - "path": [ - "versioned", - "test4.txt" - ] - } - }, - "response": [] - }, - { - "name": "versioned write", - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/octet-stream", - "type": "text" - }, - { - "key": "user", - "value": "ver", - "type": "text" - }, - { - "key": "password", - "value": "ver", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - } - ], - "body": { - "mode": "file", - "file": { - "src": "/Users/maxim/parallel_transactions_merge_logic_fix.patch" - } - }, - "url": { - "raw": "{{host}}/versioned/test10.txt", - "host": [ - "{{host}}" - ], - "path": [ - "versioned", - "test10.txt" - ] - } - }, - "response": [] - }, - { - "name": "versioned list", - "request": { - "method": "GET", - "header": [ - { - "key": "user", - "value": "ver", - "type": "text" - }, - { - "key": "password", - "value": "ver", - "type": "text" - }, - { - "key": "token", - "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJzZWN1cmUtYXBpIiwiYXVkIjoic2VjdXJlLWFwcCIsInN1YiI6InVzZXJuYW1lIiwiZXhwIjoxNTYxMzA0MTA4LCJyb2wiOlsiUk9MRV9VU0VSIl19.Ungu5hob8mbwb7GfWfvqj1VsrNNv1pN6UEMJYJHSAHBicRlsYVbRAE7Ju_N1nnjdoZ6TSs4hireff92SkXvesQ", - "type": "text" - }, - { - "key": "Accept", - "value": "application/json", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/versioned/", - "host": [ - "{{host}}" - ], - "path": [ - "versioned", - "" - ] - } - }, - "response": [] - } - ] + "info": { + "_postman_id": "64bacc95-26af-47f0-8e77-a9767cc87c5d", + "name": "Datasafe", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "1072488", + "_collection_link": "https://www.postman.com/max402/workspace/datasafe/collection/1072488-64bacc95-26af-47f0-8e77-a9767cc87c5d?action=share&source=collection_link&creator=1072488" + }, + "item": [ + { + "name": "user", + "item": [ + { + "name": "authenticate", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "", + "pm.collectionVariables.set(\"token\", pm.response.headers.get('token'))", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text", + "disabled": true + }, + { + "key": "Origin", + "value": "http://localhost:8080", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\"userName\":\"username\",\"password\":\"password\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host}}/api/authenticate", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "authenticate" + ] + } + }, + "response": [] + }, + { + "name": "delete user Alice", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/user", + "host": [ + "{{host}}" + ], + "path": [ + "user" + ] + } + }, + "response": [] + }, + { + "name": "delete user Bob", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "user", + "value": "Bob", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/user", + "host": [ + "{{host}}" + ], + "path": [ + "user" + ] + } + }, + "response": [] + }, + { + "name": "create user Alice", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"userName\":\"Alice\",\n\t\"password\":\"123\"\n}" + }, + "url": { + "raw": "{{host}}/user", + "host": [ + "{{host}}" + ], + "path": [ + "user" + ] + } + }, + "response": [] + }, + { + "name": "create user Bob", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"userName\":\"Bob\",\n\t\"password\":\"123\"\n}" + }, + "url": { + "raw": "{{host}}/user", + "host": [ + "{{host}}" + ], + "path": [ + "user" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "private", + "item": [ + { + "name": "store document", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text" + }, + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "postman-cloud:///1eefbe22-4f28-4bc0-8682-38c006661c24" + } + ] + }, + "url": { + "raw": "{{host}}/document", + "host": [ + "{{host}}" + ], + "path": [ + "document" + ] + } + }, + "response": [] + }, + { + "name": "list documents", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "", + "var rb = JSON.parse(responseBody);", + "", + "pm.test(\"response contains one file\", () => {", + " pm.expect(rb.length).to.eql(1);", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/documents", + "host": [ + "{{host}}" + ], + "path": [ + "documents" + ] + } + }, + "response": [] + }, + { + "name": "read document", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "", + "pm.test(\"file content is read\", () => {", + " pm.expect(responseBody).to.eql(\"hello world!\");", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/octet-stream", + "type": "text" + }, + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/document/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "document", + "test.txt" + ] + } + }, + "response": [] + }, + { + "name": "delete document", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/document/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "document", + "test.txt" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "versioned", + "item": [ + { + "name": "versioned write", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text" + }, + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "postman-cloud:///1eefbe22-4f28-4bc0-8682-38c006661c24" + } + ] + }, + "url": { + "raw": "{{host}}/versioned/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "versioned", + "test.txt" + ] + } + }, + "response": [] + }, + { + "name": "versioned write 2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data", + "type": "text" + }, + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "postman-cloud:///1eefbe32-6174-4030-8d62-d50e2f6b243a" + } + ] + }, + "url": { + "raw": "{{host}}/versioned/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "versioned", + "test.txt" + ] + } + }, + "response": [] + }, + { + "name": "versioned list (last version)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "", + "var rb = JSON.parse(responseBody);", + "", + "pm.test(\"response contains one file\", () => {", + " pm.expect(rb.length).to.eql(1);", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/versioned/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "versioned", + "test.txt" + ] + } + }, + "response": [] + }, + { + "name": "versions list", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "", + "var rb = JSON.parse(responseBody);", + "", + "pm.test(\"response contains one file\", () => {", + " pm.expect(rb.length).to.eql(2);", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "user", + "type": "text", + "value": "Alice" + }, + { + "key": "password", + "type": "text", + "value": "123" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/versions/list/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "versions", + "list", + "test.txt" + ] + } + }, + "response": [] + }, + { + "name": "versioned read (last version)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "", + "pm.test(\"file content is read\", () => {", + " pm.expect(responseBody).to.eql(\"yet another file\");", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + }, + { + "key": "Accept", + "value": "application/octet-stream", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/versioned/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "versioned", + "test.txt" + ] + } + }, + "response": [] + }, + { + "name": "versioned delete", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "user", + "value": "Alice", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/versioned/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "versioned", + "test.txt" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "inbox", + "item": [ + { + "name": "send to inbox", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "user", + "value": "Alice", + "type": "text", + "disabled": true + }, + { + "key": "password", + "value": "123", + "type": "text", + "disabled": true + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + }, + { + "key": "users", + "value": "Bob", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "postman-cloud:///1eefbe22-4f28-4bc0-8682-38c006661c24" + } + ] + }, + "url": { + "raw": "{{host}}/inbox/document", + "host": [ + "{{host}}" + ], + "path": [ + "inbox", + "document" + ] + } + }, + "response": [] + }, + { + "name": "inbox list", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "", + "var rb = JSON.parse(responseBody);", + "", + "pm.test(\"response contains one file\", () => {", + " pm.expect(rb.length).to.eql(1);", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "user", + "value": "Bob", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/inbox/documents", + "host": [ + "{{host}}" + ], + "path": [ + "inbox", + "documents" + ] + } + }, + "response": [] + }, + { + "name": "inbox read", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "", + "pm.test(\"file content is read\", () => {", + " pm.expect(responseBody).to.eql(\"hello world!\");", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "user", + "value": "Bob", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "Accept", + "value": "application/octet-stream", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/inbox/document/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "inbox", + "document", + "test.txt" + ] + } + }, + "response": [] + }, + { + "name": "inbox delete", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Success', function() {", + " pm.response.to.have.status(200)", + "})", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "user", + "value": "Bob", + "type": "text" + }, + { + "key": "password", + "value": "123", + "type": "text" + }, + { + "key": "token", + "value": "{{token}}", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/inbox/document/test.txt", + "host": [ + "{{host}}" + ], + "path": [ + "inbox", + "document", + "test.txt" + ] + } + }, + "response": [] + } + ] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "host", + "value": "http://localhost:8080", + "type": "string" + }, + { + "key": "token", + "value": "" + } + ] } \ No newline at end of file diff --git a/datasafe-rest-impl/docker-compose.yml b/datasafe-rest-impl/docker-compose.yml deleted file mode 100644 index 838c605fb..000000000 --- a/datasafe-rest-impl/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: '3.3' -services: - db: - image: mysql:5.7 - restart: always - environment: - MYSQL_DATABASE: 'test_db' - MYSQL_USER: 'test' - MYSQL_PASSWORD: 'test' - MYSQL_ROOT_PASSWORD: 'password' - ports: - - '3306:3306' - expose: - - '3306' - volumes: - - my-db:/var/lib/mysql -volumes: - my-db: diff --git a/datasafe-rest-impl/pom.xml b/datasafe-rest-impl/pom.xml index 666cbd40e..33dd14c4b 100644 --- a/datasafe-rest-impl/pom.xml +++ b/datasafe-rest-impl/pom.xml @@ -20,9 +20,15 @@ 2.2.4 1.6.0 3.0.2 + 2.3.0 + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc-openapi-starter-webmvc-ui.version} + de.adorsys datasafe-business @@ -157,7 +163,6 @@ true - ${project.artifactId}-${project.version} org.springframework.boot diff --git a/datasafe-rest-impl/run.sh b/datasafe-rest-impl/run.sh index d13c46e48..01eadff0c 100755 --- a/datasafe-rest-impl/run.sh +++ b/datasafe-rest-impl/run.sh @@ -7,7 +7,7 @@ if [[ -z "$API_URL" ]]; then fi # Bind API url and credentials, sed -i won't work because of OC user permissions -sed 's!${API_URL}!'"$API_URL"'!g' "$APP_HOME/frontend/env.js" > /tmp/env.js && mv /tmp/env.js "$APP_HOME/frontend/env.js" +sed 's!${API_URL}!'"$API_URL"'!g' "$APP_HOME/frontend/env.prod.js" > /tmp/env.js && mv /tmp/env.js "$APP_HOME/frontend/env.js" # do not expose sensitive data by default LOGIN="" diff --git a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/DatasafeRestApplication.java b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/DatasafeRestApplication.java index cf581b75a..bdb527dc8 100644 --- a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/DatasafeRestApplication.java +++ b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/DatasafeRestApplication.java @@ -1,22 +1,15 @@ package de.adorsys.datasafe.rest.impl; -import de.adorsys.datasafe.rest.impl.config.DatasafeProperties; -import de.adorsys.datasafe.rest.impl.security.SecurityProperties; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class}) -@EnableConfigurationProperties({DatasafeProperties.class, SecurityProperties.class}) public class DatasafeRestApplication { public static void main(String[] args) { - System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true"); SpringApplication.run(DatasafeRestApplication.class, args); } - } diff --git a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/config/DatasafeProperties.java b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/config/DatasafeProperties.java index ddd7e8e01..8df0e212d 100644 --- a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/config/DatasafeProperties.java +++ b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/config/DatasafeProperties.java @@ -3,9 +3,11 @@ import de.adorsys.datasafe.encrypiton.api.types.encryption.MutableEncryptionConfig; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; -@ConfigurationProperties(prefix = "datasafe") @Data +@Configuration +@ConfigurationProperties(prefix = "datasafe") public class DatasafeProperties { /** diff --git a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/config/MvcConfig.java b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/config/MvcConfig.java new file mode 100644 index 000000000..a0696d97a --- /dev/null +++ b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/config/MvcConfig.java @@ -0,0 +1,32 @@ +package de.adorsys.datasafe.rest.impl.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.NonNull; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +@Slf4j +@Configuration +@RequiredArgsConstructor +public class MvcConfig extends WebMvcConfigurationSupport { + + private final DatasafeProperties datasafeProperties; + + /** + * Register static resources - frontend UI. + */ + @Override + public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) { + if (!StringUtils.hasLength(datasafeProperties.getStaticResources())) { + return; + } + + log.info("Serving static resources from {} as /static/**", datasafeProperties.getStaticResources()); + registry + .addResourceHandler("/static/**") + .addResourceLocations(datasafeProperties.getStaticResources()); + } +} diff --git a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/InboxController.java b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/InboxController.java index 2967e61d8..0415df7b8 100644 --- a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/InboxController.java +++ b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/InboxController.java @@ -55,6 +55,7 @@ public class InboxController { public void writeToInbox(@RequestHeader Set users, @PathVariable String path, @RequestParam("file") MultipartFile file) { + path = path.replaceAll("^/", ""); Set toUsers = users.stream().map(UserID::new).collect(Collectors.toSet()); try (OutputStream os = dataSafeService.inboxService().write(WriteRequest.forDefaultPublic(toUsers, path)); InputStream is = file.getInputStream()) { @@ -72,6 +73,7 @@ public void readFromInbox(@RequestHeader String user, @RequestHeader String password, @PathVariable String path, HttpServletResponse response) { + path = path.replaceAll("^/", ""); UserIDAuth userIDAuth = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); PrivateResource resource = BasePrivateResource.forPrivate(path); // this is needed for swagger, produces is just a directive: @@ -91,6 +93,7 @@ public void readFromInbox(@RequestHeader String user, public void deleteFromInbox(@RequestHeader String user, @RequestHeader String password, @PathVariable String path) { + path = path.replaceAll("^/", ""); UserIDAuth userIDAuth = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); PrivateResource resource = BasePrivateResource.forPrivate(path); RemoveRequest request = RemoveRequest.forPrivate(userIDAuth, resource); @@ -105,6 +108,7 @@ public void deleteFromInbox(@RequestHeader String user, public List listInbox(@RequestHeader String user, @RequestHeader String password, @PathVariable(required = false) String path) { + path = path.replaceAll("^/", ""); UserIDAuth userIDAuth = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); path = Optional.ofNullable(path) .map(it -> it.replaceAll("^\\.$", "")) diff --git a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/security/SecurityConfig.java b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/security/SecurityConfig.java index ed5677088..b6bc4e98a 100644 --- a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/security/SecurityConfig.java +++ b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/security/SecurityConfig.java @@ -26,10 +26,13 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import static de.adorsys.datasafe.rest.impl.security.SecurityConstants.TOKEN_HEADER; +import static org.springframework.security.config.Customizer.withDefaults; @Configuration @EnableWebSecurity @@ -44,7 +47,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc, AuthenticationManager authenticationManager) throws Exception { - MvcRequestMatcher[] SWAGGER_RESOURCES = { + MvcRequestMatcher[] swaggerResources = { mvc.pattern("/v2/api-docs"), mvc.pattern("/configuration/ui"), mvc.pattern("/swagger-resources"), @@ -54,14 +57,13 @@ public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Buil mvc.pattern("/swagger-resources/configuration/ui"), mvc.pattern("/swagger-ui.html") }; - - http.cors(AbstractHttpConfigurer::disable) + http.cors(withDefaults()) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authz -> authz - .requestMatchers(SWAGGER_RESOURCES).permitAll() + .requestMatchers(swaggerResources).permitAll() .requestMatchers(mvc.pattern("/static/**")).permitAll() .requestMatchers(mvc.pattern(SecurityConstants.AUTH_LOGIN_URL)).permitAll() - .requestMatchers(mvc.pattern(HttpMethod.OPTIONS, "/**")).permitAll() + .requestMatchers(mvc.pattern(HttpMethod.GET, "/**")).permitAll() .anyRequest().authenticated() ) .addFilter(new JwtAuthorizationFilter(authenticationManager, securityProperties)) @@ -77,8 +79,8 @@ MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) { } @Bean - public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) { - UserDetails user = User.withDefaultPasswordEncoder() + public InMemoryUserDetailsManager userDetailsService(PasswordEncoder encoder) { + UserDetails user = User.builder().passwordEncoder(encoder::encode) .username(securityProperties.getDefaultUser()) .password(securityProperties.getDefaultPassword()) .authorities("ROLE_USER") @@ -101,19 +103,15 @@ public PasswordEncoder passwordEncoder() { } @Bean - public CorsConfigurationSource corsConfigurationSource() { + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of("http://localhost:4200")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); + configuration.addExposedHeader(TOKEN_HEADER); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - - CorsConfiguration authConfig = new CorsConfiguration().applyPermitDefaultValues(); - authConfig.addExposedHeader(TOKEN_HEADER); - source.registerCorsConfiguration(SecurityConstants.AUTH_LOGIN_URL, authConfig); - - CorsConfiguration globalConfig = new CorsConfiguration().applyPermitDefaultValues(); - globalConfig.addAllowedMethod(HttpMethod.OPTIONS); - globalConfig.addAllowedMethod(HttpMethod.PUT); - globalConfig.addAllowedMethod(HttpMethod.DELETE); - source.registerCorsConfiguration("/**", globalConfig); - + source.registerCorsConfiguration("/**", configuration); return source; } diff --git a/datasafe-rest-impl/src/main/resources/jwt-config.properties b/datasafe-rest-impl/src/main/resources/jwt-config.properties index 97cd74c18..d0b6f7474 100644 --- a/datasafe-rest-impl/src/main/resources/jwt-config.properties +++ b/datasafe-rest-impl/src/main/resources/jwt-config.properties @@ -1,5 +1,5 @@ -jwt_secret=${JWT_SECRET} -default_user=${DEFAULT_USER} -default_password=${DEFAULT_PASSWORD} +jwt_secret=${JWT_SECRET:n2r5u8x/A%D*G-KaPdSgVkYp3s6v9y$B&E(H+MbQeThWmZq4t7w!z%C*F-J@NcRf} +default_user=${DEFAULT_USER:username} +default_password=${DEFAULT_PASSWORD:password} #10 Days in ms token_expiration=${TOKEN_EXPIRATION:864000000} diff --git a/datasafe-rest-impl/src/test/resources/jwt-config.properties b/datasafe-rest-impl/src/test/resources/jwt-config.properties deleted file mode 100644 index 7402462a6..000000000 --- a/datasafe-rest-impl/src/test/resources/jwt-config.properties +++ /dev/null @@ -1,7 +0,0 @@ -debug=false -management.endpoints.web.exposure.include=* - -jwt_secret=n2r5u8x/A%D*G-KaPdSgVkYp3s6v9y$B&E(H+MbQeThWmZq4t7w!z%C*F-J@NcRf -default_user=username -default_password=password -token_expiration=864000000 diff --git a/frontend/README.md b/frontend/README.md index ca8275e3a..174a51035 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -3,6 +3,8 @@ Use `npm run-script ng:serve:web` for local development Use `npm run-script start` for electron development +Use `ng serve -c dev` if you need to debug + ### Notes - API url and credentials are provided by env.js file (API_URL, API_USERNAME, API_PASSWORD). Credentials (API_USERNAME, API_PASSWORD) are intended for local use only. diff --git a/frontend/datasafe-ui/angular.json b/frontend/datasafe-ui/angular.json index 544edcee8..8d763c103 100755 --- a/frontend/datasafe-ui/angular.json +++ b/frontend/datasafe-ui/angular.json @@ -40,7 +40,6 @@ "optimization": true, "outputHashing": "all", "sourceMap": false, - "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, @@ -53,24 +52,38 @@ "maximumError": "5mb" } ] + }, + "dev": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.dev.ts" + } + ], + "optimization": false, + "extractLicenses": false, + "sourceMap": true } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "my-app:build" + "buildTarget": "my-app:build" }, "configurations": { "production": { - "browserTarget": "my-app:build:production" + "buildTarget": "my-app:build:production" + }, + "dev": { + "buildTarget": "my-app:build:dev" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "my-app:build" + "buildTarget": "my-app:build" } }, "test": { @@ -105,5 +118,7 @@ } } }, - "defaultProject": "datasafe-ui" + "cli": { + "analytics": false + } } \ No newline at end of file diff --git a/frontend/datasafe-ui/main.js b/frontend/datasafe-ui/main.js index 49966638b..c6746dce1 100644 --- a/frontend/datasafe-ui/main.js +++ b/frontend/datasafe-ui/main.js @@ -21,7 +21,7 @@ function createWindow() { }); if (serve) { require('electron-reload')(__dirname, { - electron: require(__dirname + "/node_modules/electron") + electron: require("".concat(__dirname, "/node_modules/electron")) }); win.loadURL('http://localhost:4200'); } diff --git a/frontend/datasafe-ui/main.js.map b/frontend/datasafe-ui/main.js.map index f967e92ff..0723729af 100644 --- a/frontend/datasafe-ui/main.js.map +++ b/frontend/datasafe-ui/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":";;AAAA,qCAAsD;AACtD,2BAA6B;AAC7B,yBAA2B;AAE3B,IAAI,GAAG,EAAE,KAAK,CAAC;AACf,IAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,UAAA,GAAG,IAAI,OAAA,GAAG,KAAK,SAAS,EAAjB,CAAiB,CAAC,CAAC;AAE5C,SAAS,YAAY;IAEnB,IAAM,cAAc,GAAG,iBAAM,CAAC;IAC9B,IAAM,IAAI,GAAG,cAAc,CAAC,iBAAiB,EAAE,CAAC,YAAY,CAAC;IAE7D,6BAA6B;IAC7B,GAAG,GAAG,IAAI,wBAAa,CAAC;QACtB,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,cAAc,EAAE;YACd,eAAe,EAAE,IAAI;SACtB;KACF,CAAC,CAAC;IAEH,IAAI,KAAK,EAAE;QACT,OAAO,CAAC,iBAAiB,CAAC,CAAC,SAAS,EAAE;YACpC,QAAQ,EAAE,OAAO,CAAI,SAAS,2BAAwB,CAAC;SACxD,CAAC,CAAC;QACH,GAAG,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;KACtC;SAAM;QACL,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YACrB,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC;YACjD,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,IAAI;SACd,CAAC,CAAC,CAAC;KACL;IAED,IAAI,KAAK,EAAE;QACT,GAAG,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;KAChC;IAED,qCAAqC;IACrC,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE;QACf,gEAAgE;QAChE,mEAAmE;QACnE,oDAAoD;QACpD,GAAG,GAAG,IAAI,CAAC;IACb,CAAC,CAAC,CAAC;AAEL,CAAC;AAED,IAAI;IAEF,wDAAwD;IACxD,yDAAyD;IACzD,sDAAsD;IACtD,cAAG,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAE9B,oCAAoC;IACpC,cAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE;QAC1B,2DAA2D;QAC3D,8DAA8D;QAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE;YACjC,cAAG,CAAC,IAAI,EAAE,CAAC;SACZ;IACH,CAAC,CAAC,CAAC;IAEH,cAAG,CAAC,EAAE,CAAC,UAAU,EAAE;QACjB,gEAAgE;QAChE,4DAA4D;QAC5D,IAAI,GAAG,KAAK,IAAI,EAAE;YAChB,YAAY,EAAE,CAAC;SAChB;IACH,CAAC,CAAC,CAAC;CAEJ;AAAC,OAAO,CAAC,EAAE;IACV,cAAc;IACd,WAAW;CACZ"} \ No newline at end of file +{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":";;AAAA,qCAAoD;AACpD,2BAA6B;AAC7B,yBAA2B;AAE3B,IAAI,GAAG,EAAE,KAAK,CAAC;AACf,IAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,UAAA,GAAG,IAAI,OAAA,GAAG,KAAK,SAAS,EAAjB,CAAiB,CAAC,CAAC;AAE5C,SAAS,YAAY;IAEnB,IAAM,cAAc,GAAG,iBAAM,CAAC;IAC9B,IAAM,IAAI,GAAG,cAAc,CAAC,iBAAiB,EAAE,CAAC,YAAY,CAAC;IAE7D,6BAA6B;IAC7B,GAAG,GAAG,IAAI,wBAAa,CAAC;QACtB,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,cAAc,EAAE;YACd,eAAe,EAAE,IAAI;SACtB;KACF,CAAC,CAAC;IAEH,IAAI,KAAK,EAAE;QACT,OAAO,CAAC,iBAAiB,CAAC,CAAC,SAAS,EAAE;YACpC,QAAQ,EAAE,OAAO,CAAC,UAAG,SAAS,2BAAwB,CAAC;SACxD,CAAC,CAAC;QACH,GAAG,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;KACtC;SAAM;QACL,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YACrB,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC;YACjD,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,IAAI;SACd,CAAC,CAAC,CAAC;KACL;IAED,IAAI,KAAK,EAAE;QACT,GAAG,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;KAChC;IAED,qCAAqC;IACrC,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE;QACf,gEAAgE;QAChE,mEAAmE;QACnE,oDAAoD;QACpD,GAAG,GAAG,IAAI,CAAC;IACb,CAAC,CAAC,CAAC;AAEL,CAAC;AAED,IAAI;IAEF,wDAAwD;IACxD,yDAAyD;IACzD,sDAAsD;IACtD,cAAG,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAE9B,oCAAoC;IACpC,cAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE;QAC1B,2DAA2D;QAC3D,8DAA8D;QAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE;YACjC,cAAG,CAAC,IAAI,EAAE,CAAC;SACZ;IACH,CAAC,CAAC,CAAC;IAEH,cAAG,CAAC,EAAE,CAAC,UAAU,EAAE;QACjB,gEAAgE;QAChE,4DAA4D;QAC5D,IAAI,GAAG,KAAK,IAAI,EAAE;YAChB,YAAY,EAAE,CAAC;SAChB;IACH,CAAC,CAAC,CAAC;CAEJ;AAAC,OAAO,CAAC,EAAE;IACV,cAAc;IACd,WAAW;CACZ"} \ No newline at end of file diff --git a/frontend/datasafe-ui/package.json b/frontend/datasafe-ui/package.json index 3195a72de..33f011d8c 100644 --- a/frontend/datasafe-ui/package.json +++ b/frontend/datasafe-ui/package.json @@ -4,7 +4,7 @@ "description": "datasafe-ui with web and electron interface", "keywords": [ "angular", - "angular 8", + "angular 17", "electron", "typescript", "sass" @@ -32,29 +32,28 @@ "e2e": "npm run build:prod && ../datasafe-ui/node_modules/.bin/mocha --timeout 300000 --require ts-node/register e2e/**/*.spec.ts" }, "devDependencies": { - "@angular-devkit/build-angular": "15.2.9", - "@angular/animations": "15.2.9", - "@angular/cdk": "15.2.9", - "@angular/cli": "15.2.9", - "@angular/common": "15.2.9", - "@angular/compiler": "15.2.9", - "@angular/compiler-cli": "15.2.9", - "@angular/core": "15.2.9", - - "@angular/forms": "15.2.9", - "@angular/material": "15.2.9", - "@angular/material-moment-adapter": "15.2.9", - "@angular/platform-browser": "15.2.9", - "@angular/platform-browser-dynamic": "15.2.9", - "@angular/router": "15.2.9", + "@angular-devkit/build-angular": "^17.3.5", + "@angular/animations": "^17.3.5", + "@angular/cdk": "^17.3.5", + "@angular/cli": "^17.3.5", + "@angular/common": "^17.3.5", + "@angular/compiler": "^17.3.5", + "@angular/compiler-cli": "^17.3.5", + "@angular/core": "^17.3.5", + "@angular/forms": "^17.3.5", + "@angular/material": "^17.3.5", + "@angular/material-moment-adapter": "^17.3.5", + "@angular/platform-browser": "^17.3.5", + "@angular/platform-browser-dynamic": "^17.3.5", + "@angular/router": "^17.3.5", "@types/jasmine": "4.3.6", "@types/jasminewd2": "2.0.11", "@types/mocha": "10.0.2", "@types/node": "20.8.0", "codelyzer": "6.0.2", "core-js": "3.33.0", - "electron": "26.2.4", - "electron-builder": "24.7.0", + "electron": "^26.6.9", + "electron-builder": "24.13.3", "electron-reload": "1.5.0", "hammerjs": "2.0.8", "jasmine-core": "5.1.1", @@ -71,13 +70,13 @@ "spectron": "19.0.0", "ts-node": "10.9.1", "tslint": "5.17.0", - "typescript": "4.9.4", - "wait-on": "7.0.1", + "typescript": "5.4.5", + "wait-on": "^7.2.0", "web-animations-js": "2.3.2", "webdriver-manager": "13.0.2", - "zone.js": "0.14.0" + "zone.js": "^0.14.3" }, "engines": { "node": ">=18.10.0" } -} +} \ No newline at end of file diff --git a/frontend/datasafe-ui/src/app/app.component.ts b/frontend/datasafe-ui/src/app/app.component.ts index 788f93502..bee20cf9a 100644 --- a/frontend/datasafe-ui/src/app/app.component.ts +++ b/frontend/datasafe-ui/src/app/app.component.ts @@ -1,6 +1,6 @@ -import {Component, OnInit} from '@angular/core'; -import {ErrorStateMatcher} from "@angular/material"; -import {FormControl, FormGroupDirective, NgForm} from "@angular/forms"; +import {Component} from '@angular/core'; +import {ErrorStateMatcher} from '@angular/material/core'; +import {FormControl, FormGroupDirective, NgForm} from '@angular/forms'; export class Env { @@ -8,6 +8,10 @@ export class Env { private static browserWindow = window || {}; private static browserWindowEnv = Env.browserWindow['__env'] || {}; + static apiUrl = Env.get('apiUrl'); + static apiUsername = Env.get('apiUsername'); + static apiPassword = Env.get('apiPassword'); + static get(key): string { if (this.browserWindowEnv.hasOwnProperty(key)) { return window['__env'][key]; @@ -15,10 +19,6 @@ export class Env { return null; } - - static apiUrl = Env.get('apiUrl'); - static apiUsername = Env.get('apiUsername'); - static apiPassword = Env.get('apiPassword'); } export class FieldErrorStateMatcher implements ErrorStateMatcher { @@ -30,8 +30,8 @@ export class FieldErrorStateMatcher implements ErrorStateMatcher { export class ParentOrFieldErrorStateMatcher implements ErrorStateMatcher { isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { - const invalidCtrl = !!(control && control.invalid && control.parent.dirty); - const invalidParent = !!(control && control.parent && control.parent.invalid && control.parent.dirty); + const invalidCtrl = !!(control?.invalid && control?.parent?.dirty); + const invalidParent = !!(control?.parent?.invalid && control?.parent?.dirty); return (invalidCtrl || invalidParent); } @@ -40,12 +40,12 @@ export class ParentOrFieldErrorStateMatcher implements ErrorStateMatcher { export class ErrorMessageUtil { static extract(error): string { - let errMsg = "Failed " + error.message; - if (error && error.error && error.error.message) { + let errMsg = 'Failed ' + error.message; + if (error?.error?.message) { errMsg = error.error.message; } - return errMsg.substring(0, 32) + (errMsg.length >= 32 ? "..." : ""); + return errMsg.substring(0, 32) + (errMsg.length >= 32 ? '...' : ''); } } @@ -54,10 +54,7 @@ export class ErrorMessageUtil { templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) -export class AppComponent implements OnInit { +export class AppComponent { constructor() { } - - ngOnInit() { - } } diff --git a/frontend/datasafe-ui/src/app/app.module.ts b/frontend/datasafe-ui/src/app/app.module.ts index b582f3904..374db1559 100755 --- a/frontend/datasafe-ui/src/app/app.module.ts +++ b/frontend/datasafe-ui/src/app/app.module.ts @@ -34,7 +34,7 @@ const appRoutes: Routes = [ ReactiveFormsModule, RouterModule.forRoot(appRoutes) ], - entryComponents: [AppComponent, AddFolderDialog, ConfigureApiDialog], + declarations: [AppComponent, UserComponent, LoginComponent, RegisterComponent, FiletreeComponent, AddFolderDialog, ConfigureApiDialog], bootstrap: [AppComponent], providers: [] diff --git a/frontend/datasafe-ui/src/app/component/filetree/filetree.component.ts b/frontend/datasafe-ui/src/app/component/filetree/filetree.component.ts index 57aee9256..9e055c08c 100644 --- a/frontend/datasafe-ui/src/app/component/filetree/filetree.component.ts +++ b/frontend/datasafe-ui/src/app/component/filetree/filetree.component.ts @@ -3,11 +3,11 @@ import {FlatTreeControl} from '@angular/cdk/tree'; import {Component, Inject, Injectable} from '@angular/core'; import {BehaviorSubject, merge, Observable} from 'rxjs'; import {map} from 'rxjs/operators'; -import {ApiService} from "../../service/api/api.service"; -import {CredentialsService} from "../../service/credentials/credentials.service"; -import {Router} from "@angular/router"; -import {ErrorMessageUtil} from "../../app.component"; -import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material"; +import {ApiService} from '../../service/api/api.service'; +import {CredentialsService} from '../../service/credentials/credentials.service'; +import {Router} from '@angular/router'; +import {ErrorMessageUtil} from '../../app.component'; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog'; class UserFileSystem { @@ -19,42 +19,43 @@ class UserFileSystem { this.fs.clear(); // maintain consistent order - files.concat(Array.from(this.uiCreatedFolders).map(it => it + "/")) + files.concat(Array.from(this.uiCreatedFolders).map(it => it + '/')) .sort() .forEach(it => this.addEntry(it)); } - rootLevelNodes() : string[] { - let res = new Set(); + rootLevelNodes(): string[] { + const res = new Set(); this.fs.forEach((value, key) => { - let split = key.split("/", 2); - res.add(split[0] + (split.length > 1 ? "/" : "")); + const split = key.split('/', 2); + res.add(split[0] + (split.length > 1 ? '/' : '')); }); - return Array.from(res) + return Array.from(res); } private addEntry(path: string) { - var fullPath = ""; - var folder = ""; - path.split("/").forEach(segment => { + let fullPath = ''; + let folder = ''; + path = (path.startsWith('/')) ? path.substring(1) : path; + path.split('/').forEach(segment => { fullPath += segment; - fullPath += (fullPath === path ? "" : "/"); + fullPath += (fullPath === path ? '' : '/'); - let name = (((fullPath === path) && (!path.endsWith("/"))) ? segment : segment + "/"); + const name = (((fullPath === path) && (!path.endsWith('/'))) ? segment : segment + '/'); this.putToFolder(folder, name); - folder = fullPath - }) + folder = fullPath; + }); } private putToFolder(folder: string, name: string) { - if ("" === name || "/" === name) { + if ('' === name || '/' === name) { name = null; } - if (folder === "") { + if (folder === '') { folder = name; name = null; } @@ -75,15 +76,15 @@ export class DynamicFlatNode { isLoading: boolean; constructor(path: string) { - let level = path.split("/").length - 1; - if (path.endsWith("/")) { + let level = path.split('/').length - 1; + if (path.endsWith('/')) { level = level - 1; } - this.name = path.replace(/\/$/, "").match(/(.+\/)*([^\/]+)$/)[2]; + this.name = path.replace(/\/$/, '').match(/(.+\/)*([^\/]+)$/)[2]; this.path = path; this.level = level; - this.expandable = path.endsWith("/"); + this.expandable = path.endsWith('/'); } } @@ -92,7 +93,7 @@ export class DynamicDatabase { storageTree = new UserFileSystem(); loadData(api: ApiService, creds: CredentialsService, filetreeComponent: FiletreeComponent, router: Router) { - api.listDocuments("", creds.getCredentialsForApi()) + api.listDocuments('', creds.getCredentialsForApi()) .then(res => { this.storageTree.buildFs(> res); @@ -110,7 +111,7 @@ export class DynamicDatabase { } rebuildView(filetreeComponent: FiletreeComponent) { - let paths = this.memoizedFs(); + const paths = this.memoizedFs(); this.storageTree.buildFs(Array.from(paths)); filetreeComponent.dataSource.data = this.storageTree.rootLevelNodes() @@ -118,14 +119,14 @@ export class DynamicDatabase { } private memoizedFs() { - let paths = new Set(); + const paths = new Set(); this.storageTree.fs.forEach((values, key) => { paths.add(key); values.forEach(file => { if (null != file) { paths.add(key + file); } - }) + }); }); return paths; } @@ -157,7 +158,7 @@ export class DynamicDataSource { } private keepExpandedNodesState() { - let toExpand = new Set(this.expandedMemoize); + const toExpand = new Set(this.expandedMemoize); let expanded = false; do { expanded = false; @@ -168,7 +169,7 @@ export class DynamicDataSource { expanded = true; toExpand.delete(node.path); }); - } while (toExpand.size != 0 && expanded); + } while (toExpand.size !== 0 && expanded); } constructor(private treeControl: FlatTreeControl, @@ -253,10 +254,10 @@ export class FiletreeComponent { treeControl: FlatTreeControl; dataSource: DynamicDataSource; + error: any; getLevel = (node: DynamicFlatNode) => node.level; isExpandable = (node: DynamicFlatNode) => node.expandable; hasChild = (_: number, _nodeData: DynamicFlatNode) => _nodeData.expandable; - error: any; constructor(private database: DynamicDatabase, private api: ApiService, private creds: CredentialsService, private router: Router, public dialog: MatDialog) { @@ -269,19 +270,19 @@ export class FiletreeComponent { addUiFolderWithPath(path: string) { const dialogRef = this.dialog.open(AddFolderDialog, { width: '250px', - data: {folderPath: ""} + data: {folderPath: ''} }); dialogRef.afterClosed().subscribe(result => { if (result !== undefined) { - this.database.storageTree.uiCreatedFolders.add("" !== path ? path + result : result); + this.database.storageTree.uiCreatedFolders.add('' !== path ? path + result : result); this.database.rebuildView(this); } }); } addUiFolder() { - this.addUiFolderWithPath(""); + this.addUiFolderWithPath(''); } addUiFolderWithpathFromName(event) { @@ -302,13 +303,13 @@ export class FiletreeComponent { this.error = ''; this.removePathFromUiCreatedFolders(path); this.api.deleteDocument(path, this.creds.getCredentialsForApi()) - .then(res => this.loadTree()) + .then(_ => this.loadTree()) .catch(err => this.error = 'Delete failed: ' + ErrorMessageUtil.extract(err)); } private removePathFromUiCreatedFolders(path: string) { - let pathPrefix = path.replace(/\/$/, ""); - let toRemove = Array.from(this.database.storageTree.uiCreatedFolders) + const pathPrefix = path.replace(/\/$/, ''); + const toRemove = Array.from(this.database.storageTree.uiCreatedFolders) .filter(it => it.startsWith(pathPrefix)); toRemove.forEach(remove => this.database.storageTree.uiCreatedFolders.delete(remove)); } @@ -316,7 +317,7 @@ export class FiletreeComponent { uploadFile(event) { this.error = ''; this.api.uploadDocument(event.target.files[0], event.target.files[0].name, this.creds.getCredentialsForApi()) - .then(res => this.loadTree()) + .then(_ => this.loadTree()) .catch(err => { this.error = 'Upload failed: ' + ErrorMessageUtil.extract(err); }); @@ -328,7 +329,7 @@ export class FiletreeComponent { event.currentTarget.files[0], event.currentTarget.name + event.currentTarget.files[0].name, this.creds.getCredentialsForApi()) - .then(res => this.loadTree()) + .then(_ => this.loadTree()) .catch(err => { this.error = 'Upload failed: ' + ErrorMessageUtil.extract(err); }); diff --git a/frontend/datasafe-ui/src/app/component/login/login.component.ts b/frontend/datasafe-ui/src/app/component/login/login.component.ts index f943deeff..b2c947f34 100644 --- a/frontend/datasafe-ui/src/app/component/login/login.component.ts +++ b/frontend/datasafe-ui/src/app/component/login/login.component.ts @@ -3,7 +3,7 @@ import {Router} from '@angular/router'; import {FormBuilder, FormControl, Validators} from '@angular/forms'; import {CredentialsService} from '../../service/credentials/credentials.service'; import {Env, FieldErrorStateMatcher} from '../../app.component'; -import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material'; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog'; export interface ApiConfigData { apiUrl: string; diff --git a/frontend/datasafe-ui/src/app/component/register/register.component.ts b/frontend/datasafe-ui/src/app/component/register/register.component.ts index 4aff54c12..38edd7acc 100644 --- a/frontend/datasafe-ui/src/app/component/register/register.component.ts +++ b/frontend/datasafe-ui/src/app/component/register/register.component.ts @@ -1,14 +1,14 @@ -import {Component, OnInit} from '@angular/core'; -import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; -import {Router} from "@angular/router"; -import {ApiService} from "../../service/api/api.service"; -import {CredentialsService} from "../../service/credentials/credentials.service"; -import {ErrorMessageUtil, FieldErrorStateMatcher, ParentOrFieldErrorStateMatcher} from "../../app.component"; +import {Component} from '@angular/core'; +import {FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators} from '@angular/forms'; +import {Router} from '@angular/router'; +import {ApiService} from '../../service/api/api.service'; +import {CredentialsService} from '../../service/credentials/credentials.service'; +import {ErrorMessageUtil, FieldErrorStateMatcher, ParentOrFieldErrorStateMatcher} from '../../app.component'; class PasswordsMatchControl extends FormControl { constructor(private hidden: boolean) { - super('', []) + super('', []); } get Hidden(): boolean { @@ -29,7 +29,11 @@ class PasswordsMatchControl extends FormControl { templateUrl: './register.component.html', styleUrls: ['./register.component.css'] }) -export class RegisterComponent implements OnInit { +export class RegisterComponent { + + constructor(public router: Router, private api: ApiService, private fb: FormBuilder, + private creds: CredentialsService) { + } userNameControl = new FormControl('', [ Validators.required, @@ -47,37 +51,33 @@ export class RegisterComponent implements OnInit { username: this.userNameControl, passwords: this.passwordControl, matchPasswords: this.passwordMatchControl - }, {validator: RegisterComponent.checkPasswords}); - + }, { + validators: [RegisterComponent.checkPasswords] + }); fieldMatcher = new FieldErrorStateMatcher(); parentOrFieldMatcher = new ParentOrFieldErrorStateMatcher(); - constructor(public router: Router, private api: ApiService, private fb: FormBuilder, - private creds: CredentialsService) { - } + private static checkPasswords(): ValidatorFn { // here we have the 'passwords' group + return (group: FormGroup): ValidationErrors | null => { + const matchControl = group.controls.matchPasswords; + const pass = group.controls.passwords.value; + const confirmPass = matchControl.value; - ngOnInit() { + return (matchControl.Hidden || pass === confirmPass) ? null : {notSame: true}; + }; } public handleCreateUserClick() { if (!this.registerForm.valid) { - return + return; } this.api.createUser(this.userNameControl.value, this.passwordControl.value) - .then(res => { + .then(() => { this.creds.setCredentials(this.userNameControl.value, this.passwordControl.value); - this.router.navigate(['/user']) + this.router.navigate(['/user']); }) .catch(error => this.registerForm.setErrors({'createFailed': ErrorMessageUtil.extract(error)})); } - - private static checkPasswords(group: FormGroup) { // here we have the 'passwords' group - let matchControl = group.controls.matchPasswords; - let pass = group.controls.passwords.value; - let confirmPass = matchControl.value; - - return (matchControl.Hidden || pass === confirmPass) ? null : {notSame: true} - } } diff --git a/frontend/datasafe-ui/src/app/component/user/user.component.ts b/frontend/datasafe-ui/src/app/component/user/user.component.ts index e9f0191ff..4a976da5a 100644 --- a/frontend/datasafe-ui/src/app/component/user/user.component.ts +++ b/frontend/datasafe-ui/src/app/component/user/user.component.ts @@ -1,7 +1,7 @@ import {Component, OnInit} from '@angular/core'; -import {Router} from "@angular/router"; -import {CredentialsService} from "../../service/credentials/credentials.service"; -import {ApiService} from "../../service/api/api.service"; +import {Router} from '@angular/router'; +import {CredentialsService} from '../../service/credentials/credentials.service'; +import {ApiService} from '../../service/api/api.service'; @Component({ selector: 'app-user', @@ -19,11 +19,11 @@ export class UserComponent implements OnInit { ngOnInit() { if (null == this.creds.getCredentialsForApi()) { - this.router.navigate(['']) + this.router.navigate(['']); } } doLogout() { - this.router.navigate(['']) + this.router.navigate(['']); } } diff --git a/frontend/datasafe-ui/src/app/polyfills.ts b/frontend/datasafe-ui/src/app/polyfills.ts index 5134b24e9..d42447b83 100755 --- a/frontend/datasafe-ui/src/app/polyfills.ts +++ b/frontend/datasafe-ui/src/app/polyfills.ts @@ -1,5 +1,4 @@ -import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; +import 'core-js/features/reflect'; +import 'zone.js'; import 'hammerjs'; -import 'web-animations-js'; \ No newline at end of file +import 'web-animations-js'; diff --git a/frontend/datasafe-ui/src/app/service/api/api.service.ts b/frontend/datasafe-ui/src/app/service/api/api.service.ts index 14db1ed7d..7b78662d5 100644 --- a/frontend/datasafe-ui/src/app/service/api/api.service.ts +++ b/frontend/datasafe-ui/src/app/service/api/api.service.ts @@ -1,137 +1,126 @@ import {Injectable} from '@angular/core'; -import {HttpClient, HttpResponse} from "@angular/common/http"; -import {Observable, of} from "rxjs"; -import {flatMap, map} from "rxjs/operators"; -import {Credentials} from "../credentials/credentials.service"; -import {Env} from "../../app.component"; +import {HttpClient, HttpResponse} from '@angular/common/http'; +import {lastValueFrom, Observable, of} from 'rxjs'; +import {mergeMap, map} from 'rxjs/operators'; +import {Credentials} from '../credentials/credentials.service'; +import {Env} from '../../app.component'; @Injectable({providedIn: 'root'}) export class ApiService { - private static TOKEN_HEADER = "token"; + private static TOKEN_HEADER = 'token'; apiUserName = Env.apiUsername; apiPassword = Env.apiPassword; private uri = Env.apiUrl; - private authorizeUri = this.uri + "/api/authenticate"; - private createUserUri = this.uri + "/user"; - private listDocumentUri = this.uri + "/documents/"; - private putDocumentUri = this.uri + "/document/"; - private getDocumentUri = this.uri + "/document/"; - private deleteDocumentUri = this.uri + "/document/"; + private authorizeUri = this.uri + '/api/authenticate'; + private createUserUri = this.uri + '/user'; + private listDocumentUri = this.uri + '/documents/'; + private putDocumentUri = this.uri + '/document/'; + private getDocumentUri = this.uri + '/document/'; + private deleteDocumentUri = this.uri + '/document/'; private token: string; + private static headers(token: string) { + return {'headers': {[ApiService.TOKEN_HEADER]: token}}; + } + + private static headersWithAuth(token: string, creds: Credentials) { + return {'headers': { + [ApiService.TOKEN_HEADER]: token, + 'user': creds.username, + 'password': creds.password} + }; + } + + private static extractToken(response: HttpResponse<{}>): string { + return response.headers.get(ApiService.TOKEN_HEADER); + } + constructor(private httpClient: HttpClient) { } authorize() { - let result = this.httpClient.post( + const result = this.httpClient.post( this.authorizeUri, - {"userName": this.apiUserName, "password": this.apiPassword}, + {'userName': this.apiUserName, 'password': this.apiPassword}, {observe: 'response'} ); result.subscribe(res => { - this.token = ApiService.extractToken(res) + this.token = ApiService.extractToken(res); }); return result; } - createUser(username: string, password: string) { + async createUser(username: string, password: string) { + // tslint:disable-next-line:no-console console.info(`Creating user using api URL '${this.uri}'`); - return this.withAuthorization() - .pipe(flatMap(token => - this.httpClient.put( - this.createUserUri, - {"userName": username, "password": password}, - ApiService.headers(token) - ))).toPromise(); + return await lastValueFrom(this.withAuthorization() + .pipe(mergeMap(token => + this.httpClient.put(this.createUserUri, {'userName': username, 'password': password}, ApiService.headers(token)) + ))); } - listDocuments(path: string, creds: Credentials) { - return this.withAuthorization() - .pipe(flatMap(token => - this.httpClient.get( - this.listDocumentUri + path, - ApiService.headersWithAuth(token, creds) - ))).toPromise(); + async listDocuments(path: string, creds: Credentials) { + return await lastValueFrom(this.withAuthorization() + .pipe(mergeMap(token => + this.httpClient.get(this.listDocumentUri + path, ApiService.headersWithAuth(token, creds)) + ))); } - uploadDocument(document, path: string, creds: Credentials) { - let formData: FormData = new FormData(); + async uploadDocument(document: string | Blob, path: string, creds: Credentials) { + const formData: FormData = new FormData(); formData.append('file', document); - return this.withAuthorization() - .pipe(flatMap(token => + return await lastValueFrom(this.withAuthorization() + .pipe(mergeMap(token => this.httpClient.put( this.putDocumentUri + path, formData, - { - "headers": ApiService.headersWithAuth(token, creds)["headers"], - responseType: 'blob' as 'json' - }) - )).toPromise(); + {'headers': ApiService.headersWithAuth(token, creds)['headers'], responseType: 'blob' as 'json'} + ) + ))); } downloadDocument(path: string, creds: Credentials) { this.withAuthorization() - .pipe(flatMap(token => + .pipe(mergeMap(token => this.httpClient.get( this.getDocumentUri + path, - { - "headers": ApiService.headersWithAuth(token, creds)["headers"], - responseType: 'blob' as 'json' - } + {'headers': ApiService.headersWithAuth(token, creds)['headers'], responseType: 'blob' as 'json'} ) )).subscribe( (response: any) => { - let dataType = response.type; - let binaryData = []; + const dataType = response.type; + const binaryData = []; binaryData.push(response); - let downloadLink = document.createElement('a'); + const downloadLink = document.createElement('a'); downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, {type: dataType})); - downloadLink.setAttribute('download', path.match(/(.+\/)*([^/]+)$/)[2]); + downloadLink.setAttribute('download', RegExp(/(.+\/)*([^/]+)$/).exec(path)[2]); document.body.appendChild(downloadLink); downloadLink.click(); } - ) + ); } - deleteDocument(path: string, creds: Credentials) { - return this.withAuthorization() - .pipe(flatMap(token => - this.httpClient.delete( - this.deleteDocumentUri + path, - ApiService.headersWithAuth(token, creds) - ))).toPromise(); + async deleteDocument(path: string, creds: Credentials) { + return await lastValueFrom(this.withAuthorization() + .pipe(mergeMap(token => + this.httpClient.delete(this.deleteDocumentUri + path, ApiService.headersWithAuth(token, creds)) + ))); } - private withAuthorization() : Observable { + private withAuthorization(): Observable { if (!this.token) { return this.authorize() - .pipe(map((res) => ApiService.extractToken(res))) + .pipe(map((res) => ApiService.extractToken(res))); } - return of(this.token) - } - - private static headers(token: string) { - return {"headers": {[ApiService.TOKEN_HEADER]: token}}; - } - - private static headersWithAuth(token: string, creds: Credentials) { - return {"headers": { - [ApiService.TOKEN_HEADER]: token, - "user": creds.username, - "password": creds.password} - }; - } - - private static extractToken(response: HttpResponse<{}>) : string { - return response.headers.get(ApiService.TOKEN_HEADER) + return of(this.token); } } diff --git a/frontend/datasafe-ui/src/app/service/credentials/credentials.service.ts b/frontend/datasafe-ui/src/app/service/credentials/credentials.service.ts index b98faf8c5..545b9e60c 100644 --- a/frontend/datasafe-ui/src/app/service/credentials/credentials.service.ts +++ b/frontend/datasafe-ui/src/app/service/credentials/credentials.service.ts @@ -20,10 +20,10 @@ export class CredentialsService { constructor() { } setCredentials(username: string, password: string) { - this.credentials = new Credentials(username, password) + this.credentials = new Credentials(username, password); } - getCredentialsForApi() : Credentials { - return this.credentials + getCredentialsForApi(): Credentials { + return this.credentials; } } diff --git a/frontend/datasafe-ui/src/env.js b/frontend/datasafe-ui/src/env.js index 6d0d1f1c2..5526ce23a 100644 --- a/frontend/datasafe-ui/src/env.js +++ b/frontend/datasafe-ui/src/env.js @@ -1,9 +1,9 @@ (function (window) { window.__env = window.__env || {}; - window.__env.apiUrl = '${API_URL}'; + window.__env.apiUrl = 'http://localhost:8080'; // ideally these are not necessary, but API is protected, so supplying it for docker-local deployment - window.__env.apiUsername = '${API_USERNAME}'; - window.__env.apiPassword = '${API_PASSWORD}'; + window.__env.apiUsername = 'username'; + window.__env.apiPassword = 'password'; }(this)); \ No newline at end of file diff --git a/frontend/datasafe-ui/src/env.prod.js b/frontend/datasafe-ui/src/env.prod.js new file mode 100644 index 000000000..fc7e7a057 --- /dev/null +++ b/frontend/datasafe-ui/src/env.prod.js @@ -0,0 +1,9 @@ +(function (window) { + window.__env = window.__env || {}; + + window.__env.apiUrl = `${API_URL}`; + // ideally these are not necessary, but API is protected, so supplying it for docker-local deployment + window.__env.apiUsername = '${API_USERNAME}'; + window.__env.apiPassword = '${API_PASSWORD}'; + +}(this)); \ No newline at end of file diff --git a/pom.xml b/pom.xml index b15b12999..cc049524c 100644 --- a/pom.xml +++ b/pom.xml @@ -333,27 +333,6 @@ jaxb-api ${jaxb-api.version} - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - ${jackson.version} - test -