Skip to content

Commit

Permalink
feat: implement oAuth for Camunda Webapp (#433)
Browse files Browse the repository at this point in the history
closes #367
  • Loading branch information
DaAnda97 authored Jul 26, 2024
1 parent 7e7d0fc commit 31c00d8
Show file tree
Hide file tree
Showing 43 changed files with 1,506 additions and 242 deletions.
5 changes: 0 additions & 5 deletions connect/camunda7-remote/all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@
<artifactId>element-templates-c7</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.miragon.miranum.connect</groupId>
<artifactId>oauth-camunda7-remote</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
Expand All @@ -19,19 +20,26 @@
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.time.Instant;
import java.util.concurrent.locks.ReentrantLock;

@Configuration
@Profile("!no-security")
public class Camunda7oAuthAutoConfiguration {

@Value("${miranum.security.auth.server.url}")
@Value("${spring.security.oauth2.client.provider.keycloak.token-uri}")
private String authServerUrl;

@Value("${miranum.security.client.clientId}")
@Value("${spring.security.oauth2.client.registration.keycloak.client-id}")
private String clientId;

@Value("${miranum.security.client.clientSecret}")
@Value("${spring.security.oauth2.client.registration.keycloak.client-secret}")
private String clientSecret;

private String cachedToken;
private Instant tokenExpiry;
private final ReentrantLock lock = new ReentrantLock();

@Bean
public ClientRequestInterceptor interceptor() {
return context -> context.addHeader("Authorization", this.getAccessToken());
Expand All @@ -51,17 +59,31 @@ public Response intercept(final Interceptor.Chain chain) throws IOException {
}

public String getAccessToken() {
lock.lock();
try {
if (cachedToken == null || Instant.now().isAfter(tokenExpiry)) {
fetchAndCacheToken();
}
return "Bearer " + cachedToken;
} finally {
lock.unlock();
}
}

private void fetchAndCacheToken() {
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(this.clientId, this.clientSecret);

final MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "client_credentials");
map.add("client_id", this.clientId);
map.add("client_secret", this.clientSecret);

final HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
final ResponseEntity<JsonNode> response = new RestTemplate().postForEntity(this.authServerUrl, request, JsonNode.class);
return "Bearer " + response.getBody().get("access_token").asText();
}

}
JsonNode body = response.getBody();
this.cachedToken = body.get("access_token").asText();
int expiresIn = body.get("expires_in").asInt();
this.tokenExpiry = Instant.now().plusSeconds(expiresIn - 10); // refresh token 10 sec before it actually expires
}
}
14 changes: 7 additions & 7 deletions examples/inquiry-integration-service/inquiry.http
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
### Get Access Token
POST http://keycloak:9090/auth/realms/miranum/protocol/openid-connect/token
POST http://keycloak:9090/auth/realms/miragon/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type = password &
client_secret = s3creT &
client_id = miranum &
client_id = inquiry &
username = [email protected] &
password = test

Expand All @@ -30,8 +30,8 @@ Authorization: Bearer {{ access_token }}
%}


### Get Group Tasks for group1
GET http://localhost:8083/rest/task/group/group1
### Get Group Tasks for sales-department
GET http://localhost:8083/rest/task/group/sales-department
Content-Type: application/json
Authorization: Bearer {{ access_token }}

Expand All @@ -41,13 +41,13 @@ Authorization: Bearer {{ access_token }}
%}


### Claim Task for user alex.admin
### Claim Task for user Alex Admin
POST http://localhost:8083/rest/task/{{group_task_id}}/assign
Content-Type: application/json
Authorization: Bearer {{ access_token }}

{
"assignee": "alex.admin"
"assignee": "alex.admin@example.com"
}


Expand All @@ -63,7 +63,7 @@ Authorization: Bearer {{ access_token }}
}


### Get assigned Tasks for user alex.admin
### Get assigned Tasks for user Alex Admin
GET http://localhost:8083/rest/task/user
Content-Type: application/json
Authorization: Bearer {{ access_token }}
Expand Down
8 changes: 4 additions & 4 deletions examples/inquiry-integration-service/local.env
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
MIRANUM_PROCESS_INTEGRATION_EXAMPLE_PORT=8084
WORKER_BACKOFF_STRATEGIE_DISABLED=true
WORKER_BACKOFF_STRATEGIE_DISABLED=false

