Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement oAuth for Camunda Webapp #433

Merged
merged 11 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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