Skip to content
This repository was archived by the owner on Feb 26, 2021. It is now read-only.

Commit 9e403ea

Browse files
authored
Multi tenancy support for the secureCodeBox API (#79)
Multi tenancy support for the secureCodeBox API
2 parents 8394dfe + f7fd5cd commit 9e403ea

File tree

12 files changed

+289
-23
lines changed

12 files changed

+289
-23
lines changed

scb-engine/src/main/java/io/securecodebox/engine/WebMvcConfigurer.java renamed to scb-engine/src/main/java/io/securecodebox/engine/CustomWebMvcConfigurer.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import org.springframework.context.annotation.Configuration;
2323
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
24-
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
24+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
2525

2626
/**
2727
* This class adds additional resources to the spring application.
@@ -31,7 +31,8 @@
3131
* @since 02.03.18
3232
*/
3333
@Configuration
34-
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
34+
public class CustomWebMvcConfigurer implements WebMvcConfigurer {
35+
3536
@Override
3637
public void addResourceHandlers(ResourceHandlerRegistry registry) {
3738
registry.addResourceHandler("/forms/**").addResourceLocations("classpath:/forms/");
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
*
3+
* SecureCodeBox (SCB)
4+
* Copyright 2015-2018 iteratec GmbH
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
* /
18+
*/
19+
package io.securecodebox.engine.auth;
20+
21+
import io.securecodebox.engine.service.AuthService;
22+
import org.camunda.bpm.engine.IdentityService;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
29+
import org.springframework.web.servlet.HandlerInterceptor;
30+
import org.springframework.web.servlet.ModelAndView;
31+
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
32+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
33+
34+
import javax.servlet.http.HttpServletRequest;
35+
import javax.servlet.http.HttpServletResponse;
36+
37+
@Configuration
38+
@EnableWebSecurity
39+
@ConditionalOnProperty(name = "securecodebox.rest.auth", havingValue = "basic auth")
40+
public class CamundaAuthContextSetup implements WebMvcConfigurer {
41+
private static final Logger LOG = LoggerFactory.getLogger(CamundaAuthContextSetup.class);
42+
43+
@Autowired
44+
IdentityService identityService;
45+
46+
@Autowired
47+
private AuthService authService;
48+
49+
@Override
50+
public void addInterceptors(InterceptorRegistry registry) {
51+
registry.addInterceptor(new CamundaAuthContextSetupInterceptor()).addPathPatterns("/box/**");
52+
}
53+
54+
/**
55+
* Sets up the Camunda Authentication Context before
56+
* the Resource gets executed and tears it down afterwards
57+
*/
58+
private class CamundaAuthContextSetupInterceptor implements HandlerInterceptor {
59+
60+
@Override
61+
public boolean preHandle(
62+
HttpServletRequest request,
63+
HttpServletResponse response,
64+
Object handler) {
65+
identityService.setAuthentication(authService.getAuthentication());
66+
67+
return true;
68+
}
69+
70+
@Override
71+
public void postHandle(
72+
HttpServletRequest request,
73+
HttpServletResponse response,
74+
Object handler,
75+
ModelAndView modelAndView) {
76+
identityService.clearAuthentication();
77+
}
78+
}
79+
}

scb-engine/src/main/java/io/securecodebox/engine/helper/DefaultGroupConfiguration.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ public void postProcessEngineBuild(final ProcessEngine processEngine) {
6363
Resources.PROCESS_INSTANCE,
6464
Permissions.READ, Permissions.UPDATE
6565
);
66-
66+
createAuthorizationForGroup(
67+
processEngine.getAuthorizationService(),
68+
GROUP_SCANNER,
69+
Resources.PROCESS_DEFINITION,
70+
Permissions.READ, Permissions.READ_INSTANCE, Permissions.UPDATE_INSTANCE
71+
);
6772

6873
createGroup(identityService, GROUP_CI);
6974
createAuthorizationForGroup(

scb-engine/src/main/java/io/securecodebox/engine/listener/ListenerRegistrarPlugin.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package io.securecodebox.engine.listener;
2121

22+
import io.securecodebox.engine.tenancy.CustomTenantIdProvider;
2223
import org.camunda.bpm.engine.impl.bpmn.parser.BpmnParseListener;
2324
import org.camunda.bpm.engine.impl.cfg.AbstractProcessEnginePlugin;
2425
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
@@ -38,9 +39,14 @@ public class ListenerRegistrarPlugin extends AbstractProcessEnginePlugin {
3839
@Autowired
3940
DefaultListenerRegistrar registrar;
4041

42+
@Autowired
43+
CustomTenantIdProvider tenantIdProvider;
44+
4145
@Override
4246
public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
4347

48+
processEngineConfiguration.setTenantIdProvider(tenantIdProvider);
49+
4450
// get all existing preParseListeners
4551
List<BpmnParseListener> preParseListeners = processEngineConfiguration.getCustomPreBPMNParseListeners();
4652

scb-engine/src/main/java/io/securecodebox/engine/rest/ScanJobResource.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.swagger.annotations.ApiResponse;
3737
import io.swagger.annotations.ApiResponses;
3838
import io.swagger.annotations.Authorization;
39+
import org.camunda.bpm.engine.IdentityService;
3940
import org.camunda.bpm.engine.ProcessEngine;
4041
import org.camunda.bpm.engine.exception.NotFoundException;
4142
import org.camunda.bpm.engine.externaltask.ExternalTask;
@@ -55,9 +56,11 @@
5556

5657
import javax.validation.Valid;
5758
import java.util.HashMap;
59+
import java.util.LinkedList;
5860
import java.util.List;
5961
import java.util.Map;
6062
import java.util.UUID;
63+
import java.util.stream.Collectors;
6164

6265
/**
6366
* API / Endpoint for scan jobs.
@@ -117,15 +120,20 @@ public ResponseEntity<ScanConfiguration> lockJob(
117120
)
118121
@PathVariable UUID scannerId
119122
) {
120-
try{
123+
try {
121124
authService.checkAuthorizedFor(ResourceType.SECURITY_TEST, PermissionType.READ);
122-
}catch (InsufficientAuthenticationException e){
125+
} catch (InsufficientAuthenticationException e) {
123126
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
124127
}
125-
126128
ExternalTaskQueryBuilder externalTaskQueryBuilder = engine.getExternalTaskService()
127129
.fetchAndLock(1, scannerId.toString());
128-
externalTaskQueryBuilder.topic(topic, LOCK_DURATION_MS);
130+
131+
List<String> tenantIds = authService.getAuthentication().getTenantIds();
132+
if(tenantIds.isEmpty()){
133+
externalTaskQueryBuilder.topic(topic, LOCK_DURATION_MS).withoutTenantId();
134+
} else {
135+
externalTaskQueryBuilder.topic(topic, LOCK_DURATION_MS).tenantIdIn(tenantIds.stream().toArray(String[]::new));
136+
}
129137

130138
LockedExternalTask result = Iterables.getFirst(externalTaskQueryBuilder.execute(), null);
131139
if (result != null) {
@@ -167,9 +175,9 @@ public ResponseEntity completeJob(
167175
@PathVariable UUID id,
168176
@Valid @RequestBody ScanResult result
169177
) {
170-
try{
178+
try {
171179
authService.checkAuthorizedFor(id.toString(), ResourceType.SECURITY_TEST, PermissionType.UPDATE);
172-
} catch (InsufficientAuthenticationException e){
180+
} catch (InsufficientAuthenticationException e) {
173181
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
174182
}
175183

@@ -227,9 +235,9 @@ public ResponseEntity failJob(
227235
@PathVariable UUID id,
228236
@Valid @RequestBody ScanFailure result
229237
) {
230-
try{
238+
try {
231239
authService.checkAuthorizedFor(id.toString(), ResourceType.SECURITY_TEST, PermissionType.UPDATE);
232-
}catch (InsufficientAuthenticationException e){
240+
} catch (InsufficientAuthenticationException e) {
233241
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
234242
}
235243

@@ -253,6 +261,7 @@ public ResponseEntity failJob(
253261
engine.getExternalTaskService()
254262
.handleFailure(id.toString(), result.getScannerId().toString(), result.getErrorMessage(),
255263
result.getErrorDetails(), retriesLeft, 1000);
264+
256265
return ResponseEntity.ok().build();
257266
}
258267

scb-engine/src/main/java/io/securecodebox/engine/rest/SecurityTestResource.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,12 @@ public ResponseEntity<List<UUID>> startSecurityTests(
141141

142142
List<UUID> processInstances = new LinkedList<>();
143143

144-
for (SecurityTestConfiguration securityTest : securityTests) {
145-
processInstances.add(securityTestService.startSecurityTest(securityTest));
144+
try {
145+
for (SecurityTestConfiguration securityTest : securityTests) {
146+
processInstances.add(securityTestService.startSecurityTest(securityTest));
147+
}
148+
} catch (InsufficientAuthorizationException e){
149+
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
146150
}
147151

148152
return ResponseEntity.status(HttpStatus.CREATED).body(processInstances);
@@ -209,8 +213,8 @@ public ResponseEntity<SecurityTest> getSecurityTest(
209213
if (securityTest.isFinished()) {
210214
return ResponseEntity.status(HttpStatus.OK).body(securityTest);
211215
}
212-
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).body(securityTest);
213216

217+
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).body(securityTest);
214218
} catch (SecurityTestService.SecurityTestNotFoundException e) {
215219
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
216220
} catch (SecurityTestService.SecurityTestErroredException e) {

scb-engine/src/main/java/io/securecodebox/engine/service/AuthService.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.securecodebox.engine.model.PermissionType;
2323
import io.securecodebox.engine.model.ResourceType;
2424
import org.camunda.bpm.engine.identity.Group;
25+
import org.camunda.bpm.engine.identity.Tenant;
2526
import org.springframework.beans.factory.annotation.Autowired;
2627
import org.springframework.beans.factory.annotation.Value;
2728
import org.springframework.security.core.Authentication;
@@ -39,20 +40,24 @@ public class AuthService {
3940
private static String AUTH_DISABLED_TYPE = "none";
4041
private static final Logger LOG = LoggerFactory.getLogger(AuthService.class);
4142

42-
@Autowired
4343
ProcessEngine engine;
4444

4545
@Value("${securecodebox.rest.auth}")
4646
private String authType;
4747

48+
@Autowired
49+
public AuthService(ProcessEngine engine){
50+
this.engine = engine;
51+
}
52+
4853
public void checkAuthorizedFor(String resourceId, ResourceType resource, PermissionType permission) throws InsufficientAuthorizationException {
49-
if(AUTH_DISABLED_TYPE.equals(authType)){
54+
if (AUTH_DISABLED_TYPE.equals(authType)) {
5055
return;
5156
}
5257

5358
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
5459

55-
if(authentication == null) {
60+
if (authentication == null) {
5661
throw new InsufficientAuthorizationException("No authentication provided.");
5762
}
5863

@@ -66,7 +71,7 @@ public void checkAuthorizedFor(String resourceId, ResourceType resource, Permiss
6671
.collect(Collectors.toList());
6772

6873
boolean isAuthorized = false;
69-
if(resourceId == null){
74+
if (resourceId == null) {
7075
isAuthorized = engine.getAuthorizationService().isUserAuthorized(
7176
authentication.getName(),
7277
groups,
@@ -86,12 +91,41 @@ public void checkAuthorizedFor(String resourceId, ResourceType resource, Permiss
8691
LOG.trace("Current User '{}' with groups: '{}'", authentication.getName(), groups);
8792
LOG.trace("Access check for [{}, {}, {}]: {}", resourceId, resource, permission, isAuthorized);
8893

89-
if(!isAuthorized){
94+
if (!isAuthorized) {
9095
throw new InsufficientAuthorizationException("User is not authorised to perform this action.");
9196
}
9297
}
9398

9499
public void checkAuthorizedFor(ResourceType resource, PermissionType permission) throws InsufficientAuthorizationException {
95100
this.checkAuthorizedFor(null, resource, permission);
96101
}
102+
103+
public org.camunda.bpm.engine.impl.identity.Authentication getAuthentication() {
104+
String userId = SecurityContextHolder.getContext().getAuthentication().getName();
105+
106+
List<String> groups = engine
107+
.getIdentityService()
108+
.createGroupQuery()
109+
.groupMember(userId)
110+
.list()
111+
.stream()
112+
.map(Group::getId)
113+
.collect(Collectors.toList());
114+
115+
List<String> tenants = engine
116+
.getIdentityService()
117+
.createTenantQuery()
118+
.userMember(userId)
119+
.list()
120+
.stream()
121+
.map(Tenant::getId)
122+
.collect(Collectors.toList());
123+
124+
return new org.camunda.bpm.engine.impl.identity.Authentication(
125+
userId,
126+
groups,
127+
tenants
128+
);
129+
130+
}
97131
}

scb-engine/src/main/java/io/securecodebox/engine/service/SecurityTestService.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package io.securecodebox.engine.service;
2020

2121
import io.securecodebox.constants.DefaultFields;
22+
import io.securecodebox.engine.auth.InsufficientAuthorizationException;
2223
import io.securecodebox.model.execution.Target;
2324
import io.securecodebox.model.findings.Finding;
2425
import io.securecodebox.model.rest.Report;
@@ -65,7 +66,7 @@ public void checkSecurityTestDefinitionExistence(String key) throws NonExistentS
6566
}
6667
}
6768

68-
public UUID startSecurityTest(SecurityTestConfiguration securityTest){
69+
public UUID startSecurityTest(SecurityTestConfiguration securityTest) throws InsufficientAuthorizationException {
6970
Map<String, Object> values = new HashMap<>();
7071

7172
List<Target> targets = new LinkedList<>();
@@ -77,6 +78,10 @@ public UUID startSecurityTest(SecurityTestConfiguration securityTest){
7778
values.put(DefaultFields.PROCESS_TARGETS.name(), ProcessVariableHelper.generateObjectValue(targets));
7879
values.put(DefaultFields.PROCESS_META_DATA.name(), securityTest.getMetaData());
7980

81+
if(securityTest.getTenant() != null){
82+
values.put(DefaultFields.PROCESS_TENANT.name(), securityTest.getTenant());
83+
}
84+
8085
ProcessInstance instance = engine.getRuntimeService().startProcessInstanceByKey(securityTest.getProcessDefinitionKey(), values);
8186
return UUID.fromString(instance.getProcessInstanceId());
8287
}
@@ -130,10 +135,15 @@ public SecurityTest getCompletedSecurityTest(UUID id) throws SecurityTestNotFoun
130135

131136
String context = (String) variables.get(DefaultFields.PROCESS_CONTEXT.name()).getValue();
132137
String name = (String) variables.get(DefaultFields.PROCESS_NAME.name()).getValue();
138+
String tenant = null;
139+
if(variables.containsKey(DefaultFields.PROCESS_TENANT.name())){
140+
tenant = (String) variables.get(DefaultFields.PROCESS_TENANT.name()).getValue();
141+
}
142+
133143
List<Target> targets = getListValue(variables, DefaultFields.PROCESS_TARGETS, Target.class);
134144
Map<String, String> metaData = (Map<String, String>) variables.get(DefaultFields.PROCESS_META_DATA.name()).getValue();
135145

136-
return new SecurityTest(id, context, name, targets.get(0), report, metaData);
146+
return new SecurityTest(id, context, name, targets.get(0), report, metaData, tenant);
137147
}
138148

139149
private <T> List<T> getListValue(Map<String, HistoricVariableInstance> variables, DefaultFields name, Class<T> type) {

0 commit comments

Comments
 (0)