#
# SSO Settings / Keycloak
#
SSO_BASE_URL=http://keycloak:9090/auth
SSO_REALM=miranum
SSO_REALM=miragon
SSO_ISSUER_URL=${SSO_BASE_URL}/realms/${SSO_REALM}
SSO_WORKER_CLIENT_ID=miranum-worker
SSO_WORKER_CLIENT_SECRET=s3creT
SSO_INQUIRY_WORKER_CLIENT_ID=inquiry-worker
SSO_INQUIRY_WORKER_CLIENT_SECRET=s3creT
5 changes: 5 additions & 0 deletions examples/inquiry-integration-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@
<artifactId>spring-security-starter</artifactId>
<version>${miranum-platform.version}</version>
</dependency>
<dependency>
<groupId>io.miragon.miranum.connect</groupId>
<artifactId>oauth-camunda7-remote</artifactId>
<version>${miranum-connect.version}</version>
</dependency>
<dependency>
<groupId>io.miragon.miranum.connect</groupId>
<artifactId>connect-camunda7-remote-all</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.miragon.miranum.platform.security;
package io.miragon.miranum.inquiry;

import io.miragon.miranum.platform.security.JwtAuthenticationConverter;
import io.miragon.miranum.platform.security.SpringSecurityProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -14,37 +16,26 @@
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.web.SecurityFilterChain;

import static io.miragon.miranum.platform.security.SecurityConfiguration.SECURITY;

/**
* The central class for configuration of all security aspects.
*/
@Profile("!no-security")
@Configuration
@Profile(SECURITY)
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration {
/**
* Activates security.
*/
public static final String SECURITY = "!no-security";

private final SpringSecurityProperties springSecurityProperties;

@Bean
public SecurityFilterChain configure(final HttpSecurity http, Converter<Jwt, AbstractAuthenticationToken> converter) throws Exception {
http.authorizeHttpRequests(auth ->
auth.requestMatchers(springSecurityProperties.getPermittedUrls())
auth.requestMatchers(springSecurityProperties.getPermittedUrls().toArray(new String[0]))
.permitAll()
.anyRequest()
.authenticated()
)
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt ->
jwt.jwtAuthenticationConverter(converter)
)
)
.authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(converter)))
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
Expand All @@ -58,3 +49,4 @@ public Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter()

}


Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import io.miragon.miranum.inquiry.application.port.in.InquiryReceived;
import io.miragon.miranum.inquiry.application.port.in.model.NewInquiryCommand;
import io.miragon.miranum.inquiry.domain.InquiryId;
import io.miragon.miranum.platform.security.authentication.UserAuthenticationProvider;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ spring:
name: '@project.artifactId@'

miranum:
# override default security settings
security:
auth:
server:
url: ${SSO_BASE_URL}/realms/${SSO_REALM}/protocol/openid-connect/token
server:
base-url: ${SSO_BASE_URL}
realm: ${SSO_REALM}
user-name-attribute: email
client:
enabled: true
clientId: ${SSO_WORKER_CLIENT_ID}
clientSecret: ${SSO_WORKER_CLIENT_SECRET}
clientId: ${SSO_INQUIRY_WORKER_CLIENT_ID}
clientSecret: ${SSO_INQUIRY_WORKER_CLIENT_SECRET}
permittedUrls:
- "/error"
- "/actuator/**"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<bpmn:outgoing>Flow_0mci6qe</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0mci6qe" sourceRef="Event_12or2y7" targetRef="Activity_12rgiry" />
<bpmn:userTask id="Activity_12rgiry" name="Check Resources" camunda:modelerTemplate="io.miranum.basis-usertask">
<bpmn:userTask id="Activity_12rgiry" name="Check Resources" camunda:modelerTemplate="io.miranum.basis-usertask" camunda:candidateGroups="sales-department">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="miranum_task_form">check-resources</camunda:inputParameter>
Expand All @@ -29,7 +29,7 @@
<bpmn:incoming>Flow_066p5i1</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_066p5i1" sourceRef="Activity_15vhg4s" targetRef="Event_1m92ie7" />
<bpmn:userTask id="Activity_0iahoe8" name="Create Offer" camunda:modelerTemplate="io.miranum.basis-usertask">
<bpmn:userTask id="Activity_0iahoe8" name="Create Offer" camunda:modelerTemplate="io.miranum.basis-usertask" camunda:candidateUsers="[email protected]">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="miranum_task_form">create-offer</camunda:inputParameter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ ENGINE_DATASOURCE_USER=${LOCAL_DATASOURCE_USERNAME}
ENGINE_DATASOURCE_PASSWORD=${LOCAL_DATASOURCE_PASSWORD}
ENGINE_DATASOURCE_URL=${LOCAL_JDBC_BASE_URL}${ENGINE_DATASOURCE_DB}
ENGINE_SCHEMA_URL=http://localhost:8081/schema-registry
ENGINE_DATABASE_PLATFORM=io.miragon.miranum.platform.example.shared.configuration.NoToastPostgresSQLDialect
ENGINE_DATABASE_PLATFORM=io.miragon.miranum.platform.example.shared.database.NoToastPostgresSQLDialect
#
# DB Settings for Schema Service
#
Expand All @@ -41,15 +41,20 @@ SCHEMA_DATASOURCE_DRIVERCLASSNAME=${LOCAL_DATASOURCE_DRIVERCLASSNAME}
#
# SSO Settings / Keycloak
#
SSO_REALM=miragon
SSO_BASE_URL=http://keycloak:9090/auth
SSO_REALM=miranum
SSO_ISSUER_URL=${SSO_BASE_URL}/realms/${SSO_REALM}
SSO_ENGINE_CLIENT_ID=miranum
SSO_ENGINE_CLIENT_SECRET=s3creT
SSO_DATASOURCE_USERNAME=${LOCAL_DATASOURCE_USERNAME}
SSO_DATASOURCE_PASSWORD=${LOCAL_DATASOURCE_PASSWORD}
SSO_WORKER_CLIENT_ID=miranum-worker
SSO_WORKER_CLIENT_SECRET=s3creT
SSO_ENGINE_CLIENT_ID=engine
SSO_ENGINE_CLIENT_SECRET=s3creT
SSO_ENGINE_WEBAPPS_REQUIRED_ROLE=admin
SSO_ENGINE_WORKER_REQUIRED_ROLE=worker
SSO_INQUIRY_CLIENT_ID=inquiry
SSO_INQUIRY_CLIENT_SECRET=s3creT
SSO_INQUIRY_WORKER_CLIENT_ID=inquiry-worker
SSO_INQUIRY_WORKER_CLIENT_SECRET=s3creT


