Skip to content

Commit afb502e

Browse files
authored
Persist WorkerpoolAuthorization instances to MongoDB (#127)
1 parent 80455d4 commit afb502e

File tree

6 files changed

+131
-15
lines changed

6 files changed

+131
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
1515
- Label REST API with `v1` version. (#120)
1616
- Add an endpoint to retrieve a JWT against a valid `WorkerpoolAuthorization`. (#123 #124)
1717
- Verify TEE tasks `enclaveSignature` before accepting IPFS upload. (#125 #126)
18+
- Persist `WorkerpoolAuthorization` instances to MongoDB. (#127)
1819

1920
### Quality
2021

build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ plugins {
1111
ext {
1212
springCloudVersion = '2021.0.8'
1313
jjwtVersion = '0.11.5'
14+
testContainersVersion = '1.19.3'
1415
}
1516

1617
if (!project.hasProperty('gitBranch')) {
@@ -77,6 +78,10 @@ dependencies {
7778
// test
7879
testImplementation 'org.springframework.boot:spring-boot-starter-test'
7980
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
81+
82+
// mongo
83+
testImplementation "org.testcontainers:junit-jupiter:$testContainersVersion"
84+
testImplementation "org.testcontainers:mongodb:$testContainersVersion"
8085
}
8186

8287
dependencyManagement {
@@ -98,6 +103,7 @@ tasks.named("bootJar") {
98103

99104
test {
100105
useJUnitPlatform()
106+
systemProperty "mongo.image", "mongo:4.4.28-focal"
101107
}
102108

103109
tasks.register('itest') {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2020-2024 IEXEC BLOCKCHAIN TECH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.iexec.resultproxy.authorization;
18+
19+
import com.iexec.commons.poco.chain.WorkerpoolAuthorization;
20+
import lombok.Getter;
21+
import lombok.NoArgsConstructor;
22+
import org.springframework.data.annotation.Id;
23+
import org.springframework.data.annotation.Version;
24+
import org.springframework.data.mongodb.core.index.CompoundIndex;
25+
import org.springframework.data.mongodb.core.mapping.Document;
26+
27+
@Document
28+
@CompoundIndex(name = "workerpool_authorization", def = "{'chainTaskId': 1, 'workerWallet': 1}", unique = true)
29+
@Getter
30+
@NoArgsConstructor
31+
public class Authorization {
32+
@Id
33+
private String id;
34+
35+
@Version
36+
private Long version;
37+
38+
private String chainTaskId;
39+
private String workerWallet;
40+
private String enclaveChallenge;
41+
42+
public Authorization(WorkerpoolAuthorization workerpoolAuthorization) {
43+
this.chainTaskId = workerpoolAuthorization.getChainTaskId();
44+
this.workerWallet = workerpoolAuthorization.getWorkerWallet();
45+
this.enclaveChallenge = workerpoolAuthorization.getEnclaveChallenge();
46+
}
47+
48+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2024-2024 IEXEC BLOCKCHAIN TECH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.iexec.resultproxy.authorization;
18+
19+
import org.springframework.data.mongodb.repository.MongoRepository;
20+
21+
import java.util.Optional;
22+
23+
public interface AuthorizationRepository extends MongoRepository<Authorization, String> {
24+
Optional<Authorization> findByChainTaskIdAndWorkerWallet(String chainTaskId, String workerWallet);
25+
}

src/main/java/com/iexec/resultproxy/authorization/AuthorizationService.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.iexec.resultproxy.authorization;
1818

19-
import com.iexec.common.lifecycle.purge.ExpiringTaskMapFactory;
2019
import com.iexec.common.result.ResultModel;
2120
import com.iexec.commons.poco.chain.ChainDeal;
2221
import com.iexec.commons.poco.chain.ChainTask;
@@ -30,9 +29,9 @@
3029
import com.iexec.resultproxy.chain.IexecHubService;
3130
import lombok.extern.slf4j.Slf4j;
3231
import org.apache.commons.lang3.StringUtils;
32+
import org.springframework.dao.DataAccessException;
3333
import org.springframework.stereotype.Service;
3434

35-
import java.util.Map;
3635
import java.util.Optional;
3736

3837
import static com.iexec.resultproxy.authorization.AuthorizationError.*;
@@ -41,12 +40,12 @@
4140
@Service
4241
public class AuthorizationService {
4342

43+
private final AuthorizationRepository authorizationRepository;
4444
private final IexecHubService iexecHubService;
45-
private final Map<String, WorkerpoolAuthorization> workerpoolAuthorizations;
4645

47-
public AuthorizationService(IexecHubService iexecHubService) {
46+
public AuthorizationService(AuthorizationRepository authorizationRepository, IexecHubService iexecHubService) {
47+
this.authorizationRepository = authorizationRepository;
4848
this.iexecHubService = iexecHubService;
49-
this.workerpoolAuthorizations = ExpiringTaskMapFactory.getExpiringTaskMap();
5049
}
5150

5251
/**
@@ -122,11 +121,12 @@ public boolean checkEnclaveSignature(ResultModel model, String walletAddress) {
122121
return false;
123122
}
124123
final String chainTaskId = model.getChainTaskId();
125-
final String wpAuthKey = String.join("-", chainTaskId, walletAddress);
126124
final String resultHash = HashUtils.concatenateAndHash(chainTaskId, model.getDeterministHash());
127125
final String resultSeal = HashUtils.concatenateAndHash(walletAddress, chainTaskId, model.getDeterministHash());
128126
final String messageHash = HashUtils.concatenateAndHash(resultHash, resultSeal);
129-
final WorkerpoolAuthorization workerpoolAuthorization = workerpoolAuthorizations.get(wpAuthKey);
127+
final Authorization workerpoolAuthorization = authorizationRepository
128+
.findByChainTaskIdAndWorkerWallet(chainTaskId, walletAddress)
129+
.orElse(null);
130130
if (workerpoolAuthorization == null) {
131131
log.warn("No workerpool authorization was found [chainTaskId:{}, walletAddress:{}]",
132132
chainTaskId, walletAddress);
@@ -136,7 +136,7 @@ public boolean checkEnclaveSignature(ResultModel model, String walletAddress) {
136136
boolean isSignedByEnclave = isSignedByHimself(messageHash, model.getEnclaveSignature(), enclaveChallenge);
137137
if (isSignedByEnclave) {
138138
log.info("Valid enclave signature received, allowed to push result");
139-
workerpoolAuthorizations.remove(wpAuthKey);
139+
authorizationRepository.deleteById(workerpoolAuthorization.getId());
140140
log.debug("Workerpool authorization entry removed [chainTaskId:{}, workerWallet:{}]",
141141
workerpoolAuthorization.getChainTaskId(), workerpoolAuthorization.getWorkerWallet());
142142
} else {
@@ -146,10 +146,14 @@ public boolean checkEnclaveSignature(ResultModel model, String walletAddress) {
146146
}
147147

148148
public void putIfAbsent(WorkerpoolAuthorization workerpoolAuthorization) {
149-
final String key = String.join("-", workerpoolAuthorization.getChainTaskId(), workerpoolAuthorization.getWorkerWallet());
150-
workerpoolAuthorizations.putIfAbsent(key, workerpoolAuthorization);
151-
log.debug("Workerpool authorization entry added [chainTaskId:{}, workerWallet:{}]",
152-
workerpoolAuthorization.getChainTaskId(), workerpoolAuthorization.getWorkerWallet());
149+
try {
150+
authorizationRepository.save(new Authorization(workerpoolAuthorization));
151+
log.debug("Workerpool authorization entry added [chainTaskId:{}, workerWallet:{}]",
152+
workerpoolAuthorization.getChainTaskId(), workerpoolAuthorization.getWorkerWallet());
153+
} catch (DataAccessException e) {
154+
log.warn("Workerpool authorization entry not added [chainTaskId:{}, workerWallet: {}]",
155+
workerpoolAuthorization.getChainTaskId(), workerpoolAuthorization.getWorkerWallet(), e);
156+
}
153157
}
154158
// endregion
155159

src/test/java/com/iexec/resultproxy/authorization/AuthorizationServiceTests.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,16 @@
2929
import com.iexec.resultproxy.chain.IexecHubService;
3030
import org.junit.jupiter.api.BeforeEach;
3131
import org.junit.jupiter.api.Test;
32-
import org.mockito.InjectMocks;
3332
import org.mockito.Mock;
3433
import org.mockito.MockitoAnnotations;
34+
import org.springframework.beans.factory.annotation.Autowired;
35+
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
36+
import org.springframework.test.context.DynamicPropertyRegistry;
37+
import org.springframework.test.context.DynamicPropertySource;
38+
import org.testcontainers.containers.MongoDBContainer;
39+
import org.testcontainers.junit.jupiter.Container;
40+
import org.testcontainers.junit.jupiter.Testcontainers;
41+
import org.testcontainers.utility.DockerImageName;
3542
import org.web3j.crypto.Credentials;
3643
import org.web3j.crypto.ECKeyPair;
3744
import org.web3j.crypto.Keys;
@@ -50,16 +57,28 @@
5057
import static org.assertj.core.api.Assertions.assertThat;
5158
import static org.mockito.Mockito.when;
5259

60+
@DataMongoTest
61+
@Testcontainers
5362
class AuthorizationServiceTests {
5463

5564
private static final String CHAIN_TASK_ID = "0x0123";
5665
private static final String RESULT_DIGEST = "0x3210";
5766
private static final String WALLET_ADDRESS = "0xabcd";
5867

68+
@Container
69+
private static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse(System.getProperty("mongo.image")));
70+
71+
@DynamicPropertySource
72+
static void registerProperties(DynamicPropertyRegistry registry) {
73+
registry.add("spring.data.mongodb.host", mongoDBContainer::getHost);
74+
registry.add("spring.data.mongodb.port", () -> mongoDBContainer.getMappedPort(27017));
75+
}
76+
77+
@Autowired
78+
private AuthorizationRepository authorizationRepository;
5979
@Mock
60-
IexecHubService iexecHubService;
80+
private IexecHubService iexecHubService;
6181

62-
@InjectMocks
6382
private AuthorizationService authorizationService;
6483

6584
private Credentials enclaveCreds;
@@ -68,6 +87,8 @@ class AuthorizationServiceTests {
6887
void beforeEach() throws GeneralSecurityException {
6988
MockitoAnnotations.openMocks(this);
7089
enclaveCreds = Credentials.create(Keys.createEcKeyPair());
90+
authorizationRepository.deleteAll();
91+
authorizationService = new AuthorizationService(authorizationRepository, iexecHubService);
7192
}
7293

7394
// region isAuthorizedOnExecutionWithDetailedIssue
@@ -249,6 +270,17 @@ void shouldBeSignedByEnclave() {
249270
}
250271
// endregion
251272

273+
// region
274+
@Test
275+
void shouldNotAddAuthorizationTwiceInCollection() {
276+
final WorkerpoolAuthorization authorization = getWorkerpoolAuthorization();
277+
authorizationService.putIfAbsent(authorization);
278+
assertThat(authorizationRepository.count()).isOne();
279+
authorizationService.putIfAbsent(authorization);
280+
assertThat(authorizationRepository.count()).isOne();
281+
}
282+
// endregion
283+
252284
// region utils
253285
ChainDeal getChainDeal() {
254286
return ChainDeal.builder()

0 commit comments

Comments
 (0)