From 3d2cb078de5e8dde1d01fb74f7c6dc00e8aa1f2b Mon Sep 17 00:00:00 2001 From: Muhammad Ali Date: Wed, 13 Jul 2022 07:51:24 +0400 Subject: [PATCH] initial commit --- README.md | 56 + backend-api-definition/HELP.md | 10 + backend-api-definition/pom.xml | 101 + .../resources/tribune/reporting-service.yml | 22 + backend-application/README.md | 9 + backend-application/pom.xml | 50 + backend-application/src/local/README.md | 19 + .../src/local/create_keycloak_db.sh | 9 + .../src/local/docker-compose.yml | 72 + .../src/local/keycloak-init/realm-export.json | 2351 +++++++++++++++++ .../tribune/backend/BackendApplication.java | 16 + .../src/main/resources/application-local.yml | 22 + .../src/main/resources/application.yml | 46 + .../src/main/resources/banner.txt | 8 + .../tribune/application/ApplicationTests.java | 13 + backend-domain/README.md | 22 + backend-domain/pom.xml | 58 + .../tribune/backend/domain/AggregateRoot.java | 15 + .../tribune/backend/domain/DomainObject.java | 8 + .../com/tribune/backend/domain/Entity.java | 59 + .../domain/IdentifiableDomainObject.java | 9 + .../tribune/backend/domain/LocalEntity.java | 11 + .../tribune/backend/domain/ValueObject.java | 9 + .../backend/domain/context/CustomerOrder.java | 20 + .../backend/domain/context/package-info.java | 5 + .../backend/domain/dto/GenericResponse.java | 23 + .../backend/domain/dto/OrdersResponse.java | 12 + .../backend/domain/dto/PlaceOrderRequest.java | 26 + .../domain/dto/SingleOrderResponse.java | 27 + .../domain/dto/UpdateOrderRequest.java | 26 + .../backend/domain/dto/customer/Customer.java | 38 + .../domain/dto/customer/CustomerState.java | 12 + .../domain/dto/customer/CustomerType.java | 6 + .../domain/dto/customer/address/Address.java | 22 + .../dto/customer/address/PostalCode.java | 15 + .../domain/dto/customer/address/Street.java | 10 + .../domain/dto/customer/common/Country.java | 19 + .../dto/customer/common/Profession.java | 46 + .../domain/dto/customer/invoice/Invoice.java | 19 + .../domain/dto/customer/payment/Payment.java | 20 + .../backend/domain/dto/order/Order.java | 26 + .../domain/dto/order/lineitem/LineItem.java | 22 + .../dto/order/lineitem/product/Product.java | 16 + .../backend/domain/enums/BlogStatus.java | 5 + .../backend/domain/enums/OrderStatus.java | 6 + .../backend/domain/enums/UserStatus.java | 5 + .../backend/domain/error/DomainException.java | 8 + .../service/CustomerRepositoryAdapter.java | 32 + .../domain/validation/ValidationError.java | 12 + backend-infrastructure/README.md | 2 + backend-infrastructure/pom.xml | 120 + .../infrastructure/config/AppConfig.java | 80 + .../config/security/CorsConfig.java | 30 + .../config/security/CredentialsUtils.java | 16 + .../config/security/CustomFilter.java | 21 + .../security/KeycloakConfigProperties.java | 45 + .../config/security/SecurityConfig.java | 94 + ...eKeycloakAuthenticationFailureHandler.java | 36 + .../config/security/UserDTO.java | 24 + .../config/security/UserLoginRequest.java | 21 + .../security/UserManagementService.java | 25 + .../security/UserManagementServiceImpl.java | 139 + .../controller/DummyController.java | 19 + .../controller/OrdersController.java | 30 + .../controller/UserController.java | 64 + .../db/entities/AddressEntity.java | 28 + .../db/entities/CustomerEntity.java | 40 + .../db/entities/InvoiceEntity.java | 26 + .../db/entities/LineItemEntity.java | 25 + .../db/entities/OrderEntity.java | 28 + .../db/entities/PaymentEntity.java | 27 + .../db/entities/ProductEntity.java | 20 + .../db/repository/CustomerRepository.java | 14 + .../db/repository/OrderRepository.java | 12 + .../error/BackendException.java | 19 + .../error/NotFoundException.java | 12 + .../infrastructure/error/TribuneAdvisor.java | 27 + .../services/CustomerService.java | 10 + .../services/CustomerServiceImpl.java | 17 + .../infrastructure/services/OrderService.java | 16 + .../services/OrderServiceImpl.java | 38 + .../backend/domain/DataApplicationTests.java | 13 + pom.xml | 166 ++ 83 files changed, 4777 insertions(+) create mode 100644 README.md create mode 100644 backend-api-definition/HELP.md create mode 100644 backend-api-definition/pom.xml create mode 100644 backend-api-definition/src/main/resources/tribune/reporting-service.yml create mode 100644 backend-application/README.md create mode 100644 backend-application/pom.xml create mode 100644 backend-application/src/local/README.md create mode 100644 backend-application/src/local/create_keycloak_db.sh create mode 100644 backend-application/src/local/docker-compose.yml create mode 100644 backend-application/src/local/keycloak-init/realm-export.json create mode 100644 backend-application/src/main/java/com/tribune/backend/BackendApplication.java create mode 100644 backend-application/src/main/resources/application-local.yml create mode 100644 backend-application/src/main/resources/application.yml create mode 100644 backend-application/src/main/resources/banner.txt create mode 100644 backend-application/src/test/java/com/tribune/application/ApplicationTests.java create mode 100644 backend-domain/README.md create mode 100644 backend-domain/pom.xml create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/AggregateRoot.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/DomainObject.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/Entity.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/IdentifiableDomainObject.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/LocalEntity.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/ValueObject.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/context/CustomerOrder.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/context/package-info.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/GenericResponse.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/OrdersResponse.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/PlaceOrderRequest.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/SingleOrderResponse.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/UpdateOrderRequest.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/Customer.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/CustomerState.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/CustomerType.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/Address.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/PostalCode.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/Street.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/common/Country.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/common/Profession.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/invoice/Invoice.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/payment/Payment.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/order/Order.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/order/lineitem/LineItem.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/dto/order/lineitem/product/Product.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/enums/BlogStatus.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/enums/OrderStatus.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/enums/UserStatus.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/error/DomainException.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/service/CustomerRepositoryAdapter.java create mode 100644 backend-domain/src/main/java/com/tribune/backend/domain/validation/ValidationError.java create mode 100644 backend-infrastructure/README.md create mode 100644 backend-infrastructure/pom.xml create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/AppConfig.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CorsConfig.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CredentialsUtils.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CustomFilter.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/KeycloakConfigProperties.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/SecurityConfig.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/TribuneKeycloakAuthenticationFailureHandler.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserDTO.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserLoginRequest.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserManagementService.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserManagementServiceImpl.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/DummyController.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/OrdersController.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/UserController.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/AddressEntity.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/CustomerEntity.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/InvoiceEntity.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/LineItemEntity.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/OrderEntity.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/PaymentEntity.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/ProductEntity.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/repository/CustomerRepository.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/repository/OrderRepository.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/BackendException.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/NotFoundException.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/TribuneAdvisor.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/CustomerService.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/CustomerServiceImpl.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/OrderService.java create mode 100644 backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/OrderServiceImpl.java create mode 100644 backend-infrastructure/src/test/java/com/tribune/backend/domain/DataApplicationTests.java create mode 100644 pom.xml diff --git a/README.md b/README.md new file mode 100644 index 0000000..daacef6 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Getting Started + +This is a demo project to demonstrate the concept of DDD (Domain Driven Development) +within a monolithic web application. + +## DDD in a nutshell + +

You cannot create a banking software system unless you have a good understanding about the domain of banking.

+

Domain Driven Design - by Eric Evans.