#
# Camunda Cockpit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Use this only in dev environments. It's not intended for production usage.
version: '3.9'
services:

keycloak:
Expand All @@ -12,7 +11,7 @@ services:
- '9090:9090'
command: 'start-dev --http-relative-path /auth --http-port=9090'
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9090/auth/realms/miranum/.well-known/openid-configuration" ]
test: [ "CMD", "curl", "-f", "http://keycloak:9090/auth/health" ]
interval: 5s
timeout: 2s
retries: 30
Expand All @@ -21,11 +20,12 @@ services:
environment:
KC_HOSTNAME: keycloak # this hostname must be resolved to 127.0.0.1 locally. Add it to your hosts file.
KC_HOSTNAME_STRICT: 'false'
KC_HEALTH_ENABLED: 'true'
KC_DB: ${LOCAL_KEYCLOAK_DB_VENDOR}
KC_TRANSACTION_XA_ENABLED: 'false'
KC_DB_URL: ${SSO_DATASOURCE_URL}
KC_DB_USERNAME: ${SSO_DATASOURCE_USERNAME}
KC_DB_PASSWORD: ${SSO_DATASOURCE_PASSWORD}
KC_TRANSACTION_XA_ENABLED: 'false'
KEYCLOAK_ADMIN: ${SSO_ADMIN:-admin}
KEYCLOAK_ADMIN_PASSWORD: ${SSO_ADMIN_PASSWORD:-admin}
networks:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
id: initial_realm
author: Miranum
author: Miragon
changes:
- addRealm:
name: ${SSO_REALM}
- addGroup:
realm: ${SSO_REALM}
name: group1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
id: engine
author: Miranum
author: Miragon
realm: ${SSO_REALM}
changes:
- addSimpleClient:
Expand All @@ -11,28 +11,28 @@ changes:
clientId: ${SSO_ENGINE_CLIENT_ID}
webOrigins:
- "*"
- addRole:
clientId: ${SSO_ENGINE_CLIENT_ID}
clientRole: true
name: clientrole_deployer
description: Can deploy, assigns BACKEND_DEPLOY_RESOURCE authority on other stages then local.
- addRole:
- addClientMapper:
clientId: ${SSO_ENGINE_CLIENT_ID}
clientRole: true
name: clientrole_task_importer
description: Can import user task from the engine to Polyflow.
- addRole:
clientId: ${SSO_ENGINE_CLIENT_ID}
clientRole: true
name: admin
description: Role Admin
name: userClientRole
protocolMapper: oidc-usermodel-client-role-mapper
config:
access.token.claim: true
id.token.claim: true
userinfo.token.claim: true
jsonType.label: String
multivalued: true
claim.name: "roles"

# Camunda Webapp users will need this role
- addRole:
clientId: ${SSO_ENGINE_CLIENT_ID}
clientRole: true
name: office
description: Office office
name: ${SSO_ENGINE_WEBAPPS_REQUIRED_ROLE}
description: Administrates the camunda webapps

# Engine Worker (Service Accounts) will need this role
- addRole:
clientId: ${SSO_ENGINE_CLIENT_ID}
clientRole: true
name: group1
description: Group 1
name: ${SSO_ENGINE_WORKER_REQUIRED_ROLE}
description: Allows workers to work on service tasks
Loading

0 comments on commit 31c00d8

Please sign in to comment.