Skip to content

Commit f3659b2

Browse files
authored
Fix conditions to retrieve a JWT or to allow a result upload (#132)
1 parent eca658c commit f3659b2

File tree

7 files changed

+198
-122
lines changed

7 files changed

+198
-122
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44

55
## [[NEXT]](https://github.com/iExecBlockchainComputing/iexec-result-proxy/releases/tag/vNEXT) 2024
66

7+
### Bug Fixes
8+
9+
- Fix conditions to retrieve a JWT or to allow a result upload. (#132)
10+
711
## [[8.4.0]](https://github.com/iExecBlockchainComputing/iexec-result-proxy/releases/tag/v8.4.0) 2024-02-29
812

913
### Deprecation Notices

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public enum AuthorizationError {
2020
EMPTY_PARAMS_UNAUTHORIZED,
2121
NO_MATCH_ONCHAIN_TYPE,
2222
GET_CHAIN_TASK_FAILED,
23-
TASK_NOT_ACTIVE,
23+
TASK_FINAL_DEADLINE_REACHED,
2424
GET_CHAIN_DEAL_FAILED,
2525
INVALID_SIGNATURE;
2626
}

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

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import com.iexec.common.result.ResultModel;
2020
import com.iexec.commons.poco.chain.ChainDeal;
2121
import com.iexec.commons.poco.chain.ChainTask;
22-
import com.iexec.commons.poco.chain.ChainTaskStatus;
2322
import com.iexec.commons.poco.chain.WorkerpoolAuthorization;
2423
import com.iexec.commons.poco.security.Signature;
2524
import com.iexec.commons.poco.tee.TeeUtils;
@@ -32,6 +31,7 @@
3231
import org.springframework.dao.DataAccessException;
3332
import org.springframework.stereotype.Service;
3433

34+
import java.time.Instant;
3535
import java.util.Optional;
3636

3737
import static com.iexec.resultproxy.authorization.AuthorizationError.*;
@@ -50,8 +50,19 @@ public AuthorizationService(AuthorizationRepository authorizationRepository, Iex
5050

5151
/**
5252
* Checks whether this execution is authorized.
53-
* If not authorized, return the reason.
54-
* Otherwise, returns an empty {@link Optional}.
53+
* <p>
54+
* The following conditions have to be verified:
55+
* <ul>
56+
* <li>The {@code WorkerpoolAuthorization} must not be null and must contain a task ID
57+
* <li>The task must be retrieved from the blockchain network
58+
* <li>The current timestamp must be before the task final deadline
59+
* <li>The deal must be retrieved from the blockchain network
60+
* <li>If the {@code WorkerpoolAuthorization} contains an enclave challenge, the on-chain deal must have a correct tag
61+
* <li>The {@code WorkerpoolAuthorization} has been signed with the private key of the workerpool owner found in the deal
62+
* </ul>
63+
*
64+
* @param workerpoolAuthorization The authorization to check
65+
* @return the reason if unauthorized, an empty {@code Optional} otherwise
5566
*/
5667
public Optional<AuthorizationError> isAuthorizedOnExecutionWithDetailedIssue(WorkerpoolAuthorization workerpoolAuthorization) {
5768
if (workerpoolAuthorization == null || StringUtils.isEmpty(workerpoolAuthorization.getChainTaskId())) {
@@ -60,38 +71,36 @@ public Optional<AuthorizationError> isAuthorizedOnExecutionWithDetailedIssue(Wor
6071
}
6172

6273
final String chainTaskId = workerpoolAuthorization.getChainTaskId();
63-
Optional<ChainTask> optionalChainTask = iexecHubService.getChainTask(chainTaskId);
64-
if (optionalChainTask.isEmpty()) {
74+
final ChainTask chainTask = iexecHubService.getChainTask(chainTaskId).orElse(null);
75+
if (chainTask == null) {
6576
log.error("Could not get chainTask [chainTaskId:{}]", chainTaskId);
6677
return Optional.of(GET_CHAIN_TASK_FAILED);
6778
}
68-
final ChainTask chainTask = optionalChainTask.get();
6979

70-
final ChainTaskStatus taskStatus = chainTask.getStatus();
71-
if (taskStatus != ChainTaskStatus.ACTIVE) {
72-
log.error("Task not active onchain [chainTaskId:{}, status:{}]",
73-
chainTaskId, taskStatus);
74-
return Optional.of(TASK_NOT_ACTIVE);
80+
final long deadline = chainTask.getFinalDeadline();
81+
if (Instant.now().isAfter(Instant.ofEpochMilli(deadline))) {
82+
log.error("Task deadline reached [chainTaskId:{}, deadline:{}]",
83+
chainTaskId, Instant.ofEpochMilli(deadline));
84+
return Optional.of(TASK_FINAL_DEADLINE_REACHED);
7585
}
7686

7787
final String chainDealId = chainTask.getDealid();
78-
Optional<ChainDeal> optionalChainDeal = iexecHubService.getChainDeal(chainDealId);
79-
if (optionalChainDeal.isEmpty()) {
88+
final ChainDeal chainDeal = iexecHubService.getChainDeal(chainDealId).orElse(null);
89+
if (chainDeal == null) {
8090
log.error("isAuthorizedOnExecution failed (getChainDeal failed) [chainTaskId:{}]", chainTaskId);
8191
return Optional.of(GET_CHAIN_DEAL_FAILED);
8292
}
83-
ChainDeal chainDeal = optionalChainDeal.get();
8493

85-
final boolean isTeeTask = !workerpoolAuthorization.getEnclaveChallenge().equals(BytesUtils.EMPTY_HEX_STRING_32);
94+
final boolean isTeeTask = !workerpoolAuthorization.getEnclaveChallenge().equals(BytesUtils.EMPTY_ADDRESS);
8695
final boolean isTeeTaskOnchain = TeeUtils.isTeeTag(chainDeal.getTag());
8796
if (isTeeTask != isTeeTaskOnchain) {
88-
log.error("Could not match onchain task type [isTeeTask:{}, isTeeTaskOnchain:{}, chainTaskId:{}, walletAddress:{}]",
97+
log.error("Could not match on-chain task type [isTeeTask:{}, isTeeTaskOnchain:{}, chainTaskId:{}, walletAddress:{}]",
8998
isTeeTask, isTeeTaskOnchain, chainTaskId, workerpoolAuthorization.getWorkerWallet());
9099
return Optional.of(NO_MATCH_ONCHAIN_TYPE);
91100
}
92101

93-
String workerpoolAddress = chainDeal.getPoolOwner();
94-
boolean isSignedByWorkerpool = isSignedByHimself(workerpoolAuthorization.getHash(),
102+
final String workerpoolAddress = chainDeal.getPoolOwner();
103+
final boolean isSignedByWorkerpool = isSignedByHimself(workerpoolAuthorization.getHash(),
95104
workerpoolAuthorization.getSignature().getValue(), workerpoolAddress);
96105

97106
if (!isSignedByWorkerpool) {

src/main/java/com/iexec/resultproxy/proxy/ProxyService.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ public ProxyService(AuthorizationService authorizationService,
6060
this.ipfsResultService = ipfsResultService;
6161
}
6262

63+
/**
64+
* Checks if result can be uploaded.
65+
* <p>
66+
* The following conditions have to be verified:
67+
* <ul>
68+
* <li>No result should have been uploaded for this task
69+
* <li>Both task and deal data can be retrieved from the blockchain network
70+
* <li>A standard task must be in {@code REVEALING} state and {@link #isResultValid(String, String, byte[])} must return {@literal true}
71+
* <li>A TEE task must be in {@code ACTIVE} state and its enclave signature must be valid and verified against the enclave challenge
72+
* </ul>
73+
* Task status: REVEALING for standard tasks, ACTIVE for TEE tasks with contributeAndFinalize workflow.
74+
*
75+
* @param model Model containing data relevant to the requested result upload
76+
* @param walletAddress Wallet address of the JWT requesting an upload
77+
* @return {@literal true} if result can be contributed, {@literal false} otherwise.
78+
*/
6379
boolean canUploadResult(ResultModel model, String walletAddress) {
6480
final String chainTaskId = model.getChainTaskId();
6581
final byte[] zip = model.getZip();
@@ -74,14 +90,14 @@ boolean canUploadResult(ResultModel model, String walletAddress) {
7490
// TODO [PoCo Boost] on-chain deal id available in result model to avoid fetching task
7591
final ChainTask chainTask = iexecHubService.getChainTask(chainTaskId).orElse(null);
7692
if (chainTask == null) {
77-
log.error("Trying to upload result for TEE but getChainTask failed [chainTaskId:{}, uploader:{}]",
93+
log.error("Trying to upload result but on-chain task retrieval failed [chainTaskId:{}, uploader:{}]",
7894
chainTaskId, walletAddress);
7995
return false;
8096
}
8197

8298
final ChainDeal chainDeal = iexecHubService.getChainDeal(chainTask.getDealid()).orElse(null);
8399
if (chainDeal == null) {
84-
log.error("Trying to upload result for TEE but getChainDeal failed [chainTaskId:{}, uploader:{}]",
100+
log.error("Trying to upload result but on-chain deal retrieval failed [chainTaskId:{}, uploader:{}]",
85101
chainTaskId, walletAddress);
86102
return false;
87103
}
@@ -90,7 +106,7 @@ boolean canUploadResult(ResultModel model, String walletAddress) {
90106

91107
// Standard tasks
92108
if (!isTeeTask) {
93-
return isResultValid(chainTaskId, walletAddress, zip);
109+
return chainTask.getStatus() == ChainTaskStatus.REVEALING && isResultValid(chainTaskId, walletAddress, zip);
94110
}
95111

96112
// TODO remove this case in the future. As we support 2 stack versions, it will be a major after deprecated proxy controller endpoints removal
@@ -100,10 +116,12 @@ boolean canUploadResult(ResultModel model, String walletAddress) {
100116
}
101117

102118
// TEE tasks with ResultModel containing the enclave signature
103-
return authorizationService.checkEnclaveSignature(model, walletAddress);
119+
return chainTask.getStatus() == ChainTaskStatus.ACTIVE && authorizationService.checkEnclaveSignature(model, walletAddress);
104120
}
105121

106122
/**
123+
* Checks a result is valid for a standard task and can safely be uploaded on IPFS.
124+
* <p>
107125
* A result for a standard task is considered as valid if:
108126
* <ul>
109127
* <li>It has an associated on-chain contribution
@@ -116,7 +134,7 @@ boolean canUploadResult(ResultModel model, String walletAddress) {
116134
* @param zip Result as a zip
117135
* @return {@literal true} if the result is valid, {@literal false} otherwise.
118136
*/
119-
boolean isResultValid(String chainTaskId, String walletAddress, byte[] zip) {
137+
private boolean isResultValid(String chainTaskId, String walletAddress, byte[] zip) {
120138
final String resultFolderPath = getResultFolderPath(chainTaskId);
121139
final String resultZipPath = resultFolderPath + ".zip";
122140
final String zipDestinationPath = resultFolderPath + SLASH_IEXEC_OUT;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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;
18+
19+
import com.iexec.commons.poco.chain.ChainDeal;
20+
import com.iexec.commons.poco.chain.ChainTask;
21+
import com.iexec.commons.poco.chain.ChainTaskStatus;
22+
import com.iexec.commons.poco.tee.TeeUtils;
23+
import lombok.AccessLevel;
24+
import lombok.NoArgsConstructor;
25+
26+
import java.time.Instant;
27+
import java.time.temporal.ChronoUnit;
28+
29+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
30+
public class TestUtils {
31+
public static final String CHAIN_DEAL_ID = "0x8012c1515a23e0d7ef07b7de780343df2fd18034ca55eea7202cab814603d288";
32+
public static final String CHAIN_TASK_ID = "0x877210dbec7b8461e396751e311b574d6b6909e3618dd0622f7182eaffdc6901";
33+
34+
public static final String POOL_ADDRESS = "0xc911f9345717ba7c8ec862ce002af3e058df84e4";
35+
public static final String POOL_PRIVATE = "0xe2a973b083fae8043543f15313955aecee9de809a318656c1cfb22d3a6d52de1";
36+
public static final String POOL_WRONG_SIGNATURE = "0xf869daaca2407b7eabd27c3c4c5a3f3565172ca7211ac1d8bfacea2beb511a4029446a07cccc0884"
37+
+ "c2193b269dfb341461db8c680a8898bb53862d6e48340c2e1b";
38+
39+
public static ChainDeal getChainDeal() {
40+
return ChainDeal.builder()
41+
.poolOwner(POOL_ADDRESS)
42+
.tag(TeeUtils.TEE_SCONE_ONLY_TAG)
43+
.build();
44+
}
45+
46+
public static ChainTask getChainTask(ChainTaskStatus status) {
47+
return ChainTask.builder()
48+
.dealid(CHAIN_DEAL_ID)
49+
.finalDeadline(Instant.now().plus(5L, ChronoUnit.SECONDS).toEpochMilli())
50+
.status(status)
51+
.build();
52+
}
53+
}

0 commit comments

Comments
 (0)