Skip to content

Commit 0ce3c15

Browse files
antoinemzsguillaumejparis
authored andcommitted
[backend] introduce external ID for injector contract and associated changes (#3334)
Signed-off-by: Antoine MAZEAS <[email protected]>
1 parent b185006 commit 0ce3c15

21 files changed

+1360
-374
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.openbas.migration;
2+
3+
import java.sql.Connection;
4+
import java.sql.Statement;
5+
import org.flywaydb.core.api.migration.BaseJavaMigration;
6+
import org.flywaydb.core.api.migration.Context;
7+
import org.springframework.stereotype.Component;
8+
9+
@Component
10+
public class V4_15__Add_column_injector_contract_external_id extends BaseJavaMigration {
11+
12+
@Override
13+
public void migrate(Context context) throws Exception {
14+
Connection connection = context.getConnection();
15+
Statement stmt = connection.createStatement();
16+
stmt.execute(
17+
"""
18+
ALTER TABLE injectors_contracts
19+
ADD COLUMN injector_contract_external_id VARCHAR UNIQUE;
20+
""");
21+
}
22+
23+
/* ROLLBACK
24+
ALTER TABLE injectors_contracts DROP COLUMN injector_contract_external_id;
25+
*/
26+
}

openbas-api/src/main/java/io/openbas/rest/attack_pattern/service/AttackPatternService.java

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package io.openbas.rest.attack_pattern.service;
22

3+
import static io.openbas.helper.StreamHelper.fromIterable;
4+
35
import com.fasterxml.jackson.databind.JsonNode;
46
import com.fasterxml.jackson.databind.ObjectMapper;
57
import io.openbas.database.model.AttackPattern;
68
import io.openbas.database.repository.AttackPatternRepository;
79
import io.openbas.ee.Ee;
810
import io.openbas.rest.attack_pattern.form.AnalysisResultFromTTPExtractionAIWebserviceOutput;
11+
import io.openbas.rest.exception.ElementNotFoundException;
912
import jakarta.annotation.Resource;
1013
import java.io.IOException;
1114
import java.nio.charset.StandardCharsets;
@@ -113,21 +116,65 @@ private Set<String> extractExternalAttackPatternIdsFromResponse(String responseB
113116
return externalAttackPatternIds;
114117
}
115118

119+
private List<AttackPattern> getAttackPatternsByExternalIds(Set<String> ids) {
120+
if (ids.isEmpty()) {
121+
return Collections.emptyList();
122+
}
123+
return this.attackPatternRepository.findAllByExternalIdInIgnoreCase(new ArrayList<>(ids));
124+
}
125+
126+
private List<AttackPattern> getAttackPatternsByInternalIds(Set<String> ids) {
127+
if (ids.isEmpty()) {
128+
return Collections.emptyList();
129+
}
130+
return fromIterable(this.attackPatternRepository.findAllById(new ArrayList<>(ids)));
131+
}
132+
116133
/**
117134
* Get the attack pattern IDs from the external IDs.
118135
*
119136
* @param externalAttackPatternIds Set of external attack pattern IDs to be converted to internal
120137
* IDs.
121138
* @return List of attack pattern IDs corresponding to the external IDs.
122139
*/
123-
private List<String> getAttackPatternIds(Set<String> externalAttackPatternIds) {
124-
if (!externalAttackPatternIds.isEmpty()) {
125-
List<AttackPattern> attackPatterns =
126-
this.attackPatternRepository.findAllByExternalIdInIgnoreCase(
127-
new ArrayList<>(externalAttackPatternIds));
128-
return attackPatterns.stream().map(AttackPattern::getId).toList();
140+
private List<String> getAttackPatternInternalIdsFromExternalIds(
141+
Set<String> externalAttackPatternIds) {
142+
return this.getAttackPatternsByExternalIds(externalAttackPatternIds).stream()
143+
.map(AttackPattern::getId)
144+
.toList();
145+
}
146+
147+
public List<AttackPattern> getAttackPatternsByExternalIdsThrowIfMissing(
148+
Set<String> externalAttackPatternIds) {
149+
List<AttackPattern> attackPatterns =
150+
this.getAttackPatternsByExternalIds(externalAttackPatternIds);
151+
List<String> missingIds =
152+
externalAttackPatternIds.stream()
153+
.filter(
154+
id ->
155+
!attackPatterns.stream()
156+
.map(AttackPattern::getExternalId)
157+
.toList()
158+
.contains(id))
159+
.toList();
160+
if (!missingIds.isEmpty()) {
161+
throw new ElementNotFoundException(
162+
String.format("Missing attack patterns: %s", String.join(", ", missingIds)));
163+
}
164+
return attackPatterns;
165+
}
166+
167+
public List<AttackPattern> getAttackPatternsByInternalIdsThrowIfMissing(Set<String> ids) {
168+
List<AttackPattern> attackPatterns = this.getAttackPatternsByInternalIds(ids);
169+
List<String> missingIds =
170+
ids.stream()
171+
.filter(id -> !attackPatterns.stream().map(AttackPattern::getId).toList().contains(id))
172+
.toList();
173+
if (!missingIds.isEmpty()) {
174+
throw new ElementNotFoundException(
175+
String.format("Missing attack patterns: %s", String.join(", ", missingIds)));
129176
}
130-
return Collections.emptyList();
177+
return attackPatterns;
131178
}
132179

133180
/**
@@ -159,7 +206,7 @@ public List<String> searchAttackPatternWithTTPAIWebservice(
159206
String responseBody = callTTPExtractionAIWebservice(files, text);
160207
Set<String> attackPatternExternalIds =
161208
extractExternalAttackPatternIdsFromResponse(responseBody);
162-
return getAttackPatternIds(attackPatternExternalIds);
209+
return getAttackPatternInternalIdsFromExternalIds(attackPatternExternalIds);
163210

164211
} catch (IOException e) {
165212
throw new RuntimeException(e);

openbas-api/src/main/java/io/openbas/rest/injector_contract/InjectorContractApi.java

Lines changed: 30 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
11
package io.openbas.rest.injector_contract;
22

33
import static io.openbas.database.model.User.ROLE_ADMIN;
4-
import static io.openbas.helper.DatabaseHelper.updateRelation;
5-
import static io.openbas.helper.StreamHelper.fromIterable;
64
import static io.openbas.utils.ArchitectureFilterUtils.handleArchitectureFilter;
75
import static io.openbas.utils.pagination.PaginationUtils.buildPaginationCriteriaBuilder;
86

7+
import com.fasterxml.jackson.databind.ObjectMapper;
98
import io.openbas.database.model.InjectorContract;
109
import io.openbas.database.raw.RawInjectorsContrats;
11-
import io.openbas.database.repository.AttackPatternRepository;
12-
import io.openbas.database.repository.InjectorContractRepository;
13-
import io.openbas.database.repository.InjectorRepository;
14-
import io.openbas.rest.exception.ElementNotFoundException;
1510
import io.openbas.rest.helper.RestBehavior;
1611
import io.openbas.rest.injector_contract.form.InjectorContractAddInput;
1712
import io.openbas.rest.injector_contract.form.InjectorContractUpdateInput;
1813
import io.openbas.rest.injector_contract.form.InjectorContractUpdateMappingInput;
19-
import io.openbas.rest.injector_contract.output.InjectorContractOutput;
20-
import io.openbas.utils.pagination.SearchPaginationInput;
21-
import jakarta.transaction.Transactional;
14+
import io.openbas.rest.injector_contract.input.InjectorContractSearchPaginationInput;
15+
import io.openbas.rest.injector_contract.output.InjectorContractBaseOutput;
2216
import jakarta.validation.Valid;
23-
import java.time.Instant;
2417
import lombok.RequiredArgsConstructor;
2518
import org.springframework.data.domain.Page;
2619
import org.springframework.security.access.annotation.Secured;
@@ -30,91 +23,64 @@
3023
@RestController
3124
public class InjectorContractApi extends RestBehavior {
3225

33-
private final AttackPatternRepository attackPatternRepository;
26+
private final ObjectMapper mapper;
3427

35-
private final InjectorRepository injectorRepository;
36-
37-
private final InjectorContractRepository injectorContractRepository;
28+
public static final String INJECTOR_CONTRACT_URL = "/api/injector_contracts";
3829

3930
private final InjectorContractService injectorContractService;
4031

41-
@GetMapping("/api/injector_contracts")
32+
@GetMapping(INJECTOR_CONTRACT_URL)
4233
public Iterable<RawInjectorsContrats> injectContracts() {
43-
return injectorContractRepository.getAllRawInjectorsContracts();
34+
return injectorContractService.getAllRawInjectContracts();
4435
}
4536

46-
@PostMapping("/api/injector_contracts/search")
47-
public Page<InjectorContractOutput> injectorContracts(
48-
@RequestBody @Valid final SearchPaginationInput searchPaginationInput) {
49-
return buildPaginationCriteriaBuilder(
50-
this.injectorContractService::injectorContracts,
51-
handleArchitectureFilter(searchPaginationInput),
52-
InjectorContract.class);
37+
@PostMapping(INJECTOR_CONTRACT_URL + "/search")
38+
public Page<? extends InjectorContractBaseOutput> injectorContracts(
39+
@RequestBody @Valid final InjectorContractSearchPaginationInput input) {
40+
if (input.isIncludeFullDetails()) {
41+
return buildPaginationCriteriaBuilder(
42+
this.injectorContractService::getSinglePageFullDetails,
43+
handleArchitectureFilter(input),
44+
InjectorContract.class);
45+
} else {
46+
return buildPaginationCriteriaBuilder(
47+
this.injectorContractService::getSinglePageBaseDetails,
48+
handleArchitectureFilter(input),
49+
InjectorContract.class);
50+
}
5351
}
5452

5553
@Secured(ROLE_ADMIN)
56-
@GetMapping("/api/injector_contracts/{injectorContractId}")
54+
@GetMapping(INJECTOR_CONTRACT_URL + "/{injectorContractId}")
5755
public InjectorContract injectorContract(@PathVariable String injectorContractId) {
58-
return injectorContractRepository
59-
.findById(injectorContractId)
60-
.orElseThrow(ElementNotFoundException::new);
56+
return injectorContractService.getSingleInjectorContract(injectorContractId);
6157
}
6258

6359
@Secured(ROLE_ADMIN)
64-
@PostMapping("/api/injector_contracts")
65-
@Transactional(rollbackOn = Exception.class)
60+
@PostMapping(INJECTOR_CONTRACT_URL)
6661
public InjectorContract createInjectorContract(
6762
@Valid @RequestBody InjectorContractAddInput input) {
68-
InjectorContract injectorContract = new InjectorContract();
69-
injectorContract.setCustom(true);
70-
injectorContract.setUpdateAttributes(input);
71-
if (!input.getAttackPatternsExternalIds().isEmpty()) {
72-
injectorContract.setAttackPatterns(
73-
fromIterable(
74-
attackPatternRepository.findAllByExternalIdInIgnoreCase(
75-
input.getAttackPatternsExternalIds())));
76-
} else if (!input.getAttackPatternsIds().isEmpty()) {
77-
injectorContract.setAttackPatterns(
78-
fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
79-
}
80-
injectorContract.setInjector(
81-
updateRelation(input.getInjectorId(), injectorContract.getInjector(), injectorRepository));
82-
return injectorContractRepository.save(injectorContract);
63+
return injectorContractService.createNewInjectorContract(input);
8364
}
8465

8566
@Secured(ROLE_ADMIN)
86-
@PutMapping("/api/injector_contracts/{injectorContractId}")
67+
@PutMapping(INJECTOR_CONTRACT_URL + "/{injectorContractId}")
8768
public InjectorContract updateInjectorContract(
8869
@PathVariable String injectorContractId,
8970
@Valid @RequestBody InjectorContractUpdateInput input) {
90-
InjectorContract injectorContract =
91-
injectorContractRepository
92-
.findById(injectorContractId)
93-
.orElseThrow(ElementNotFoundException::new);
94-
injectorContract.setUpdateAttributes(input);
95-
injectorContract.setAttackPatterns(
96-
fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
97-
injectorContract.setUpdatedAt(Instant.now());
98-
return injectorContractRepository.save(injectorContract);
71+
return injectorContractService.updateInjectorContract(injectorContractId, input);
9972
}
10073

10174
@Secured(ROLE_ADMIN)
102-
@PutMapping("/api/injector_contracts/{injectorContractId}/mapping")
75+
@PutMapping(INJECTOR_CONTRACT_URL + "/{injectorContractId}/mapping")
10376
public InjectorContract updateInjectorContractMapping(
10477
@PathVariable String injectorContractId,
10578
@Valid @RequestBody InjectorContractUpdateMappingInput input) {
106-
InjectorContract injectorContract =
107-
injectorContractRepository
108-
.findById(injectorContractId)
109-
.orElseThrow(ElementNotFoundException::new);
110-
injectorContract.setAttackPatterns(
111-
fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
112-
injectorContract.setUpdatedAt(Instant.now());
113-
return injectorContractRepository.save(injectorContract);
79+
return injectorContractService.updateAttackPatternMappings(injectorContractId, input);
11480
}
11581

11682
@Secured(ROLE_ADMIN)
117-
@DeleteMapping("/api/injector_contracts/{injectorContractId}")
83+
@DeleteMapping(INJECTOR_CONTRACT_URL + "/{injectorContractId}")
11884
public void deleteInjectorContract(@PathVariable String injectorContractId) {
11985
this.injectorContractService.deleteInjectorContract(injectorContractId);
12086
}

0 commit comments

Comments
 (0)