+
+ +Our Application usually consists of three layers: +1. **Domain layer** includes all the information about the business case and the business rules. +2. **Infrastructure layer** supports communication between the domain and other layers. +Infrastructure layer is an expression/implementation for the domain layer - a way of conducting/executing business information described on domain layer. +Yes, Domain layer is like a java interface, and infrastructure layer is its implementation. +We express business information through services. +Services are built using contracts - a way of abstracting business information. +So that for each service, there’s a contract and one or more implementations. +Contracts like [Adapters - a well-known design pattern] are placed within the domain layer, +while their implementations are placed within the infrastructure layer. +Most importantly, the domain layer is in the center of the business application. +This means that it should be separated from the rest of the layers. +It shouldn’t depend on the other layers or their frameworks. +Contracts (adapters) implementations should NOT contain any business logic. +All operations with Domain objects should be initiated and encapsulated only in domain module. +So, you can utilize Utils/Tools/Handlers built within domain layer. +3. **Application layer** a point of control that manages a combination of both previous layers. +On this project you'll find it encapsulating spring boot configuration properties. +In general for dependencies in a DDD Service, the Application layer depends on Domain and Infrastructure, +and Infrastructure depends on Domain, but Domain doesn't depend on any layer / The other way around is not permitted. +Think about it this way - What is more likely to be replaced at some point? Infrastructure or domain? +Infrastructure will change over time (different providers, different servers, …), +Your domain on the other hand will always be there. + +## Useful Concepts +### Aggregates +* For each business feature delivered, it can be wrapped within what’s called a **BoundedContext**. +* To use Aggregates please read this article offered by [Bealdung](https://baeldung-cn.com/java-modules-ddd-bounded-contexts). +* Aggregate Root is the mothership entity inside the aggregate. +* It identifies a transaction boundary so that an aggregate root and all related boundaries +should be modified following strong consistency principle (oppose to domain events +and eventual consistency for cross aggregation root changes). + + +## Deployment notes + +Environment Variables: +* host +* keycloak.credentials.secret + + + +## Authors +[![Linkedin](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white&label=Muhammad%20Ali)](https://linkedin.com/in/zatribune) + diff --git a/backend-api-definition/HELP.md b/backend-api-definition/HELP.md new file mode 100644 index 0000000..79bc4de --- /dev/null +++ b/backend-api-definition/HELP.md @@ -0,0 +1,10 @@ +# Getting Started + +### Reference Documentation + +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/#build-image) + diff --git a/backend-api-definition/pom.xml b/backend-api-definition/pom.xml new file mode 100644 index 0000000..abfdd7a --- /dev/null +++ b/backend-api-definition/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + com.tribune + backend + 0.0.1-SNAPSHOT + + + backend-api-definition + + + 1.6.6 + 1.0.0 + 2.9.2 + 2.6.7 + 2.6.7 + 0.2.2 + + + + + io.swagger + swagger-annotations + ${swagger-annotations-version} + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot-starter-web-version} + + + org.openapitools + jackson-databind-nullable + ${jackson-databind-nullable} + + + + + + + org.openapitools + openapi-generator-maven-plugin + 4.3.1 + + + + generate + + + + ${project.basedir}/src/main/resources/tribune/reporting-service.yml + + spring + com.tribune.backend.infrastructure.api + + + com.tribune.backend.infrastructure.model + + + ApiUtil.java + + + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + none + + + + + + diff --git a/backend-api-definition/src/main/resources/tribune/reporting-service.yml b/backend-api-definition/src/main/resources/tribune/reporting-service.yml new file mode 100644 index 0000000..de216c0 --- /dev/null +++ b/backend-api-definition/src/main/resources/tribune/reporting-service.yml @@ -0,0 +1,22 @@ +openapi: "3.0.3" +info: + title: "reporting API" + description: "reporting API" + version: "1.0.0" +servers: + - url: "https://reporting" +paths: + /reporting/generate/{type}: + post: + summary: "POST reporting/generate/{type}" + operationId: "generate" + parameters: + - name: "type" + in: "path" + required: true + schema: + type: string + example: "PDF" + responses: + "200": + description: "OK" \ No newline at end of file diff --git a/backend-application/README.md b/backend-application/README.md new file mode 100644 index 0000000..17dd93e --- /dev/null +++ b/backend-application/README.md @@ -0,0 +1,9 @@ +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.6.7/maven-plugin/reference/html/#build-image) + diff --git a/backend-application/pom.xml b/backend-application/pom.xml new file mode 100644 index 0000000..4b15204 --- /dev/null +++ b/backend-application/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + com.tribune + backend + 0.0.1-SNAPSHOT + + + backend-application + + + + + + ${project.groupId} + backend-infrastructure + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + ${project.groupId} + backend-domain + test-jar + + + ${project.groupId} + backend-infrastructure + test-jar + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/backend-application/src/local/README.md b/backend-application/src/local/README.md new file mode 100644 index 0000000..9466bad --- /dev/null +++ b/backend-application/src/local/README.md @@ -0,0 +1,19 @@ +## Notes regarding keycloak deployment + +### Database +* Keycloak is integrated with the main postgres instance of our backend, but connected to a different database. +* Keycloak can access this database via a dedicated user. +* Both Keycloak's user and database are initialized within this script file [create_keycloak_db](create_keycloak_db.sh) and mounted on start. +* Scripts in `/docker-entrypoint-initdb.d` are only run if you start +the container with a data directory that is empty; any pre-existing database +will be left untouched on container startup. +* One common problem is that if one of your `/docker-entrypoint-initdb.d` +scripts fails (which will cause the entrypoint script to exit) and your orchestrator restarts the container with the already initialized data directory, it will not continue on with your scripts. + +* Any `*.sql` files will be executed by `POSTGRES_USER`, which defaults to the postgres **superuser**. So it is recommended that any `psql` commands that are run inside a `*.sh` script be executed +as `POSTGRES_USER` by using the `--username "$POSTGRES_USER"` flag. +* This newly created user is supposed to be able to connect without a password due to the presence of +trust authentication for Unix socket connections made inside the container (**tested and failed -> needs a password on user creation**). + +### Initial Configuration +* This file [realm-export.json](keycloak-init/realm-export.json) is used as a backup for realm configuration. It can be loaded from the admin console. \ No newline at end of file diff --git a/backend-application/src/local/create_keycloak_db.sh b/backend-application/src/local/create_keycloak_db.sh new file mode 100644 index 0000000..3c92ca5 --- /dev/null +++ b/backend-application/src/local/create_keycloak_db.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE USER keycloak WITH ENCRYPTED PASSWORD 'keycloak'; + CREATE DATABASE keycloak_db; + GRANT ALL PRIVILEGES ON DATABASE keycloak_db TO keycloak; +EOSQL \ No newline at end of file diff --git a/backend-application/src/local/docker-compose.yml b/backend-application/src/local/docker-compose.yml new file mode 100644 index 0000000..40b102c --- /dev/null +++ b/backend-application/src/local/docker-compose.yml @@ -0,0 +1,72 @@ +version: '3.8' + +networks: + tribune-network: + name: tribune-network + +services: + app: + image: 'tribune-backend:latest' + build: + context: ../../../../.. + dockerfile: ./infrastructure/docker/dev/Dockerfile + container_name: app + depends_on: + - db + ports: + - '8880:8880' + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/tribune_db_docker + - SPRING_DATASOURCE_USERNAME=tribune_db_user + - SPRING_DATASOURCE_PASSWORD=password + labels: + collect_logs_with_filebeat: 'true' + decode_log_event_to_json_object: 'true' + networks: + - tribune-network + + db: + image: 'postgres:13.1-alpine' + restart: unless-stopped + container_name: db + environment: + #default superuser + POSTGRES_USER: tribune_db_user + ##default superuser password + POSTGRES_PASSWORD: password + ##default database + POSTGRES_DB: tribune_db_docker + ports: + - '5432:5432' + volumes: + - ./db:/var/lib/postgresql/data + - ./create_keycloak_db.sh:/docker-entrypoint-initdb.d/create_keycloak_db.sh + labels: + collect_logs_with_filebeat: 'true' + decode_log_event_to_json_object: 'true' + networks: + - tribune-network + + keycloak: + image: bitnami/keycloak:18 + container_name: keycloak + environment: + DB_VENDOR: POSTGRES + #by service name + DB_ADDR: db + #database name + DB_DATABASE: keycloak_db + DB_USER: keycloak + DB_PASSWORD: keycloak + KEYCLOAK_USER: admin + KEYCLOAK_PASSWORD: Pass@1234 + # Uncomment the line below if you want to specify JDBC parameters. The parameter below is just an example, and it shouldn't be used in production without knowledge. It is highly recommended that you read the PostgreSQL JDBC driver documentation in order to use it. + #JDBC_PARAMS: "ssl=true" + ports: + - "8080:8080" + depends_on: + - db + networks: + - tribune-network + + diff --git a/backend-application/src/local/keycloak-init/realm-export.json b/backend-application/src/local/keycloak-init/realm-export.json new file mode 100644 index 0000000..84dc203 --- /dev/null +++ b/backend-application/src/local/keycloak-init/realm-export.json @@ -0,0 +1,2351 @@ +{ + "id": "tribune", + "realm": "tribune", + "displayName": "Pay With tribune", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "e82e3073-eb5d-4d51-a124-c217a461e46e", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "tribune", + "attributes": {} + }, + { + "id": "bb08772e-3722-4921-a781-9933c1446305", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "tribune", + "attributes": {} + }, + { + "id": "5fa6ce4f-1d6d-4ae9-88ee-adcd1345b1b8", + "name": "default-roles-tribune", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] + } + }, + "clientRole": false, + "containerId": "tribune", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "a04108fd-d06c-47d0-91e7-80dcd90e9999", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "e1dc8e8a-566e-4d8a-a508-51caf7b3f1b2", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "a630bdcb-5a5d-402e-a484-63008150e3c1", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "726b6236-893d-4e5f-ad92-dbf83dc1d19c", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "0784f0cb-fb55-43ee-8d00-0f8670d6ecd5", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "8e8bbf2f-fedc-4052-bf7a-00e2efefbc8a", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "create-client", + "manage-users", + "view-clients", + "view-events", + "manage-authorization", + "manage-events", + "query-users", + "view-authorization", + "view-users", + "view-realm", + "manage-clients", + "manage-identity-providers", + "manage-realm", + "impersonation", + "query-clients", + "view-identity-providers", + "query-realms" + ] + } + }, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "a5f5bbe9-4595-46dc-b8df-b198b171a1c3", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "43a30c79-5da1-44a4-99e7-ab7d88b57f17", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "92b3aa4d-eccd-42c2-9737-94ced153a4cb", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "2a170305-0637-410c-91a9-58651426f900", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "35664862-ba80-46e8-9692-89389be0c0c1", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "05056816-dcd1-43cc-be19-e1bcee295377", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "4ea0050b-ffb4-4e20-a7a3-4608b65a75af", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "692203d7-59c4-4c58-a504-7d37d5967889", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "da8f32c1-a3c2-4214-b16b-20a0da236085", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "0f65a0db-7d0e-410f-9436-4f7507784b0e", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "ad41530a-e127-4748-beee-5d1637215f68", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "1ac0b3e9-8e10-4f37-94a8-5a6337502601", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + }, + { + "id": "3c8a5bca-e17d-445f-98aa-4d15592ef95c", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "attributes": {} + } + ], + "tribune-user-management": [], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "e712e2e7-c62c-419c-99a3-e6a0381a06c4", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "fe5b4e8c-5b43-4e08-841b-66f9b6e35020", + "attributes": {} + } + ], + "account": [ + { + "id": "7275076e-23f3-4704-860f-e137fb7e85d9", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "dfb9a4ca-b2e9-443f-94f2-609ef373bdbc", + "attributes": {} + }, + { + "id": "a799612e-5d9a-4fcd-9222-37b75cf2395f", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "dfb9a4ca-b2e9-443f-94f2-609ef373bdbc", + "attributes": {} + }, + { + "id": "e3d9ba83-df4a-4dc3-bd01-eec458c80812", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "dfb9a4ca-b2e9-443f-94f2-609ef373bdbc", + "attributes": {} + }, + { + "id": "8c18ff05-cccf-4c36-aec0-9f028f531082", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "dfb9a4ca-b2e9-443f-94f2-609ef373bdbc", + "attributes": {} + }, + { + "id": "abeb2352-916d-47ae-a25d-6f65db6a1eb6", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "dfb9a4ca-b2e9-443f-94f2-609ef373bdbc", + "attributes": {} + }, + { + "id": "1283bd27-beb0-4cbe-a79e-3dcad6b2d581", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "dfb9a4ca-b2e9-443f-94f2-609ef373bdbc", + "attributes": {} + }, + { + "id": "93c09c68-ece5-42c1-8c2f-c500d3a985e5", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "dfb9a4ca-b2e9-443f-94f2-609ef373bdbc", + "attributes": {} + } + ], + "tribune-user": [ + { + "id": "ea993cae-419b-4c90-bbfd-9007fda87a2c", + "name": "merchant", + "composite": false, + "clientRole": true, + "containerId": "b94ce53e-0df9-4e5e-bf34-b45df7c478de", + "attributes": {} + }, + { + "id": "620b4716-70e6-4b35-b632-353b1435c31f", + "name": "user", + "composite": false, + "clientRole": true, + "containerId": "b94ce53e-0df9-4e5e-bf34-b45df7c478de", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "5fa6ce4f-1d6d-4ae9-88ee-adcd1345b1b8", + "name": "default-roles-tribune", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "tribune" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "dfb9a4ca-b2e9-443f-94f2-609ef373bdbc", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/tribune/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/tribune/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "193424f3-4a60-4268-af7e-e46a154a8b3c", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/tribune/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/tribune/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "411a9c82-3c02-420d-ab3f-1ac36f2ef228", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "dd7148d7-91c7-4a10-b932-a339339c137f", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "b94ce53e-0df9-4e5e-bf34-b45df7c478de", + "clientId": "tribune-user", + "rootUrl": "https://localhost:8880/", + "baseUrl": "https://localhost:8880/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "https://localhost:8880/*" + ], + "webOrigins": [ + "https://localhost:8880/*", + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "frontchannel.logout.session.required": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "saml.client.signature": "false", + "require.pushed.authorization.requests": "false", + "saml.allow.ecp.flow": "false", + "saml.assertion.signature": "false", + "id.token.as.detached.signature": "false", + "client.secret.creation.time": "1652478755", + "saml.encrypt": "false", + "saml.server.signature": "false", + "exclude.session.state.from.auth.response": "false", + "saml.artifact.binding": "false", + "saml_force_name_id_format": "false", + "tls.client.certificate.bound.access.tokens": "false", + "acr.loa.map": "{}", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "3928aa67-5ccd-4f52-a9c6-8a67821bbe74", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "9091ee89-4ab4-45c7-8ea7-ec2fc6bfae35", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + }, + { + "id": "acd0f49f-b098-4eb4-920b-a773f382b725", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "b38a3282-1e8a-4fa6-ad05-8d40e18bbe57", + "clientId": "tribune-user-management", + "name": "User Management Client", + "description": "Provides User Management to tribune Backend Application.", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "http://localhost:8880/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "frontchannel.logout.session.required": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "saml.client.signature": "false", + "require.pushed.authorization.requests": "false", + "saml.allow.ecp.flow": "false", + "saml.assertion.signature": "false", + "id.token.as.detached.signature": "false", + "client.secret.creation.time": "1652476352", + "saml.encrypt": "false", + "saml.server.signature": "false", + "exclude.session.state.from.auth.response": "false", + "saml.artifact.binding": "false", + "saml_force_name_id_format": "false", + "tls.client.certificate.bound.access.tokens": "false", + "acr.loa.map": "{}", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "fe5b4e8c-5b43-4e08-841b-66f9b6e35020", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "7a63afd2-e648-4aec-a7c2-14eda643f1e5", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "client.secret.creation.time": "1652468991" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ace722d9-3d3e-4310-a2e1-7b78f2ddb84d", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/tribune/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/tribune/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "2cb48511-1ddf-4ee6-9e2c-eecd9a2eba17", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "706a47f0-c290-4ba8-8058-2729d56032ff", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "4df24425-f3bb-4302-88ee-d816fc0bb593", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "e1a38d46-5aee-44e9-92d5-c891bbe6ddcb", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "416e50de-18cf-4617-a235-5e5a5cac6ebf", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "3bac6854-2647-4001-9230-7b9dae5921f3", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "ccf5da0d-70e0-4c65-9e43-a5d49e3ef4d7", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "86997019-b143-494a-bc6e-22d2fe65bdbf", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "128f2343-18d5-4e67-9d97-4c7e1e6c5672", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "afe6f782-29e5-4458-85e3-853abfea20c0", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "df104de1-4ac1-4fbc-ad79-751b7b3abdce", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "19d75c81-4279-4546-a3d6-0039504939bb", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "66051b84-b881-4dfc-a09e-dee035410209", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "55c02a19-578a-4d92-acd5-b1912fd5a2ef", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "9fa34f9a-d6c9-4b8a-bf68-fd4b981a1d56", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "6376ac17-3fee-4ba6-b98d-20d1966e5c69", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "d2ad4fc5-a0ea-4ba6-b110-f66156db1671", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "e7e0b6bc-73a5-4362-9e5f-ae29cf8b02ac", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "9df17a50-4da4-4521-8611-99aac810042d", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "2fd5bb1e-1f0d-4da8-b191-17b2dce33853", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "2be77055-3967-42f6-ba98-8467457f3265", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "f41a7953-2ead-4f53-bbae-168428d32541", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "387a5476-808e-4046-bcb6-a6ab794aaaaa", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "c1bbf010-17dd-498d-abc6-339937abbee7", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "162fcdcd-7197-4429-9a80-8c288d515035", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "aa4c4704-ae6e-4905-899b-1252f65a0c94", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "5a5faeee-2c47-40a0-94fd-fb644a8ac663", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "132b635b-ad8c-477f-8c4c-6c3797b24319", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "a61eea8c-95de-4d99-98d0-bbd96f748580", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "6dc55f42-3e6b-42a3-ae9c-3bb63fe2869a", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "1ff337a0-afa3-4951-b17f-20d803dc0b57", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "0800a94b-b25a-4f33-9cd9-c6a6bb714696", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "03f5c878-75dc-4699-b70c-002575f5b794", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "640a0774-7849-4aa3-b3df-cc85f50421fe", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "b4944c8c-7d66-45b1-90cd-c9a5cd32e82c", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "08499463-28e9-4a76-a3d7-728c8681a1ec", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "45d595dd-e0d0-4349-a1b9-68b5685ee98d", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "c9308331-3ed9-4832-a8e7-1c60d25fd69a", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "ef5934cf-ab2a-4c30-b329-7204e6219385", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "10b18ea2-f9b0-4159-9ce7-5e69f9e20ec0", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "06f0fcda-3e37-4467-be55-87e215adc22c", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "063923e6-9120-458f-b34c-2cdf75e6407d", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "1f5b9a9d-4275-4556-ac2d-356d10134dd3", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "90fa77a3-4978-4633-a232-0dd3ee71a20d", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-address-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper" + ] + } + }, + { + "id": "222e7076-0596-42ee-b2d6-bffaa8792dbb", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "be7b229c-744d-4d68-9a41-e0b86ab3ff89", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "saml-user-attribute-mapper" + ] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "9d1bdff4-c182-43cc-8d73-833c46693012", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "928bd179-bdc7-429e-bd97-ebee3e028a7b", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "24714de3-f299-4713-b404-7b5d1a5239b9", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "1e737846-40eb-4c23-9879-e64f81f07e92", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "82a2c589-cd52-49d5-8489-d8173b252946", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "a6e804cc-41cd-48fa-889e-7e3481bdab5f", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "86b84d34-e3b5-4655-8d58-67a09e1c4df7", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "7d36ec96-afae-4852-b7a0-645de55bc20e", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "dd713267-2034-43d2-bc16-2824cbc43839", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "b0a32ff0-bae4-4ef2-add4-b18d138e57c8", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "59ae23fd-eaea-4fca-b370-abd9aade92d8", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "a7f93a2e-7814-4fc7-9ac2-2babcd3b9ea8", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "07b5934e-6531-4e98-9b90-acf4239aafc9", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "9f1e5e63-8f23-4aa4-93b0-1342ab9f7457", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "2a2a57e7-5d9b-4fc8-b19d-49c1d7139066", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "e88cef7f-d4a1-4440-a106-7e0fbdd00645", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "8f82ec0f-c500-4b9d-949e-dd150da96a7c", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "3748c4de-cef7-4468-909d-8a313dfb431e", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "ff3038f8-973f-4b25-8d6b-ccef01436c1f", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "b9901ad9-de60-4706-bc65-d42dedeebe72", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "785d3b56-c43c-4847-9f8d-21b17c836db6", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Authentication Options", + "userSetupAllowed": false + } + ] + }, + { + "id": "76d827a5-aa26-465d-b6e7-e4d50c2c8411", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "7cae6704-c7f9-4b23-aadd-d284212e59c2", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "044f6d18-da73-4e57-8630-9b59574f37d5", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "60a2f447-7adc-4435-af35-09d0ae1ecc74", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "a99dfea2-6b1e-40ca-a9dc-9d8774b5fbc9", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "6ae86ffc-aa86-413c-b62a-3f976ad9f312", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "clientOfflineSessionMaxLifespan": "0", + "oauth2DevicePollingInterval": "5", + "clientSessionIdleTimeout": "0", + "userProfileEnabled": "false", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5" + }, + "keycloakVersion": "18.0.0", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/backend-application/src/main/java/com/tribune/backend/BackendApplication.java b/backend-application/src/main/java/com/tribune/backend/BackendApplication.java new file mode 100644 index 0000000..880c9c7 --- /dev/null +++ b/backend-application/src/main/java/com/tribune/backend/BackendApplication.java @@ -0,0 +1,16 @@ +package com.tribune.backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + + +@ConfigurationPropertiesScan("com.tribune.backend.infrastructure.config.security") +@SpringBootApplication +public class BackendApplication { + + public static void main(String[] args) { + SpringApplication.run(BackendApplication.class, args); + } + +} diff --git a/backend-application/src/main/resources/application-local.yml b/backend-application/src/main/resources/application-local.yml new file mode 100644 index 0000000..fc76ea1 --- /dev/null +++ b/backend-application/src/main/resources/application-local.yml @@ -0,0 +1,22 @@ + +## Spring embedded webserver +server: + port: 9090 + error: + whitelabel: + enabled: false + +spring: + ## Spring datasource config + datasource: + url: jdbc:h2:mem:testdb + username: admin + password: + # H2 specific + h2: + console: + enabled: true + jpa: + hibernate: + ddl-auto: create-drop + open-in-view: false \ No newline at end of file diff --git a/backend-application/src/main/resources/application.yml b/backend-application/src/main/resources/application.yml new file mode 100644 index 0000000..b8eda29 --- /dev/null +++ b/backend-application/src/main/resources/application.yml @@ -0,0 +1,46 @@ +spring: + application: + name: tribune + mvc: + throw-exception-if-no-handler-found: true + +management: + endpoints: + web: + exposure: + include: * + +#keycloak user client properties +keycloak: + auth-server-url: http://${host}:8080/auth/ + realm: tribune + resource: tribune-user + credentials: + secret: ${keycloak.credentials.secret} + ssl-required: external + principal-attribute: preferred_username + use-resource-role-mappings: true + bearer-only: true + +#keycloak.policy-enforcer-config.enforcement-mode=ENFORCING + +#keycloak admin client properties +tribune: + keycloak: + client: + user-management: + resource: tribune-user-management + client-secret: oT9cm9JhbJH5owI444FxS4DPKLdIpKel + username: users.admin@paywithtribune.com + password: 1234 +#app.cors.allowed-origins=https://localhost:8880 + +#if needed for any reason by frontend +#app.cors.allowed-origins: http://localhost:port +logging: + level: + org: + keycloak: DEBUG + springframework: + security: DEBUG +#spring.jackson.serialization.fail-on-empty-beans=false \ No newline at end of file diff --git a/backend-application/src/main/resources/banner.txt b/backend-application/src/main/resources/banner.txt new file mode 100644 index 0000000..ab51c5b --- /dev/null +++ b/backend-application/src/main/resources/banner.txt @@ -0,0 +1,8 @@ +${AnsiColor.198}${AnsiBackground.255} +${AnsiColor.198}${AnsiBackground.255} ████████████ ████████ ██████ ███████ ██ ██ ██ ██ ████████ +${AnsiColor.198}${AnsiBackground.255} ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ███ +${AnsiColor.198}${AnsiBackground.255} ██ ██ ███ ██ █████ ██ ██ ██ ██ ██ ████████ +${AnsiColor.198}${AnsiBackground.255} ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ███ +${AnsiColor.198}${AnsiBackground.255} ██ ██ ██ ██████ ███████ ████ ██ ██ ████████ +${AnsiColor.198}${AnsiBackground.255} +${AnsiBackground.DEFAULT} diff --git a/backend-application/src/test/java/com/tribune/application/ApplicationTests.java b/backend-application/src/test/java/com/tribune/application/ApplicationTests.java new file mode 100644 index 0000000..062f19e --- /dev/null +++ b/backend-application/src/test/java/com/tribune/application/ApplicationTests.java @@ -0,0 +1,13 @@ +package com.tribune.application; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/backend-domain/README.md b/backend-domain/README.md new file mode 100644 index 0000000..2413ab4 --- /dev/null +++ b/backend-domain/README.md @@ -0,0 +1,22 @@ +# domain module +Contains all the business information that must be consistent and generic all time. + + +### Notes +- Contracts (adapters) implementations should NOT contain any business logic. +- All operations with Domain objects should be initiated and encapsulated only in domain module. +- When using Aggregates: + - **Aggregate Root** is the mothership entity inside the aggregate. + - It identifies a transaction boundaries so that an aggregate root and all related boundaries +should be modified following strong consistency principle (oppose to domain events +and eventual consistency for cross aggregation root changes). + +- In general, your infrastructure can depend on your domain. +The other way around is not ok. +Think about it this way: +what is more likely to be replaced at some point? +Infrastructure or domain? +Infrastructure will change over time (different providers, different servers, ...) +- , Your domain on the other hand will always be there. + + diff --git a/backend-domain/pom.xml b/backend-domain/pom.xml new file mode 100644 index 0000000..d058f60 --- /dev/null +++ b/backend-domain/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + com.tribune + backend + 0.0.1-SNAPSHOT + + + backend-domain + + + 11 + + + + + org.projectlombok + lombok + + + org.apache.commons + commons-lang3 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + commons-validator + commons-validator + + + commons-codec + commons-codec + + + org.junit.jupiter + junit-jupiter-params + test + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/AggregateRoot.java b/backend-domain/src/main/java/com/tribune/backend/domain/AggregateRoot.java new file mode 100644 index 0000000..7e758cb --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/AggregateRoot.java @@ -0,0 +1,15 @@ +package com.tribune.backend.domain; + +import lombok.experimental.SuperBuilder; + +/** + * Aggregate Root is the mothership entity inside the aggregate. It identifies + * a use case boundaries so that an aggregate root and all related boundaries + * should be modified following strong consistency principle (oppose to domain events + * and eventual consistency for cross aggregation root changes). + * + * @param - type of identifier (use wrapper on top of primitive types) + */ +@SuperBuilder +public abstract class AggregateRoot extends Entity { +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/DomainObject.java b/backend-domain/src/main/java/com/tribune/backend/domain/DomainObject.java new file mode 100644 index 0000000..136061d --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/DomainObject.java @@ -0,0 +1,8 @@ +package com.tribune.backend.domain; + +/** + * Indicates any movable object that somehow relates to the domain model. + * Top-level marker interface for all domain objects. + */ +public interface DomainObject { +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/Entity.java b/backend-domain/src/main/java/com/tribune/backend/domain/Entity.java new file mode 100644 index 0000000..dddf770 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/Entity.java @@ -0,0 +1,59 @@ +package com.tribune.backend.domain; + +import com.tribune.backend.domain.error.DomainException; +import lombok.experimental.SuperBuilder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Abstract representation of Domain Entities + * @param - type of identifier (use wrapper on top of primitive types) + */ +@SuperBuilder +public abstract class Entity implements IdentifiableDomainObject { + + private final List domainEvents = new ArrayList<>(); + private ID id; + + @Override + public ID getId() { + return id; + } + + public void setId(ID id) { + this.id = id; + } + + protected T registerEvent(T event) { + if (event==null){ + //todo: + throw new DomainException("Domain event must not be null!"); + } + this.domainEvents.add(event); + return event; + } + + public void clearDomainEvents() { + this.domainEvents.clear(); + } + + public List domainEvents() { + return Collections.unmodifiableList(this.domainEvents); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Entity entity = (Entity) o; + return Objects.equals(id, entity.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} \ No newline at end of file diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/IdentifiableDomainObject.java b/backend-domain/src/main/java/com/tribune/backend/domain/IdentifiableDomainObject.java new file mode 100644 index 0000000..217b771 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/IdentifiableDomainObject.java @@ -0,0 +1,9 @@ +package com.tribune.backend.domain; + +/** + * Interface for all domain objects that can be uniquely identified in some context + * @param - type of identifier (use wrapper on top of primitive types) + */ +public interface IdentifiableDomainObject extends DomainObject { + ID getId(); +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/LocalEntity.java b/backend-domain/src/main/java/com/tribune/backend/domain/LocalEntity.java new file mode 100644 index 0000000..8952d75 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/LocalEntity.java @@ -0,0 +1,11 @@ +package com.tribune.backend.domain; + +import lombok.experimental.SuperBuilder; + +/** + * Marker superclass for all local Entities + * @param - type of identifier (use wrapper on top of primitive types) + */ +@SuperBuilder +public abstract class LocalEntity extends Entity { +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/ValueObject.java b/backend-domain/src/main/java/com/tribune/backend/domain/ValueObject.java new file mode 100644 index 0000000..d46a3f7 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/ValueObject.java @@ -0,0 +1,9 @@ +package com.tribune.backend.domain; + +/** + * Marker interface for all value objects. + * Implementations of this interface are required to be immutable and + * implement equals() and hashCode(). + */ +public interface ValueObject extends DomainObject { +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/context/CustomerOrder.java b/backend-domain/src/main/java/com/tribune/backend/domain/context/CustomerOrder.java new file mode 100644 index 0000000..f384e15 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/context/CustomerOrder.java @@ -0,0 +1,20 @@ +package com.tribune.backend.domain.context; + +import com.tribune.backend.domain.AggregateRoot; +import com.tribune.backend.domain.dto.customer.Customer; +import com.tribune.backend.domain.dto.order.Order; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +import java.util.UUID; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) +public class CustomerOrder extends AggregateRoot { + + + private Customer customer; + private Order order; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/context/package-info.java b/backend-domain/src/main/java/com/tribune/backend/domain/context/package-info.java new file mode 100644 index 0000000..83cdb6d --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/context/package-info.java @@ -0,0 +1,5 @@ +/** + * Bounded Contexts + * + */ +package com.tribune.backend.domain.context; \ No newline at end of file diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/GenericResponse.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/GenericResponse.java new file mode 100644 index 0000000..e841e13 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/GenericResponse.java @@ -0,0 +1,23 @@ +package com.tribune.backend.domain.dto; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class GenericResponse { + + private int code; + + private String message; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Object reason; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private T data; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/OrdersResponse.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/OrdersResponse.java new file mode 100644 index 0000000..3509b22 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/OrdersResponse.java @@ -0,0 +1,12 @@ +package com.tribune.backend.domain.dto; + + +import lombok.*; + +import java.util.List; + +@Data +@Builder +public class OrdersResponse { + private List blogs; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/PlaceOrderRequest.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/PlaceOrderRequest.java new file mode 100644 index 0000000..5c219e2 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/PlaceOrderRequest.java @@ -0,0 +1,26 @@ +package com.tribune.backend.domain.dto; + +import lombok.*; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class PlaceOrderRequest { + + @NotNull(message = "Content is missing.") + @Min(value = 20) + private String content; + + @NotNull + @NotEmpty + @NotBlank + private String user; + +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/SingleOrderResponse.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/SingleOrderResponse.java new file mode 100644 index 0000000..6317ca6 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/SingleOrderResponse.java @@ -0,0 +1,27 @@ +package com.tribune.backend.domain.dto; + + +import com.tribune.backend.domain.enums.BlogStatus; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Builder +public class SingleOrderResponse { + + private String id; + + private String title; + + private String content; + + private BlogStatus status; + + private String appUser; + + private LocalDateTime creationTimestamp; + + private LocalDateTime updateTimestamp; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/UpdateOrderRequest.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/UpdateOrderRequest.java new file mode 100644 index 0000000..13b72da --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/UpdateOrderRequest.java @@ -0,0 +1,26 @@ +package com.tribune.backend.domain.dto; + +import lombok.*; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class UpdateOrderRequest { + + @NotNull(message = "Content is missing.") + @Min(value = 20) + private String content; + + @NotNull + @NotEmpty + @NotBlank + private String user; + +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/Customer.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/Customer.java new file mode 100644 index 0000000..5aac310 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/Customer.java @@ -0,0 +1,38 @@ +package com.tribune.backend.domain.dto.customer; + + +import com.tribune.backend.domain.Entity; +import com.tribune.backend.domain.dto.customer.address.Address; +import com.tribune.backend.domain.dto.customer.payment.Payment; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +import java.util.List; +import java.util.UUID; + +@Getter +@Setter +@SuperBuilder +@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) +public class Customer extends Entity { + + + private UUID id; + + private String displayName; + + private String firstName; + + private String lastName; + + private CustomerState state; + + private CustomerType type; + + //todo: like Address + private List paymentList; + + private List
addressList; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/CustomerState.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/CustomerState.java new file mode 100644 index 0000000..8c59c07 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/CustomerState.java @@ -0,0 +1,12 @@ +package com.tribune.backend.domain.dto.customer; + +public enum CustomerState { + CREATED, + REGISTERED, + IDENTIFIED, + ACTIVE, + REJECTED, + DISABLED, + TERMINATED, + DELETED +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/CustomerType.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/CustomerType.java new file mode 100644 index 0000000..65debca --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/CustomerType.java @@ -0,0 +1,6 @@ +package com.tribune.backend.domain.dto.customer; + +public enum CustomerType { + TRADITIONAL, + PREMIUM +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/Address.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/Address.java new file mode 100644 index 0000000..77df6a7 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/Address.java @@ -0,0 +1,22 @@ +package com.tribune.backend.domain.dto.customer.address; + +import com.tribune.backend.domain.ValueObject; +import com.tribune.backend.domain.dto.customer.common.Country; +import lombok.Builder; +import lombok.NonNull; +import lombok.Value; + +@Value +@Builder +public class Address implements ValueObject{ + + @NonNull Country country; + + @NonNull String city; + + @NonNull Street street; + + String flatNumber; + + String additionalInformation; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/PostalCode.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/PostalCode.java new file mode 100644 index 0000000..cd5ab96 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/PostalCode.java @@ -0,0 +1,15 @@ +package com.tribune.backend.domain.dto.customer.address; + +import lombok.NonNull; +import lombok.Value; + +@Value +public class PostalCode { + + @NonNull String value; + + @Override + public String toString() { + return value; + } +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/Street.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/Street.java new file mode 100644 index 0000000..c870eca --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/address/Street.java @@ -0,0 +1,10 @@ +package com.tribune.backend.domain.dto.customer.address; + +import lombok.NonNull; +import lombok.Value; + +@Value +public class Street { + @NonNull String name; + String streetNumber; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/common/Country.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/common/Country.java new file mode 100644 index 0000000..f337564 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/common/Country.java @@ -0,0 +1,19 @@ +package com.tribune.backend.domain.dto.customer.common; + +import com.tribune.backend.domain.ValueObject; +import lombok.NonNull; +import lombok.Value; + +@Value +public class Country implements ValueObject { + @NonNull String code; + + public Country(@NonNull String code) { + this.code = code; + } + + @Override + public String toString() { + return code; + } +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/common/Profession.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/common/Profession.java new file mode 100644 index 0000000..4042d35 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/common/Profession.java @@ -0,0 +1,46 @@ +package com.tribune.backend.domain.dto.customer.common; + +public enum Profession { + + EMPLOYEE("profession_employee", 1), + WORKER("profession_worker", 2), + TRAINEE("profession_trainee", 3), + OFFICIAL("profession_official", 4), + FREELANCER("profession_freelancer", 5), + MANAGING_PARTNER("profession_managing_partner", 6), + HOUSEWIFE("profession_housewife", 7), + HOUSEMAN("profession_houseman", 8), + EXECUTIVE("profession_executive", 9), + UNEMPLOYED("profession_unemployed", 10), + PENSIONER("profession_pensioner", 11), + RETIREE("profession_retiree", 12), + PUPIL("profession_pupil", 13), + SELF_EMPLOYED("profession_self_employed", 14), + STUDENT("profession_student", 15), + OTHER("profession_other", 16), + MANAGER("profession_manager", 17), + LIBERAL_PROFESSION("profession_liberal_profession", 18), + HOUSEPARENT("profession_houseparent", 19), + LIVING_OFF_INVESTMENTS("profession_living_off_investments", 20), + COMPANY_DIRECTOR("profession_company_director", 21);; + + /** + * String code representation of the academic title used by the legacy API for registration purposes. + */ + private final String name; + + private final Integer code; + + Profession(final String name, final Integer code) { + this.name = name; + this.code = code; + } + + public String getName() { + return name; + } + + public Integer getCode() { + return code; + } +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/invoice/Invoice.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/invoice/Invoice.java new file mode 100644 index 0000000..e104ff3 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/invoice/Invoice.java @@ -0,0 +1,19 @@ +package com.tribune.backend.domain.dto.customer.invoice; + + +import com.tribune.backend.domain.dto.customer.Customer; +import lombok.*; + +import java.util.UUID; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Invoice { + + private UUID id; + + private Customer customer; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/payment/Payment.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/payment/Payment.java new file mode 100644 index 0000000..ebf43c6 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/customer/payment/Payment.java @@ -0,0 +1,20 @@ +package com.tribune.backend.domain.dto.customer.payment; + + +import com.tribune.backend.domain.LocalEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +import java.util.UUID; + +@Data +@SuperBuilder +@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) +public class Payment extends LocalEntity{ + + private String note; + + private UUID customerId; + +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/order/Order.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/order/Order.java new file mode 100644 index 0000000..c623a06 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/order/Order.java @@ -0,0 +1,26 @@ +package com.tribune.backend.domain.dto.order; + + +import com.tribune.backend.domain.Entity; +import com.tribune.backend.domain.dto.order.lineitem.LineItem; +import com.tribune.backend.domain.enums.OrderStatus; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +import java.util.List; +import java.util.UUID; + +@Setter +@Getter +@SuperBuilder +@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) +public class Order extends Entity { + + private UUID id; + + private OrderStatus status; + + private List lineItemEntities; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/order/lineitem/LineItem.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/order/lineitem/LineItem.java new file mode 100644 index 0000000..55ec5db --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/order/lineitem/LineItem.java @@ -0,0 +1,22 @@ +package com.tribune.backend.domain.dto.order.lineitem; + + +import com.tribune.backend.domain.dto.order.Order; +import com.tribune.backend.domain.dto.order.lineitem.product.Product; +import lombok.*; + +import java.util.UUID; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class LineItem { + + private UUID id; + + private Order order; + + private Product product; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/dto/order/lineitem/product/Product.java b/backend-domain/src/main/java/com/tribune/backend/domain/dto/order/lineitem/product/Product.java new file mode 100644 index 0000000..c1d4a86 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/dto/order/lineitem/product/Product.java @@ -0,0 +1,16 @@ +package com.tribune.backend.domain.dto.order.lineitem.product; + + +import lombok.*; + +import java.util.UUID; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Product { + + private UUID id; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/enums/BlogStatus.java b/backend-domain/src/main/java/com/tribune/backend/domain/enums/BlogStatus.java new file mode 100644 index 0000000..11ad88c --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/enums/BlogStatus.java @@ -0,0 +1,5 @@ +package com.tribune.backend.domain.enums; + +public enum BlogStatus { + CREATED,APPROVED,DECLINED,SETTLED; +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/enums/OrderStatus.java b/backend-domain/src/main/java/com/tribune/backend/domain/enums/OrderStatus.java new file mode 100644 index 0000000..b954558 --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/enums/OrderStatus.java @@ -0,0 +1,6 @@ +package com.tribune.backend.domain.enums; + +public enum OrderStatus { + + INITIALIZED,PROCESSING,SUBMITTED,CANCELLED +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/enums/UserStatus.java b/backend-domain/src/main/java/com/tribune/backend/domain/enums/UserStatus.java new file mode 100644 index 0000000..e10d0fa --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/enums/UserStatus.java @@ -0,0 +1,5 @@ +package com.tribune.backend.domain.enums; + +public enum UserStatus { + ACTIVE, DISABLED; +} \ No newline at end of file diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/error/DomainException.java b/backend-domain/src/main/java/com/tribune/backend/domain/error/DomainException.java new file mode 100644 index 0000000..cb9e09b --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/error/DomainException.java @@ -0,0 +1,8 @@ +package com.tribune.backend.domain.error; + +public class DomainException extends RuntimeException{ + + public DomainException(String message) { + super(message); + } +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/service/CustomerRepositoryAdapter.java b/backend-domain/src/main/java/com/tribune/backend/domain/service/CustomerRepositoryAdapter.java new file mode 100644 index 0000000..0d42dfb --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/service/CustomerRepositoryAdapter.java @@ -0,0 +1,32 @@ +package com.tribune.backend.domain.service; + +import com.tribune.backend.domain.dto.customer.Customer; + +import java.util.List; +import java.util.UUID; + +/** + * Please note that adapter implementation should NOT contain any business logic. + * All operations with Domain objects should be initiated and encapsulated only in domain module + */ +public interface CustomerRepositoryAdapter { + + /** + * Use Pagination to return a subset of the Customer collection + * @param pageNumber - zero-based page index, must not be negative + * @param pageSize - the size of the page to be returned, must be greater than 0 + * @return a subset of the Customer collection, otherwise an empty list + */ + List findAll(final int pageNumber, final int pageSize); + + + Customer findByCustomerId(final UUID customerId); + + /** + * Saves a customer in the persistence repository. + * + * @param customer the customer to save + * @return the saved {@link Customer} + */ + Customer save(Customer customer); +} diff --git a/backend-domain/src/main/java/com/tribune/backend/domain/validation/ValidationError.java b/backend-domain/src/main/java/com/tribune/backend/domain/validation/ValidationError.java new file mode 100644 index 0000000..b98d74c --- /dev/null +++ b/backend-domain/src/main/java/com/tribune/backend/domain/validation/ValidationError.java @@ -0,0 +1,12 @@ +package com.tribune.backend.domain.validation; + +import lombok.Value; + +@Value +public class ValidationError { + String path; + String fieldName; + Object expectedValue; + Object actualValue; + String reason; +} \ No newline at end of file diff --git a/backend-infrastructure/README.md b/backend-infrastructure/README.md new file mode 100644 index 0000000..3f6add0 --- /dev/null +++ b/backend-infrastructure/README.md @@ -0,0 +1,2 @@ +# infrastructure module + diff --git a/backend-infrastructure/pom.xml b/backend-infrastructure/pom.xml new file mode 100644 index 0000000..440dee8 --- /dev/null +++ b/backend-infrastructure/pom.xml @@ -0,0 +1,120 @@ + + + 4.0.0 + + com.tribune + backend + 0.0.1-SNAPSHOT + + + backend-infrastructure + + + 11 + + + + ${project.groupId} + backend-domain + + + ${project.groupId} + backend-api-definition + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-configuration-processor + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.apache.logging.log4j + log4j-slf4j-impl + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + + + + + org.springframework.boot + spring-boot-starter-security + + + org.keycloak + keycloak-spring-boot-starter + + + org.keycloak + keycloak-admin-client + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + + + + + + + + + + + + + + ${project.groupId} + backend-domain + test-jar + test + + + diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/AppConfig.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/AppConfig.java new file mode 100644 index 0000000..7123d4c --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/AppConfig.java @@ -0,0 +1,80 @@ +package com.tribune.backend.infrastructure.config; + +import com.tribune.backend.domain.dto.GenericResponse; +import com.tribune.backend.infrastructure.config.security.SecurityConfig; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static javax.ws.rs.core.Response.Status.Family.SUCCESSFUL; + +/** + * top tier config file : a config for configs + * can log responses as following: + * + * ObjectMapper mapper=new ObjectMapper(); + * mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false); + * + * ... mapper.writeValueAsString(value) + **/ +@Configuration +public class AppConfig { + + /** + * Fix cycle dependency formed by {@link org.keycloak.adapters.KeycloakConfigResolver} + * if specified within {@link SecurityConfig} + **/ + @Bean + public KeycloakSpringBootConfigResolver keycloakConfigResolver() { + return new KeycloakSpringBootConfigResolver(); + } + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + + return builder -> { + JsonSerializer jsonSerializer = new JsonSerializer<>() { + @Override + public void serialize(Response value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + + String message = value.getStatusInfo().getFamily().equals(SUCCESSFUL) ? "Success" : "Error"; + List reason = new ArrayList<>(); + Object data = null; + switch (value.getStatusInfo().getStatusCode()) { + case 409: + reason.add("Email/username already exists!"); + break; + case 201: + //returns as a result of newly generated resources + //returns a URL that ends with the newly created resource id + //"http://localhost:8080/auth/admin/realms/tribune/users/b61adfce-ffe6-4d68-9169-0c476ae11179" + data = value.getLocation(); + break; + } + reason.add(value.getStatusInfo().getReasonPhrase()); + + GenericResponse response = GenericResponse.builder() + .data(data) + .code(value.getStatus()) + .message(message) + .reason(reason) + .build(); + + gen.writePOJO(response); + + } + }; + + builder.serializerByType(Response.class, jsonSerializer); + }; + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CorsConfig.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CorsConfig.java new file mode 100644 index 0000000..e40565a --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CorsConfig.java @@ -0,0 +1,30 @@ +package com.tribune.backend.infrastructure.config.security; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import java.util.Collections; +import java.util.List; + +@Configuration +public class CorsConfig { + +// @Value("${app.cors.allowed-origins}") +// private List allowedOrigins; +// +// @Bean +// CorsFilter corsFilter() { +// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); +// CorsConfiguration config = new CorsConfiguration(); +// config.setAllowCredentials(true); +// config.setAllowedOrigins(allowedOrigins); +// config.setAllowedMethods(Collections.singletonList("*")); +// config.setAllowedHeaders(Collections.singletonList("*")); +// source.registerCorsConfiguration("/**", config); +// return new CorsFilter(source); +// } +} \ No newline at end of file diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CredentialsUtils.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CredentialsUtils.java new file mode 100644 index 0000000..2cff08f --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CredentialsUtils.java @@ -0,0 +1,16 @@ +package com.tribune.backend.infrastructure.config.security; + +import org.keycloak.representations.idm.CredentialRepresentation; + +public class CredentialsUtils { + + private CredentialsUtils(){} + + public static CredentialRepresentation createPasswordCredentials(String password) { + CredentialRepresentation passwordCredentials = new CredentialRepresentation(); + passwordCredentials.setTemporary(false); + passwordCredentials.setType(CredentialRepresentation.PASSWORD); + passwordCredentials.setValue(password); + return passwordCredentials; + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CustomFilter.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CustomFilter.java new file mode 100644 index 0000000..6f13c84 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/CustomFilter.java @@ -0,0 +1,21 @@ +package com.tribune.backend.infrastructure.config.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.*; +import java.io.IOException; + + +@Slf4j +public class CustomFilter extends GenericFilterBean { + + @Override + public void doFilter( + ServletRequest request, + ServletResponse response, + FilterChain chain) throws IOException, ServletException { + log.error("XXX"); + chain.doFilter(request, response); + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/KeycloakConfigProperties.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/KeycloakConfigProperties.java new file mode 100644 index 0000000..bd1e808 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/KeycloakConfigProperties.java @@ -0,0 +1,45 @@ +package com.tribune.backend.infrastructure.config.security; + + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.jboss.resteasy.client.jaxrs.ResteasyClient; +import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.admin.client.resource.UsersResource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Slf4j +@Getter +@Setter +@ConfigurationProperties(prefix = "tribune.keycloak.client.user-management") +public class KeycloakConfigProperties { + + @Value("${keycloak.auth-server-url}") + private String authServerUrl; + @Value("${keycloak.realm}") + private String realm; + private String resource; + private String clientSecret; + private String username; + private String password; + + public UsersResource getInstance(){ + ResteasyClient resteasyClient=new ResteasyClientBuilder() + .connectionPoolSize(10) + .build(); + return KeycloakBuilder.builder() + .serverUrl(authServerUrl) + .realm(realm) + .grantType(OAuth2Constants.PASSWORD) + .username(username) + .password(password) + .clientId(resource) + .clientSecret(clientSecret) + .resteasyClient(resteasyClient) + .build().realm(realm).users(); + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/SecurityConfig.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/SecurityConfig.java new file mode 100644 index 0000000..dbd34f5 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/SecurityConfig.java @@ -0,0 +1,94 @@ +package com.tribune.backend.infrastructure.config.security; + +import lombok.extern.slf4j.Slf4j; +import org.keycloak.adapters.springsecurity.KeycloakConfiguration; +import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; +import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; +import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; +import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; + +/** + * The {@link EnableGlobalMethodSecurity} enables direct config of role-based access + * to our controllers. See the following example: + *
{@code
+ * @PreAuthorize("hasRole('USER')")
+ * @GetMapping("/title)
+ * public ResponseEntitygetTitle(){
+ *      //
+ *      return title;
+ * }
+ * }
+ */ +@Slf4j +@Order(Ordered.HIGHEST_PRECEDENCE) +@KeycloakConfiguration +//@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) { + KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); + keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); + auth.authenticationProvider(keycloakAuthenticationProvider); + } + + /** + * The other way around + * return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); + * */ + @Bean + @Override + protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { + return new NullAuthenticatedSessionStrategy(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + + super.configure(http); + // Enable CORS and disable CSRF + http + .cors() + .and() + .csrf().disable() + // Set session management to stateless + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers(HttpMethod.GET, "/v1/transaction/status/**").hasRole(MERCHANT) + .antMatchers(HttpMethod.POST, "/v1/transaction/**").hasRole(MERCHANT) + .antMatchers(HttpMethod.POST, "/v1/users/**").anonymous() + .antMatchers(HttpMethod.GET, "/api/user-data/me", "/actuator/**").permitAll() + .antMatchers(HttpMethod.GET, "/v1/dummy/**").permitAll() + .antMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs", "/v3/api-docs/**").permitAll() + .anyRequest().fullyAuthenticated(); + } + + /** + * provided by keycloak to handle authentication failures. + * */ + @Bean + @Override + protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception { + KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(this.authenticationManagerBean()); + filter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy()); + filter.setAuthenticationFailureHandler(new TribuneKeycloakAuthenticationFailureHandler()); + return filter; + } + + public static final String MERCHANT = "merchant"; + public static final String USER = "user"; + public static final String MANAGE_USERS = "manage-users"; +} \ No newline at end of file diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/TribuneKeycloakAuthenticationFailureHandler.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/TribuneKeycloakAuthenticationFailureHandler.java new file mode 100644 index 0000000..9028164 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/TribuneKeycloakAuthenticationFailureHandler.java @@ -0,0 +1,36 @@ +package com.tribune.backend.infrastructure.config.security; + +import com.tribune.backend.domain.dto.GenericResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.keycloak.util.JsonSerialization.mapper; + +@Slf4j +@AllArgsConstructor +public class TribuneKeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException { + ex.printStackTrace(); + log.error("Authentication Error: {}", ex.getMessage()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + //todo:check removal of WWW_AUTHENTICATE header from response + GenericResponse genericResponse = GenericResponse.builder() + .message(ex.getMessage()) + .reason(response.getHeader(HttpHeaders.WWW_AUTHENTICATE)) + .code(response.getStatus()) + .build(); + response.getWriter().write(mapper.writeValueAsString(genericResponse)); + } +} \ No newline at end of file diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserDTO.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserDTO.java new file mode 100644 index 0000000..f660e74 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserDTO.java @@ -0,0 +1,24 @@ +package com.tribune.backend.infrastructure.config.security; + +import lombok.*; + +import javax.validation.constraints.NotBlank; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class UserDTO { + + @NotBlank + private String username; + @NotBlank + private String email; + @NotBlank + private String password; + @NotBlank + private String firstName; + @NotBlank + private String lastName; +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserLoginRequest.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserLoginRequest.java new file mode 100644 index 0000000..c5c5732 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserLoginRequest.java @@ -0,0 +1,21 @@ +package com.tribune.backend.infrastructure.config.security; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +@Getter +@Setter +@AllArgsConstructor +@Builder +public class UserLoginRequest { + + @NotBlank + private String username; + @NotBlank + private String password; +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserManagementService.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserManagementService.java new file mode 100644 index 0000000..fa5de92 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserManagementService.java @@ -0,0 +1,25 @@ +package com.tribune.backend.infrastructure.config.security; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.tribune.backend.domain.dto.GenericResponse; +import org.keycloak.representations.idm.UserRepresentation; + +import javax.ws.rs.core.Response; +import java.util.List; + + +public interface UserManagementService { + + Response addUser(UserDTO userDTO); + + List getUser(String userName); + + void updateUser(String userId, UserDTO userDTO); + void deleteUser(String userId); + + void sendVerificationLink(String userId); + + void sendResetPassword(String userId); + + GenericResponse authenticate(UserLoginRequest userDTO); +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserManagementServiceImpl.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserManagementServiceImpl.java new file mode 100644 index 0000000..f86e4cb --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/config/security/UserManagementServiceImpl.java @@ -0,0 +1,139 @@ +package com.tribune.backend.infrastructure.config.security; + +import com.tribune.backend.domain.dto.GenericResponse; +import com.tribune.backend.infrastructure.error.BackendException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.netty.http.client.HttpClient; + +import javax.ws.rs.core.Response; +import java.net.URI; +import java.time.Duration; +import java.util.Collections; +import java.util.List; + + +@Slf4j +@Service +public class UserManagementServiceImpl implements UserManagementService { + @Value("${keycloak.resource}") + private String tribuneSpringbootClient; + + private final KeycloakConfigProperties properties; + + @Autowired + public UserManagementServiceImpl(KeycloakConfigProperties properties) { + this.properties = properties; + } + + @Override + public Response addUser(UserDTO userDTO) { + CredentialRepresentation credential = CredentialsUtils + .createPasswordCredentials(userDTO.getPassword()); + + UserRepresentation user = new UserRepresentation(); + + user.setUsername(userDTO.getUsername()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + user.setEmail(userDTO.getEmail()); + user.setCredentials(Collections.singletonList(credential)); + user.setEnabled(true); + + return properties.getInstance().create(user); + } + + @Override + public List getUser(String userName) { + return properties.getInstance().search(userName, true); + } + + @Override + public void updateUser(String userId, UserDTO userDTO) { + CredentialRepresentation credential = CredentialsUtils + .createPasswordCredentials(userDTO.getPassword()); + UserRepresentation user = new UserRepresentation(); + user.setUsername(userDTO.getUsername()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + user.setEmail(userDTO.getEmail()); + user.setCredentials(Collections.singletonList(credential)); + //todo:needs confirmation + properties.getInstance().get(userId).update(user); + } + + @Override + public void deleteUser(String userId) { + //todo:needs confirmation + properties.getInstance().get(userId) + .remove(); + } + + @Override + public void sendVerificationLink(String userId) { + //todo:needs confirmation + properties.getInstance().get(userId) + .sendVerifyEmail(); + } + + @Override + public void sendResetPassword(String userId) { + //todo:needs confirmation + properties.getInstance().get(userId) + .executeActionsEmail(List.of("UPDATE_PASSWORD")); + } + + @Override + public GenericResponse authenticate(UserLoginRequest userDTO) { + URI uri=UriComponentsBuilder.fromUriString( + String.format("%s/realms/%s/protocol/openid-connect/token", + properties.getAuthServerUrl(), properties.getRealm()) + ).build().toUri(); + + HttpClient httpClient = HttpClient.create() + .responseTimeout(Duration.ofSeconds(2)); + + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("username", userDTO.getUsername()); + formData.add("password", userDTO.getPassword()); + formData.add("client_id", tribuneSpringbootClient); + formData.add("grant_type", CredentialRepresentation.PASSWORD); + + ObjectNode node= WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build() + .post() + .uri(uri) + .body(BodyInserters.fromFormData(formData)) + .accept(MediaType.APPLICATION_JSON) + .exchangeToMono(clientResponse -> { + if (clientResponse.statusCode().is4xxClientError()) { + log.error("error"); + return clientResponse.bodyToMono(ObjectNode.class).map(val -> { + + throw new BackendException(val, clientResponse.statusCode()); + }); + } else + return clientResponse.bodyToMono(ObjectNode.class); + }) + .block(); + + return GenericResponse.builder() + .code(200) + .message("Success!") + .data(node) + .build(); + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/DummyController.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/DummyController.java new file mode 100644 index 0000000..1f49065 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/DummyController.java @@ -0,0 +1,19 @@ +package com.tribune.backend.infrastructure.controller; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +@Slf4j +@RequestMapping("/v1/dummy") +@RestController +public class DummyController { + + @GetMapping("/hello") + public String hello(){ + return "Hello"; + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/OrdersController.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/OrdersController.java new file mode 100644 index 0000000..4a885d0 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/OrdersController.java @@ -0,0 +1,30 @@ +package com.tribune.backend.infrastructure.controller; + +import com.tribune.backend.domain.dto.PlaceOrderRequest; +import com.tribune.backend.domain.dto.GenericResponse; +import com.tribune.backend.domain.dto.SingleOrderResponse; +import com.tribune.backend.infrastructure.services.OrderService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +@Slf4j +@AllArgsConstructor +@RequestMapping("/v1/orders") +@RestController +public class OrdersController { + + private final OrderService orderService; + + @PostMapping("/order") + public GenericResponse createBlog(@Valid @RequestBody PlaceOrderRequest createOrderRequest) { + + return GenericResponse.builder() + .code(201) + .data(orderService.placeOrder(createOrderRequest)) + .message("Created!") + .build(); + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/UserController.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/UserController.java new file mode 100644 index 0000000..4649b68 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/controller/UserController.java @@ -0,0 +1,64 @@ +package com.tribune.backend.infrastructure.controller; + + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.tribune.backend.domain.dto.GenericResponse; +import com.tribune.backend.infrastructure.config.security.UserDTO; +import com.tribune.backend.infrastructure.config.security.UserLoginRequest; +import com.tribune.backend.infrastructure.config.security.UserManagementService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.ws.rs.core.Response; +import java.util.List; + +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping(path = "/v1/users") +public class UserController { + private final UserManagementService service; + + + @PostMapping("/user/auth") + public GenericResponse authenticate(@Valid @RequestBody UserLoginRequest userDTO){ + return service.authenticate(userDTO); + } + + @PostMapping("/user") + public Response addUser(@Valid @RequestBody UserDTO userDTO){ + return service.addUser(userDTO); + } + + @GetMapping(path = "/user/{userName}") + public List getUser(@PathVariable("userName") String userName){ + return service.getUser(userName); + } + + @PutMapping(path = "/user/{userId}") + public String updateUser(@PathVariable("userId") String userId, @RequestBody UserDTO userDTO){ + service.updateUser(userId, userDTO); + return "User Details Updated Successfully."; + } + + @DeleteMapping(path = "/user/{userId}") + public String deleteUser(@PathVariable("userId") String userId){ + service.deleteUser(userId); + return "User Deleted Successfully."; + } + + @GetMapping(path = "/user/verify/{userId}") + public String sendVerificationLink(@PathVariable("userId") String userId){ + service.sendVerificationLink(userId); + return "Verification Link Send to Registered E-mail Id."; + } + + @GetMapping(path = "/user/reset-password/{userId}") + public String sendResetPasswordLink(@PathVariable("userId") String userId){ + service.sendResetPassword(userId); + return "Reset Password Link Send Successfully to Registered E-mail Id."; + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/AddressEntity.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/AddressEntity.java new file mode 100644 index 0000000..44db402 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/AddressEntity.java @@ -0,0 +1,28 @@ +package com.tribune.backend.infrastructure.db.entities; + + +import lombok.*; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import java.util.UUID; + + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +public class AddressEntity { + + @Id + private UUID id; + + + @ManyToOne + @JoinColumn(name = "customer",referencedColumnName = "id") + private CustomerEntity customerEntity; +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/CustomerEntity.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/CustomerEntity.java new file mode 100644 index 0000000..74848cd --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/CustomerEntity.java @@ -0,0 +1,40 @@ +package com.tribune.backend.infrastructure.db.entities; + + +import com.tribune.backend.domain.dto.customer.CustomerState; +import com.tribune.backend.domain.dto.customer.CustomerType; +import lombok.*; + +import javax.persistence.*; +import java.util.List; +import java.util.UUID; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +public class CustomerEntity { + + @Id + private UUID id; + + private String displayName; + + private String firstName; + + private String lastName; + + @Enumerated(value = EnumType.STRING) + private CustomerState state; + + @Enumerated(value = EnumType.STRING) + private CustomerType type; + + @OneToMany(mappedBy = "customerEntity", fetch = FetchType.LAZY) + private List paymentEntityList; + + @OneToMany(mappedBy = "customerEntity", fetch = FetchType.LAZY) + private List addressEntityList; +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/InvoiceEntity.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/InvoiceEntity.java new file mode 100644 index 0000000..f3544b5 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/InvoiceEntity.java @@ -0,0 +1,26 @@ +package com.tribune.backend.infrastructure.db.entities; + + +import lombok.*; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import java.util.UUID; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +public class InvoiceEntity { + + + @Id + private UUID id; + + + @ManyToOne + private CustomerEntity customerEntity; +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/LineItemEntity.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/LineItemEntity.java new file mode 100644 index 0000000..e3b686f --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/LineItemEntity.java @@ -0,0 +1,25 @@ +package com.tribune.backend.infrastructure.db.entities; + + +import lombok.*; + +import javax.persistence.*; +import java.util.UUID; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +public class LineItemEntity { + + @Id + private UUID id; + + @ManyToOne + private OrderEntity orderEntity; + + @OneToOne + private ProductEntity productEntity; +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/OrderEntity.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/OrderEntity.java new file mode 100644 index 0000000..0197d65 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/OrderEntity.java @@ -0,0 +1,28 @@ +package com.tribune.backend.infrastructure.db.entities; + + +import com.tribune.backend.domain.enums.OrderStatus; +import lombok.*; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.List; +import java.util.UUID; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +public class OrderEntity { + + @Id + private UUID id; + + private OrderStatus status; + + @OneToMany + private List lineItemEntities; +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/PaymentEntity.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/PaymentEntity.java new file mode 100644 index 0000000..8fdf54d --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/PaymentEntity.java @@ -0,0 +1,27 @@ +package com.tribune.backend.infrastructure.db.entities; + + +import lombok.*; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import java.util.UUID; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +public class PaymentEntity { + @Id + private UUID id; + + + + @ManyToOne + @JoinColumn(name = "customer",referencedColumnName = "id") + private CustomerEntity customerEntity; +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/ProductEntity.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/ProductEntity.java new file mode 100644 index 0000000..e0b6b18 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/entities/ProductEntity.java @@ -0,0 +1,20 @@ +package com.tribune.backend.infrastructure.db.entities; + + +import lombok.*; + +import javax.persistence.Entity; +import javax.persistence.Id; +import java.util.UUID; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +public class ProductEntity { + + @Id + private UUID id; +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/repository/CustomerRepository.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/repository/CustomerRepository.java new file mode 100644 index 0000000..05c93c5 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/repository/CustomerRepository.java @@ -0,0 +1,14 @@ +package com.tribune.backend.infrastructure.db.repository; + + +import com.tribune.backend.infrastructure.db.entities.CustomerEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface CustomerRepository extends JpaRepository { + +} + diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/repository/OrderRepository.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/repository/OrderRepository.java new file mode 100644 index 0000000..114794c --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/db/repository/OrderRepository.java @@ -0,0 +1,12 @@ +package com.tribune.backend.infrastructure.db.repository; + + +import com.tribune.backend.infrastructure.db.entities.OrderEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface OrderRepository extends JpaRepository { + + +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/BackendException.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/BackendException.java new file mode 100644 index 0000000..764927c --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/BackendException.java @@ -0,0 +1,19 @@ +package com.tribune.backend.infrastructure.error; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; +import lombok.Setter; +import org.springframework.http.HttpStatus; + +@Getter +@Setter +public class BackendException extends RuntimeException{ + + private final ObjectNode error; + private final HttpStatus status; + + public BackendException(ObjectNode error, HttpStatus status) { + this.error= error; + this.status=status; + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/NotFoundException.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/NotFoundException.java new file mode 100644 index 0000000..c2ef952 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/NotFoundException.java @@ -0,0 +1,12 @@ +package com.tribune.backend.infrastructure.error; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.NOT_FOUND) +public class NotFoundException extends Exception { + + public NotFoundException(String message) { + super(message); + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/TribuneAdvisor.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/TribuneAdvisor.java new file mode 100644 index 0000000..65ff4b1 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/error/TribuneAdvisor.java @@ -0,0 +1,27 @@ +package com.tribune.backend.infrastructure.error; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.tribune.backend.domain.dto.GenericResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@Slf4j +@RestControllerAdvice +public class TribuneAdvisor extends ResponseEntityExceptionHandler { + + + @ExceptionHandler(BackendException.class) + public ResponseEntity> handleException(BackendException e) { + log.error("status:{} --- error: {}",e.getStatus(),e.getError()); + GenericResponse response=GenericResponse.builder() + .message(e.getMessage()) + .code(e.getStatus().value()) + .data(e.getError()) + .build(); + return ResponseEntity.status(e.getStatus()).body(response); + } + +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/CustomerService.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/CustomerService.java new file mode 100644 index 0000000..4b59935 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/CustomerService.java @@ -0,0 +1,10 @@ +package com.tribune.backend.infrastructure.services; + +import com.tribune.backend.infrastructure.db.entities.CustomerEntity; + +import java.util.UUID; + +public interface CustomerService { + + CustomerEntity getCustomerById(UUID userId); +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/CustomerServiceImpl.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/CustomerServiceImpl.java new file mode 100644 index 0000000..eec91db --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/CustomerServiceImpl.java @@ -0,0 +1,17 @@ +package com.tribune.backend.infrastructure.services; + +import com.tribune.backend.infrastructure.db.entities.CustomerEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Slf4j +@Service +public class CustomerServiceImpl implements CustomerService { + + @Override + public CustomerEntity getCustomerById(UUID userId) { + return null; + } +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/OrderService.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/OrderService.java new file mode 100644 index 0000000..f31dac2 --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/OrderService.java @@ -0,0 +1,16 @@ +package com.tribune.backend.infrastructure.services; + +import com.tribune.backend.domain.dto.*; +import com.tribune.backend.infrastructure.db.entities.OrderEntity; + +public interface OrderService { + + OrderEntity getById(String id); + SingleOrderResponse placeOrder(PlaceOrderRequest placeOrderRequest); + + OrderEntity updateOrder(String id, UpdateOrderRequest updateOrderRequest); + + OrdersResponse getAll(Integer page, Integer size, String sortBy, String direction); + + GenericResponse deleteById(String id, String token); +} diff --git a/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/OrderServiceImpl.java b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/OrderServiceImpl.java new file mode 100644 index 0000000..b05ff1d --- /dev/null +++ b/backend-infrastructure/src/main/java/com/tribune/backend/infrastructure/services/OrderServiceImpl.java @@ -0,0 +1,38 @@ +package com.tribune.backend.infrastructure.services; + +import com.tribune.backend.domain.dto.*; +import com.tribune.backend.infrastructure.db.entities.OrderEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + + +@Slf4j +@Service +public class OrderServiceImpl implements OrderService { + + + @Override + public OrderEntity getById(String id) { + return null; + } + + @Override + public SingleOrderResponse placeOrder(PlaceOrderRequest placeOrderRequest) { + return null; + } + + @Override + public OrderEntity updateOrder(String id, UpdateOrderRequest updateOrderRequest) { + return null; + } + + @Override + public OrdersResponse getAll(Integer page, Integer size, String sortBy, String direction) { + return null; + } + + @Override + public GenericResponse deleteById(String id, String token) { + return null; + } +} diff --git a/backend-infrastructure/src/test/java/com/tribune/backend/domain/DataApplicationTests.java b/backend-infrastructure/src/test/java/com/tribune/backend/domain/DataApplicationTests.java new file mode 100644 index 0000000..59cb268 --- /dev/null +++ b/backend-infrastructure/src/test/java/com/tribune/backend/domain/DataApplicationTests.java @@ -0,0 +1,13 @@ +package com.tribune.backend.domain; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DataApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..94c4e3d --- /dev/null +++ b/pom.xml @@ -0,0 +1,166 @@ + + + 4.0.0 + pom + + + backend-domain + backend-api-definition + backend-infrastructure + backend-application + + + com.tribune + backend + 0.0.1-SNAPSHOT + backend + An example for adapting Domain Driven Development within a Spring Boot Web Application + + UTF-8 + UTF-8 + 11 + 11 + 2.7.0 + 3.2.0 + 1.7 + 18.0.0 + 1.18.12 + + ${project.version} + + + + + + + + + org.projectlombok + lombok + ${lombok.version} + compile + + + ${project.groupId} + backend-application + ${project.version} + + + ${project.groupId} + backend-api-definition + ${project.version} + + + ${project.groupId} + backend-domain + ${project.version} + + + ${project.groupId} + backend-infrastructure + ${project.version} + + + + commons-validator + commons-validator + ${apache.commons.validator.version} + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot-version} + pom + import + + + + org.keycloak + keycloak-spring-boot-starter + ${keycloak-version} + + + org.keycloak + keycloak-admin-client + ${keycloak-version} + + + + + ${project.groupId} + backend-domain + ${project.version} + test-jar + test + + + ${project.groupId} + backend-infrastructure + ${project.version} + test-jar + test + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.9.0 + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + test-jar + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + com.google.cloud.tools + jib-maven-plugin + 3.2.0 + + + com.tribune.backend.BackendApplication + + + gcr.io/distroless/java:11 + + + tribune.jfrog.io/tribune/tribune-blogs:${project.version} + + + + + + +