diff --git a/README.md b/README.md index 937273a..8ed4c26 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,79 @@ # HA-AuthCode-Generation-Service (CovidCode-Service) HA-AuthCode-Generation-Service is an authorization code generation service for the CovidCode-UI and the proximity tracing app. +# Developer Instructions + +## Initial setup + +Do this once: + +1. Install a JDK (tested with Oracle JDK v11 and OpenjDK 1.8.0) +1. [Install Maven](https://maven.apache.org/install.html) +1. Install [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) +1. Check out [CovidCode-UI](https://github.com/admin-ch/CovidCode-UI) in another directory + +## Development Cycle + +Do this at the beginning of your session: +1. Run
docker-compose up -d
+docker-compose logs -f
and wait for the logs to become quiescent +1. Run CovidCode-UI in another window (`ng serve`) + +To run manual tests, you can run CovidCode-Service with the `local` +and `keycloak-local` Spring profiles using the following command: +``` +mvn compile exec:java +``` +(or the equivalent using your IDE's Maven functionality, if you +require access to a debugger) + +To run the test suite: +``` +mvn verify +``` + +To perform a clean build, and run the test suite with full code coverage +and upload the data to a locally-running SonarQube: +``` +mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent verify sonar:sonar +``` +SonarQube results are thereafter visible at http://localhost:9000/ + +To tear down the development support environment (but retain its state on-disk): +``` +docker-compose down +``` + +To wipe everything: +``` +docker-compose down +docker volume rm covidcode_dbdata +mvn clean +``` + ## Swagger-UI Swagger-UI is running on http://localhost:8113/swagger-ui.html. +## Local KeyCloak instance + +If CovidCode-Service is being run as suggested above, it will perform +authentication and access control against an OIDC / OAuth server +running on http://localhost:8180/ (and so will CovidCode-UI in its +default development configuration). + +The credentials for the KeyCloak administrator are visible in +docker-compose.yml in section `keycloak:`. Additionally, KeyCloak is +automatically pre-populated with a `bag-pts` realm, containing a +`doctor` account (password `doctor`) that enjoys access to both +CovidCode-UI and CovidCode-Service. + ## PostgreSQL database -To start up the application locally, run a new PostgreSQL 11+ database on port 3113. Use the profile "local" to run the application. + +docker-compose runs a new PostgreSQL database on port 3113 and takes +care of setting it up. The superuser credentials are in +`docker-compose.yml`. + +The "local" Spring profile should be used to run the application (see above). The other profiles run the script afterMigrate to reassign the owner of the objects. ### Dockerfile diff --git a/devsupport/haauthcodegeneration/create-database-and-user.sql b/devsupport/haauthcodegeneration/create-database-and-user.sql new file mode 100644 index 0000000..9d17d0b --- /dev/null +++ b/devsupport/haauthcodegeneration/create-database-and-user.sql @@ -0,0 +1,7 @@ +CREATE DATABASE haauthcodegeneration; +CREATE USER haauthcodegeneration WITH PASSWORD 'secret'; +ALTER USER haauthcodegeneration WITH SUPERUSER; +GRANT ALL ON DATABASE haauthcodegeneration TO haauthcodegeneration; + +CREATE ROLE haauthcodegeneration_role_full; +GRANT ALL ON DATABASE haauthcodegeneration TO haauthcodegeneration_role_full; diff --git a/devsupport/keycloak/create-database-and-user.sql b/devsupport/keycloak/create-database-and-user.sql new file mode 100644 index 0000000..4006e01 --- /dev/null +++ b/devsupport/keycloak/create-database-and-user.sql @@ -0,0 +1,3 @@ +CREATE DATABASE keycloak; +CREATE USER keycloak WITH PASSWORD 'keycloak'; +GRANT ALL ON DATABASE keycloak TO keycloak; diff --git a/devsupport/keycloak/realm-bag-pts-localhost.json b/devsupport/keycloak/realm-bag-pts-localhost.json new file mode 100644 index 0000000..2237937 --- /dev/null +++ b/devsupport/keycloak/realm-bag-pts-localhost.json @@ -0,0 +1,80 @@ +{ + "id": "BAG-PTS", + "realm": "bag-pts", + "enabled": true, + "clients": [ + { + "clientId": "ha-ui-web-client", + "rootUrl": "https://www.covidcode-d.admin.ch", + "adminUrl": "", + "publicClient": true, + "surrogateAuthRequired": false, + "enabled": true, + "redirectUris": [ + "http://localhost:4200/*" + ], + "webOrigins": [ + "http://localhost:4200" + ], + "protocolMappers": [ + { + "id": "showRolesInUserinfoAsUserroles", + "name": "Realm Mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "usermodel.clientRoleMapping.rolePrefix": "bag-pts-", + "multivalued": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "userroles", + "jsonType.label": "String" + } + }, + { + "id": "hardcodedCtxClaim", + "name": "Context Claim", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "false", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "ctx", + "claim.value": "USER", + "jsonType.label": "String" + } + } + ] + } + ], + "roles": { + "realm": [ + { + "name": "bag-pts-allow", + "description": "Grant this role to users, so that they can use ha-ui", + "composite": false, + "clientRole": false, + "containerId": "BAG-PTS", + "attributes": {} + } + ] + }, + "users" : [ + { + "username" : "doctor", + "enabled": true, + "email" : "doctor@example.com", + "firstName": "Doctor", + "lastName": "Example", + "credentials" : [ + { "type" : "password", + "value" : "doctor" } + ], + "realmRoles": [ "bag-pts-allow" ] + } + ] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ddfd6b5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,88 @@ +# docker-compose.yml for developer support +# +# Usage: +# +# docker-compose up -d +# +# Port allocation scheme: +# 3113 The PostgreSQL database +# 4200 The Angular UI (not part of this project) +# 8113 The covidcode back-end server (not managed by docker-compose) +# 8180 The Keycloak server, exposed through Træfik with some URL rewriting +# 9000 SonarQube, a source code linter and metrics renderer (e.g. for test coverage) + +version: "3" + +# To purge all state, stop all containers and say +# +# docker volume rm covidcode_dbdata +# +# This will erase the PostgreSQL database. Then start everything again +volumes: + dbdata: + +services: + + db: + image: "postgres:11" + container_name: "dp3t_postgres" + ports: + - "3113:5432" + environment: + POSTGRES_PASSWORD: secret + volumes: + - dbdata:/var/lib/postgresql/data + - ./devsupport/keycloak/create-database-and-user.sql:/docker-entrypoint-initdb.d/create-keycloak-database-and-user.sql + - ./devsupport/haauthcodegeneration/create-database-and-user.sql:/docker-entrypoint-initdb.d/create-haauthcodegeneration-database-and-user.sql + + keycloak: + image: jboss/keycloak + container_name: "keycloak" + environment: + # https://hub.docker.com/r/jboss/keycloak + KEYCLOAK_USER: admin + KEYCLOAK_PASSWORD: masterPassword + DB_VENDOR: postgres + DB_ADDR: db + DB_DATABASE: keycloak + DB_USER: keycloak + DB_PASSWORD: keycloak + KEYCLOAK_IMPORT: /tmp/realm-bag-pts-localhost.json + volumes: + - ./devsupport/keycloak/realm-bag-pts-localhost.json:/tmp/realm-bag-pts-localhost.json + labels: + - "traefik.enable=true" + - "traefik.http.routers.keycloak.entrypoints=web" + - "traefik.http.routers.keycloak.rule=PathPrefix(`/`)" # i.e. accept anything + # Rewrite URLs so that e.g. + # http://localhost:8180/.well-known/openid-configuration + # works (as expected by ha-ui in its dev configuration): + - "traefik.http.routers.keycloak.middlewares=rewrite-url-oidc" + - "traefik.http.middlewares.rewrite-url-oidc.replacepathregex.regex=^/(\\.well-known/.*)$$" + - "traefik.http.middlewares.rewrite-url-oidc.replacepathregex.replacement=/auth/realms/bag-pts/$$1" + + traefik: + image: traefik:2.2.1 + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + command: + - "--entrypoints.web.address=:80" + ## Enable docker provider + - "--providers.docker=true" + ## Do not expose containers unless explicitly told so + - "--providers.docker.exposedbydefault=false" + ## Uncomment the following two lines to turn on the Træfik + ## dashboard (handy for troubleshooting errors in the + ## `traefik.*` labels, above): + # - "--api.dashboard=true" + # - "--api.insecure=true" + ports: + - "8180:80" + ## Uncomment the following line to expose the Træfik dashboard + ## on port 8080: + # - "8080:8080" + + sonarqube: + image: sonarqube:community + ports: + - "9000:9000" diff --git a/pom.xml b/pom.xml index 7f40844..8114a89 100644 --- a/pom.xml +++ b/pom.xml @@ -222,6 +222,18 @@ false + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + ch.admin.bag.covidcode.authcodegeneration.AuthCodeGenerationServiceApplication + + --spring.profiles.active=local,keycloak-local + + + org.apache.maven.plugins maven-failsafe-plugin diff --git a/src/main/resources/application-keycloak-local.yml b/src/main/resources/application-keycloak-local.yml new file mode 100644 index 0000000..74bb8d6 --- /dev/null +++ b/src/main/resources/application-keycloak-local.yml @@ -0,0 +1,11 @@ +# Specific configuration for running CovidCode-Service so that it +# talks to the local Keycloak (the one of ../../../docker-compose.yml) + +jeap: + security: + oauth2: + resourceserver: + authorization-server: + issuer: "http://localhost:8180/auth/realms/bag-pts" + jwk-set-uri: ~ + diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index b7d280a..a71a8b0 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -22,3 +22,25 @@ jeap: ha-authcode-generation-service: allowed-origin: "http://localhost:4200" + +## Uncomment the following to increase logging; then issue +## `mvn compile` to copy this configuration under target/ +# server: +# tomcat: +# basedir: /tmp +# accesslog: +# enabled: true +# directory: /dev +# prefix: stdout +# buffered: false +# suffix: +# file-date-format: +# +# logging: +# level: +# org.apache.tomcat: DEBUG +# org.apache.catalina: DEBUG +# org: +# apache: +# tomcat: DEBUG +# catalina: DEBUG diff --git a/src/test/java/ch/admin/bag/covidcode/authcodegeneration/web/controller/AuthCodeGenerationControllerSecurityTest.java b/src/test/java/ch/admin/bag/covidcode/authcodegeneration/web/controller/AuthCodeGenerationControllerSecurityTest.java index 48a29aa..42e2677 100644 --- a/src/test/java/ch/admin/bag/covidcode/authcodegeneration/web/controller/AuthCodeGenerationControllerSecurityTest.java +++ b/src/test/java/ch/admin/bag/covidcode/authcodegeneration/web/controller/AuthCodeGenerationControllerSecurityTest.java @@ -32,14 +32,16 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(value = {AuthCodeGenerationController.class, OAuth2SecuredWebConfiguration.class}) +@WebMvcTest(value = {AuthCodeGenerationController.class, OAuth2SecuredWebConfiguration.class}, + properties="jeap.security.oauth2.resourceserver.authorization-server.jwk-set-uri=http://localhost:8182/.well-known/jwks.json") // Avoid port 8180, see below @ActiveProfiles("local") class AuthCodeGenerationControllerSecurityTest { private static final String URL = "/v1/authcode"; private static final String VALID_USER_ROLE = "bag-pts-allow"; private static final String INVALID_USER_ROLE = "invalid-role"; - private static final int MOCK_SERVER_PORT = 8180; + // Avoid port 8180, which is likely used by the local KeyCloak: + private static final int MOCK_SERVER_PORT = 8182; @Autowired private MockMvc mockMvc; @@ -53,7 +55,9 @@ class AuthCodeGenerationControllerSecurityTest { private static final LocalDateTime EXPIRED_IN_FUTURE = LocalDateTime.now().plusDays(1); private static final LocalDateTime EXPIRED_IN_PAST = LocalDateTime.now().minusDays(1); - private static WireMockServer wireMockServer = new WireMockServer(options().port(MOCK_SERVER_PORT)); + private static WireMockServer wireMockServer = + // new WireMockServer(options().dynamicPort()); + new WireMockServer(options().port(MOCK_SERVER_PORT)); @BeforeAll static void setup() throws Exception {