diff --git a/.gitignore b/.gitignore
index fa6f9af7..ac541123 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
.gradle
-/build/
+build
!gradle/wrapper/gradle-wrapper.jar
### STS ###
@@ -32,4 +32,3 @@
src/main/resources/iexec-sms-aes.key
src/main/resources/boot/sms-palaemon-conf.yml
-src/main/java/com/iexec/sms/utils/version/Version.java
diff --git a/Jenkinsfile b/Jenkinsfile
index 401e0898..96ce042b 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,44 +1,13 @@
-pipeline {
-
- agent {
- label 'jenkins-agent-machine-1'
- }
-
- stages {
-
- stage('Build') {
- steps {
- sh './gradlew build --refresh-dependencies --no-daemon'
- }
- }
-
- stage('Upload Jars') {
- when {
- anyOf{
- branch 'master'
- branch 'develop'
- }
- }
- steps {
- withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'nexus', usernameVariable: 'NEXUS_USER', passwordVariable: 'NEXUS_PASSWORD']]) {
- sh './gradlew -PnexusUser=$NEXUS_USER -PnexusPassword=$NEXUS_PASSWORD uploadArchives --no-daemon'
- }
- }
- }
-
- stage('Build/Upload Docker image') {
- when {
- anyOf{
- branch 'master'
- branch 'develop'
- }
- }
- steps {
- withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'nexus', usernameVariable: 'NEXUS_USER', passwordVariable: 'NEXUS_PASSWORD']]) {
- sh './gradlew -PnexusUser=$NEXUS_USER -PnexusPassword=$NEXUS_PASSWORD pushImage --no-daemon'
- }
- }
- }
- }
-
-}
+@Library('global-jenkins-library@2.0.1') _
+buildJavaProject(
+ buildInfo: getBuildInfo(),
+ integrationTestsEnvVars: [],
+ shouldPublishJars: true,
+ shouldPublishDockerImages: true,
+ dockerfileDir: 'build/resources/main',
+ dockerfileFilename: 'Dockerfile.untrusted',
+ buildContext: '.',
+ preDevelopVisibility: 'iex.ec',
+ developVisibility: 'iex.ec',
+ preProductionVisibility: 'docker.io',
+ productionVisibility: 'docker.io')
diff --git a/build.gradle b/build.gradle
index 56a4393f..be62b520 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,34 +2,42 @@ import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'java'
- id 'maven'
- id 'jacoco'
- id 'org.springframework.boot' version '2.4.3'
- id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'io.freefair.lombok' version '5.3.0'
+ id 'org.springframework.boot' version '2.6.2'
+ id 'io.spring.dependency-management' version '1.0.11.RELEASE'
+ id 'jacoco'
+ id 'org.sonarqube' version '3.3'
+ id 'maven-publish'
}
-group = 'com.iexec.sms'
-sourceCompatibility = 11
-targetCompatibility = 11
+ext {
+ springCloudVersion = '2021.0.0'
+ openFeignVersion = '11.7'
+ gitBranch = 'git rev-parse --abbrev-ref HEAD'.execute().text.trim()
+}
-repositories {
- mavenCentral()
- jcenter()
- maven {
- url "https://nexus.iex.ec/repository/maven-public/"
+allprojects {
+ group = 'com.iexec.sms'
+ sourceCompatibility = 11
+ targetCompatibility = 11
+ if (gitBranch != 'main' && gitBranch != 'master' && !(gitBranch ==~ '(release|hotfix|support)/.*')) {
+ version += '-NEXT-SNAPSHOT'
}
- maven {
- url "https://jitpack.io"
+ repositories {
+ mavenLocal()
+ mavenCentral()
+ maven {
+ url "https://nexus.intra.iex.ec/repository/maven-public/"
+ }
+ maven {
+ url "https://jitpack.io"
+ }
}
}
configurations {
- deployerJars
-}
-
-ext {
- springCloudVersion = '2020.0.1'
+ integrationTestImplementation.extendsFrom testImplementation
+ integrationTestRuntimeOnly.extendsFrom runtimeOnly
}
dependencyManagement {
@@ -41,7 +49,7 @@ dependencyManagement {
dependencies {
// iexec
implementation "com.iexec.common:iexec-common:$iexecCommonVersion"
- //implementation files("../iexec-common/build/libs/iexec-common-${iexecCommonVersion}.jar")
+ implementation project(':iexec-sms-library')
// spring
implementation "org.springframework.boot:spring-boot-starter-web"
@@ -51,11 +59,10 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
// H2
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
- runtime 'com.h2database:h2:1.4.200'
+ runtimeOnly 'com.h2database:h2:1.4.200'
- // swagger
- implementation "io.springfox:springfox-swagger2:2.9.2"
- implementation "io.springfox:springfox-swagger-ui:2.9.2"
+ // Spring Doc
+ implementation 'org.springdoc:springdoc-openapi-ui:1.6.3'
//ssl
implementation 'org.apache.httpcomponents:httpclient:4.5.9'
@@ -78,84 +85,81 @@ dependencies {
}
testImplementation 'org.springframework.security:spring-security-test'
// testImplementation 'org.mockito:mockito-inline:2.13.0' // activates mocking final classes/methods
+
+ // test containers
+ testImplementation 'org.testcontainers:junit-jupiter:1.16.0'
+ testImplementation 'org.testcontainers:testcontainers:1.16.0'
+ testImplementation 'org.testcontainers:mongodb:1.16.0'
+}
+
+sourceSets {
+ integrationTest {
+ java {
+ compileClasspath += sourceSets.main.output
+ runtimeClasspath += sourceSets.main.output
+ srcDir 'src/itest/java'
+ }
+ resources.srcDir 'src/itest/resources'
+ }
+}
+
+springBoot {
+ buildInfo()
}
test {
useJUnitPlatform()
}
-jacoco {
- toolVersion = "0.8.3"
-}
-build.dependsOn jacocoTestReport
-
-def gitBranch = 'git name-rev --name-only HEAD'.execute().text.trim()
-def isMasterBranch = gitBranch == "master"
-def isDevelopBranch = gitBranch == "develop"
-def canUploadArchives = (isMasterBranch || isDevelopBranch ) && project.hasProperty("nexusUser") && project.hasProperty("nexusPassword")
-def gitShortCommit = 'git rev-parse --short HEAD'.execute().text.trim()
-def isSnapshotVersion = project.version.contains("SNAPSHOT")
-
-project.ext.getNexusMaven = {
- def nexusMavenBase = "https://nexus.iex.ec/repository"
- if (isSnapshotVersion) {
- return nexusMavenBase + "/maven-snapshots/"
- } else {
- return nexusMavenBase + "/maven-releases/"
- }
+task itest(type:Test) {
+ group 'Verification'
+ description 'Runs the integration tests.'
+ testClassesDirs = sourceSets.integrationTest.output.classesDirs
+ classpath = sourceSets.integrationTest.runtimeClasspath
+ outputs.upToDateWhen { false } // run always
+ useJUnitPlatform()
}
-uploadArchives {
- repositories.mavenDeployer {
- configuration = configurations.deployerJars
- repository(url: getNexusMaven()) {
- authentication(userName: project.nexusUser, password: project.nexusPassword)
- }
+jacoco {
+ toolVersion = "0.8.7"
+}
+// sonarqube code coverage requires jacoco XML report
+jacocoTestReport {
+ reports {
+ xml.enabled true
}
}
-uploadArchives.enabled = canUploadArchives
+tasks.test.finalizedBy tasks.jacocoTestReport
+tasks.sonarqube.dependsOn tasks.jacocoTestReport
-// create the version controller for the core
-task createVersion(type: Copy) {
- // delete old one
- delete 'src/main/java/com/iexec/sms/utils/version/Version.java'
- // use and copy template to the new location
- from 'src/main/resources/Version.java.template'
- into 'src/main/java/com/iexec/sms/utils/version/'
-
- rename { String fileName ->
- fileName.replace('.template', '')
+publishing {
+ publications {
+ maven(MavenPublication) {
+ artifact bootJar
+ from components.java
+ }
}
- // replace tokens in the template file
- filter(ReplaceTokens, tokens: [projectversion: "${version}".toString()])
-}
-// the createVersion task should be called before compileJava or the version service will not work
-compileJava.dependsOn createVersion
-
-def imageName = "nexus.iex.ec/iexec-sms"
-def trustedDockerfileName = "Dockerfile"
-def untrustedDockerfileName = "Dockerfile.untrusted"
-def jarName = "iexec-sms-${version}.jar"
-
-project.ext.getDockerImageNameFull = {
- def imageNameWithVersion = imageName + ":${version}"
- if (isSnapshotVersion) {
- return imageNameWithVersion + "-" + gitShortCommit
- } else {
- return imageNameWithVersion
+ repositories {
+ maven {
+ credentials {
+ username project.hasProperty('nexusUser') ? nexusUser : ''
+ password project.hasProperty('nexusPassword') ? nexusPassword : ''
+ }
+ url project.hasProperty('nexusUrl') ? nexusUrl : ''
+ }
}
}
-project.ext.getDockerImageNameShortCommit = {
- return imageName + ":" + gitShortCommit
-}
+ext.jarPathForOCI = relativePath(tasks.bootJar.outputs.files.singleFile)
+ext.gitShortCommit = 'git rev-parse --short=8 HEAD'.execute().text.trim()
+ext.ociImageName = 'local/' + ['bash', '-c', 'basename $(git config --get remote.origin.url) .git'].execute().text.trim()
task buildImage(type: Exec) {
- description 'Building iexec-sms Docker image'
- commandLine("sh", "-c",
- "docker image build -f build/resources/main/${untrustedDockerfileName}" +
- " -t ${getDockerImageNameFull()} --build-arg JAR_NAME=${jarName} . &&" +
- "docker tag ${getDockerImageNameFull()} ${imageName}:dev")
+ group 'Build'
+ description 'Builds an OCI image from a Dockerfile.'
+ dependsOn bootJar
+ commandLine ("sh", "-c", "docker build -f build/resources/main/Dockerfile.untrusted --build-arg jar=$jarPathForOCI"
+ + " -t $ociImageName:$gitShortCommit . && docker tag $ociImageName:$gitShortCommit $ociImageName:dev")
standardOutput = new ByteArrayOutputStream()
ext.output = {
@@ -165,12 +169,11 @@ task buildImage(type: Exec) {
}
task buildTrustedImage(type: Exec) {
- def trustedImageName = getDockerImageNameFull() + "-trusted"
- description 'Building iexec-sms Docker image'
- commandLine("sh", "-c",
- "docker image build -f build/resources/main/${trustedDockerfileName} " +
- "-t ${trustedImageName} --build-arg JAR_NAME=${jarName} --no-cache . && " +
- "docker tag ${trustedImageName} ${imageName}:dev-trusted")
+ group 'Build'
+ description 'Builds a trusted OCI image from a trusted Dockerfile.'
+ dependsOn bootJar
+ commandLine ("sh", "-c", "docker image build -f build/resources/main/Dockerfile --build-arg jar=$jarPathForOCI"
+ + " -t $ociImageName:$gitShortCommit-trusted --no-cache . && docker tag $ociImageName:$gitShortCommit-trusted $ociImageName:dev-trusted")
}
task templatePalaemon {
@@ -219,17 +222,3 @@ task templatePalaemon {
}
//templatePalaemon.dependsOn buildImage
//buildImage.finalizedBy templatePalaemon
-
-task pushImage(type: Exec) {
- if (project.hasProperty("nexusUser") && project.hasProperty("nexusPassword")) {
- commandLine("sh", "-c", "docker login -u " + project.nexusUser + " -p " + project.nexusPassword + " nexus.iex.ec && " +
- "docker push " + getDockerImageNameFull() + " && " +
- "docker tag " + getDockerImageNameFull() + " " + getDockerImageNameShortCommit() + " && " +
- "docker push " + getDockerImageNameShortCommit() + " && " +
- "docker logout")
- } else {
- println "Credentials for DockerHub are missing, the images cannot be pushed"
- }
-}
-pushImage.dependsOn buildImage
-pushImage.enabled = canUploadArchives
diff --git a/gradle.properties b/gradle.properties
index d5cfd117..3a9e90d6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-version=7.0.0
-iexecCommonVersion=5.9.0
+version=7.1.0
+iexecCommonVersion=6.0.0
nexusUser=
-nexusPassword=
\ No newline at end of file
+nexusPassword=
diff --git a/iexec-sms-library/build.gradle b/iexec-sms-library/build.gradle
new file mode 100644
index 00000000..99fbdcd9
--- /dev/null
+++ b/iexec-sms-library/build.gradle
@@ -0,0 +1,47 @@
+plugins {
+ id 'java-library'
+ id 'jacoco'
+ id 'maven-publish'
+}
+
+dependencies {
+ implementation "com.iexec.common:iexec-common:$iexecCommonVersion"
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
+}
+
+java {
+ withJavadocJar()
+ withSourcesJar()
+}
+
+test {
+ useJUnitPlatform()
+}
+
+jacoco {
+ toolVersion = "0.8.7"
+}
+// sonarqube code coverage requires jacoco XML report
+jacocoTestReport {
+ reports {
+ xml.enabled true
+ }
+}
+tasks.test.finalizedBy tasks.jacocoTestReport
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ from components.java
+ }
+ }
+ repositories {
+ maven {
+ credentials {
+ username project.hasProperty("nexusUser")? project.nexusUser: ''
+ password project.hasProperty("nexusPassword")? project.nexusPassword: ''
+ }
+ url = project.hasProperty("nexusUrl")? project.nexusUrl: ''
+ }
+ }
+}
diff --git a/iexec-sms-library/src/main/java/com/iexec/sms/api/SmsClient.java b/iexec-sms-library/src/main/java/com/iexec/sms/api/SmsClient.java
new file mode 100644
index 00000000..d4c8f147
--- /dev/null
+++ b/iexec-sms-library/src/main/java/com/iexec/sms/api/SmsClient.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2022 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.api;
+
+import com.iexec.common.chain.WorkerpoolAuthorization;
+import com.iexec.common.sms.secret.SmsSecretResponse;
+import com.iexec.common.tee.TeeWorkflowSharedConfiguration;
+import com.iexec.common.web.ApiResponseBody;
+import feign.Headers;
+import feign.Param;
+import feign.RequestLine;
+
+import java.util.List;
+
+/**
+ * Interface allowing to instantiate a Feign client targeting SMS REST endpoints.
+ *
+ * To create the client, see the related builder.
+ * @see SmsClientBuilder
+ */
+public interface SmsClient {
+
+ @RequestLine("POST /apps/{appAddress}/secrets/1")
+ @Headers("Authorization: {authorization}")
+ ApiResponseBody> addAppDeveloperAppComputeSecret(
+ @Param("authorization") String authorization,
+ @Param("appAddress") String appAddress,
+ //@Param("secretIndex") String secretIndex,
+ String secretValue
+ );
+
+ @RequestLine("HEAD /apps/{appAddress}/secrets/{secretIndex}")
+ ApiResponseBody> isAppDeveloperAppComputeSecretPresent(
+ @Param("appAddress") String appAddress,
+ @Param("secretIndex") String secretIndex
+ );
+
+ @RequestLine("GET /cas/url")
+ String getSconeCasUrl();
+
+ @RequestLine("POST /requesters/{requesterAddress}/secrets/{secretKey}")
+ @Headers("Authorization: {authorization}")
+ ApiResponseBody> addRequesterAppComputeSecret(
+ @Param("authorization") String authorization,
+ @Param("requesterAddress") String requesterAddress,
+ @Param("secretKey") String secretKey,
+ String secretValue
+ );
+
+ @RequestLine("HEAD /requesters/{requesterAddress}/secrets/{secretKey}")
+ ApiResponseBody> isRequesterAppComputeSecretPresent(
+ @Param("requesterAddress") String requesterAddress,
+ @Param("secretKey") String secretKey
+ );
+
+ @RequestLine("POST /secrets/web2?ownerAddress={ownerAddress}&secretName={secretName}")
+ @Headers("Authorization: {authorization}")
+ String setWeb2Secret(
+ @Param("authorization") String authorization,
+ @Param("ownerAddress") String ownerAddress,
+ @Param("secretName") String secretName,
+ String secretValue
+ );
+
+ @RequestLine("POST /secrets/web3?secretAddress={secretAddress}")
+ @Headers("Authorization: {authorization}")
+ String setWeb3Secret(
+ @Param("authorization") String authorization,
+ @Param("secretAddress") String secretAddress,
+ String secretValue
+ );
+
+ @RequestLine("POST /tee/challenges/{chainTaskId}")
+ String generateTeeChallenge(@Param("chainTaskId") String chainTaskId);
+
+ @RequestLine("POST /tee/sessions")
+ @Headers("Authorization: {authorization}")
+ ApiResponseBody generateTeeSession(
+ @Param("authorization") String authorization,
+ WorkerpoolAuthorization workerpoolAuthorization
+ );
+
+ @RequestLine("GET /tee/workflow/config")
+ TeeWorkflowSharedConfiguration getTeeWorkflowConfiguration();
+
+ @RequestLine("POST /untee/secrets")
+ @Headers("Authorization: {authorization}")
+ SmsSecretResponse getUnTeeSecrets(
+ @Param("authorization") String authorization,
+ WorkerpoolAuthorization workerpoolAuthorization
+ );
+
+}
diff --git a/iexec-sms-library/src/main/java/com/iexec/sms/api/SmsClientBuilder.java b/iexec-sms-library/src/main/java/com/iexec/sms/api/SmsClientBuilder.java
new file mode 100644
index 00000000..304bb737
--- /dev/null
+++ b/iexec-sms-library/src/main/java/com/iexec/sms/api/SmsClientBuilder.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.api;
+
+import com.iexec.common.utils.FeignBuilder;
+import feign.Logger;
+
+/**
+ * Creates Feign client instances to query REST endpoints described in {@link SmsClient}.
+ * @see FeignBuilder
+ */
+public class SmsClientBuilder {
+
+ private SmsClientBuilder() {}
+
+ /**
+ * Create an unauthenticated feign client to query apis described in {@link SmsClient}.
+ * @param logLevel Feign logging level to configure.
+ * @param url Url targeted by the client.
+ * @return Feign client for {@link SmsClient} apis.
+ */
+ public static SmsClient getInstance(Logger.Level logLevel, String url) {
+ return FeignBuilder.createBuilder(logLevel)
+ .target(SmsClient.class, url);
+ }
+
+}
diff --git a/iexec-sms-library/src/main/java/com/iexec/sms/api/TeeSessionGenerationError.java b/iexec-sms-library/src/main/java/com/iexec/sms/api/TeeSessionGenerationError.java
new file mode 100644
index 00000000..77f001c1
--- /dev/null
+++ b/iexec-sms-library/src/main/java/com/iexec/sms/api/TeeSessionGenerationError.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2022 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.api;
+
+public enum TeeSessionGenerationError {
+ // region Authorization
+ INVALID_AUTHORIZATION,
+ EXECUTION_NOT_AUTHORIZED_EMPTY_PARAMS_UNAUTHORIZED,
+ EXECUTION_NOT_AUTHORIZED_NO_MATCH_ONCHAIN_TYPE,
+ EXECUTION_NOT_AUTHORIZED_GET_CHAIN_TASK_FAILED,
+ EXECUTION_NOT_AUTHORIZED_TASK_NOT_ACTIVE,
+ EXECUTION_NOT_AUTHORIZED_GET_CHAIN_DEAL_FAILED,
+ EXECUTION_NOT_AUTHORIZED_INVALID_SIGNATURE,
+ // endregion
+
+ // region Pre-compute
+ PRE_COMPUTE_GET_DATASET_SECRET_FAILED,
+ // endregion
+
+ // region App-compute
+ APP_COMPUTE_NO_ENCLAVE_CONFIG,
+ APP_COMPUTE_INVALID_ENCLAVE_CONFIG,
+ // endregion
+
+ // region Post-compute
+ POST_COMPUTE_GET_ENCRYPTION_TOKENS_FAILED_EMPTY_BENEFICIARY_KEY,
+ POST_COMPUTE_GET_STORAGE_TOKENS_FAILED,
+
+ POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_WORKER_ADDRESS,
+ POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_PUBLIC_ENCLAVE_CHALLENGE,
+ POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_TEE_CHALLENGE,
+ POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_TEE_CREDENTIALS,
+ // endregion
+
+ // region Secure session generation
+ SECURE_SESSION_CAS_CALL_FAILED,
+ SECURE_SESSION_GENERATION_FAILED,
+ // endregion
+
+ // region Miscellaneous
+ GET_TASK_DESCRIPTION_FAILED,
+ NO_SESSION_REQUEST,
+ NO_TASK_DESCRIPTION,
+ GET_SESSION_YML_FAILED,
+
+ UNKNOWN_ISSUE
+ // endregion
+}
diff --git a/iexec-sms-library/src/test/java/com/iexec/sms/api/SmsClientTest.java b/iexec-sms-library/src/test/java/com/iexec/sms/api/SmsClientTest.java
new file mode 100644
index 00000000..3cd6a2e2
--- /dev/null
+++ b/iexec-sms-library/src/test/java/com/iexec/sms/api/SmsClientTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.api;
+
+import feign.Logger;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class SmsClientTest {
+
+ @Test
+ void instantiationTest() {
+ Assertions.assertNotNull(SmsClientBuilder.getInstance(Logger.Level.FULL, "localhost"));
+ }
+
+}
diff --git a/settings.gradle b/settings.gradle
index a8c81501..26918f1d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,2 @@
rootProject.name = 'iexec-sms'
+include 'iexec-sms-library'
diff --git a/src/itest/java/com/iexec/sms/CommonTestSetup.java b/src/itest/java/com/iexec/sms/CommonTestSetup.java
new file mode 100644
index 00000000..ea333f8c
--- /dev/null
+++ b/src/itest/java/com/iexec/sms/CommonTestSetup.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms;
+
+import com.iexec.sms.blockchain.IexecHubService;
+import com.iexec.sms.blockchain.Web3jService;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@TestPropertySource(properties = {"spring.config.location = classpath:/application.yml, classpath:/application-test.yml"})
+public abstract class CommonTestSetup {
+
+ @LocalServerPort
+ protected int randomServerPort;
+
+ // region Following beans are mocked as they use the blockchain
+ @MockBean
+ protected IexecHubService iexecHubService;
+
+ @MockBean
+ protected Web3jService web3jService;
+ // endregion
+}
diff --git a/src/itest/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretIntegrationTests.java b/src/itest/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretIntegrationTests.java
new file mode 100644
index 00000000..92a806c6
--- /dev/null
+++ b/src/itest/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretIntegrationTests.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2021 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.secret.compute;
+
+import com.iexec.common.contract.generated.Ownable;
+import com.iexec.common.utils.HashUtils;
+import com.iexec.sms.CommonTestSetup;
+import com.iexec.sms.api.SmsClient;
+import com.iexec.sms.api.SmsClientBuilder;
+import com.iexec.sms.encryption.EncryptionService;
+import feign.FeignException;
+import feign.Logger;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.ExampleMatcher;
+import org.springframework.http.HttpStatus;
+import org.web3j.crypto.Hash;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+import static com.iexec.common.utils.SignatureUtils.signMessageHashAndGetSignature;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@Slf4j
+public class TeeTaskComputeSecretIntegrationTests extends CommonTestSetup {
+ private static final String APP_ADDRESS = "0xabcd1339ec7e762e639f4887e2bfe5ee8023e23e";
+ private static final String UPPER_CASE_APP_ADDRESS = "0xABCD1339EC7E762E639F4887E2BFE5EE8023E23E";
+ private static final String SECRET_VALUE = generateRandomAscii(4096);
+ private static final String OWNER_ADDRESS = "0xabcd1339ec7e762e639f4887e2bfe5ee8023e23e";
+ private static final String REQUESTER_ADDRESS = "0x123790ae4E14865B972ee04a5f9FD5fB153Cd5e7";
+ private static final String DOMAIN = "IEXEC_SMS_DOMAIN";
+ private static final String APP_DEVELOPER_PRIVATE_KEY = "0x2fac4d263f1b20bfc33ea2bcb1cbe1521322dbde81d04b0c454ffff1218f0ed6";
+ private static final String REQUESTER_PRIVATE_KEY = "0xb8e97e9e217a50dedbe3c0c4c37b85a85a10d4eb23fca6dbad55162cfbb1c450";
+
+ private SmsClient apiClient;
+
+ @Autowired
+ private EncryptionService encryptionService;
+
+ @Autowired
+ private TeeTaskComputeSecretRepository repository;
+
+ /*
+ * Generate random ASCII from seed for re-testability.
+ * See also {@link org.apache.commons.lang3.RandomStringUtils#randomAscii(int)}
+ * */
+ private static String generateRandomAscii(int count) {
+ long seed = new Date().getTime();
+ log.info("Generating random ascii from seed: {}", seed);
+ return RandomStringUtils.random(count,
+ 32,
+ 127,
+ false,
+ false,
+ null,
+ new Random(seed));
+ }
+
+ @BeforeEach
+ private void setUp() {
+ apiClient = SmsClientBuilder.getInstance(Logger.Level.FULL, "http://localhost:" + randomServerPort);
+ final Ownable appContract = mock(Ownable.class);
+ when(appContract.getContractAddress()).thenReturn(APP_ADDRESS);
+ when(iexecHubService.getOwnableContract(APP_ADDRESS))
+ .thenReturn(appContract);
+ repository.deleteAll();
+ }
+
+ @Test
+ void shouldAddNewComputeSecrets() {
+ final String appDeveloperSecretIndex = "1";
+ final String requesterSecretKey = "secret-key";
+ final String requesterAddress = REQUESTER_ADDRESS;
+ final String appAddress = APP_ADDRESS;
+ final String secretValue = SECRET_VALUE;
+ final String ownerAddress = OWNER_ADDRESS;
+
+ addNewAppDeveloperSecret(appAddress, appDeveloperSecretIndex, secretValue, ownerAddress);
+ addNewRequesterSecret(requesterAddress, requesterSecretKey, secretValue);
+
+ // Check the new secrets exists for the API
+ try {
+ apiClient.isAppDeveloperAppComputeSecretPresent(appAddress, appDeveloperSecretIndex);
+ } catch (FeignException e) {
+ Assertions.assertThat(e.status()).isEqualTo(HttpStatus.NO_CONTENT.value());
+ }
+
+ try {
+ apiClient.isRequesterAppComputeSecretPresent(requesterAddress, requesterSecretKey);
+ } catch (FeignException e) {
+ Assertions.assertThat(e.status()).isEqualTo(HttpStatus.NO_CONTENT.value());
+ }
+
+ // We check the secrets have been added to the database
+ final ExampleMatcher exampleMatcher = ExampleMatcher.matching()
+ .withIgnorePaths("value");
+ final Optional appDeveloperSecret = repository.findOne(
+ Example.of(TeeTaskComputeSecret
+ .builder()
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .onChainObjectAddress(appAddress)
+ .secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ .key(appDeveloperSecretIndex)
+ .build(),
+ exampleMatcher
+ )
+ );
+ if (appDeveloperSecret.isEmpty()) {
+ // Could be something like `Assertions.assertThat(appDeveloperSecret).isPresent()`
+ // but Sonar needs a call to `appDeveloperSecret.isEmpty()` to avoid triggering a warning.
+ Assertions.fail("An app developer secret was expected but none has been retrieved.");
+ return;
+ }
+ Assertions.assertThat(appDeveloperSecret.get().getId()).isNotBlank();
+ Assertions.assertThat(appDeveloperSecret.get().getOnChainObjectAddress()).isEqualToIgnoringCase(appAddress);
+ Assertions.assertThat(appDeveloperSecret.get().getKey()).isEqualTo(appDeveloperSecretIndex);
+ Assertions.assertThat(appDeveloperSecret.get().getValue()).isNotEqualTo(secretValue);
+ Assertions.assertThat(appDeveloperSecret.get().getValue()).isEqualTo(encryptionService.encrypt(secretValue));
+
+ final Optional requesterSecret = repository.findOne(
+ Example.of(TeeTaskComputeSecret
+ .builder()
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .onChainObjectAddress("")
+ .secretOwnerRole(SecretOwnerRole.REQUESTER)
+ .fixedSecretOwner(requesterAddress.toLowerCase())
+ .key(requesterSecretKey)
+ .build(),
+ exampleMatcher)
+ );
+ if (requesterSecret.isEmpty()) {
+ // Could be something like `Assertions.assertThat(requesterSecret).isPresent()`
+ // but Sonar needs a call to `requesterSecret.isEmpty()` to avoid triggering a warning.
+ Assertions.fail("An app requester secret was expected but none has been retrieved.");
+ return;
+ }
+ Assertions.assertThat(requesterSecret.get().getId()).isNotBlank();
+ Assertions.assertThat(requesterSecret.get().getOnChainObjectAddress()).isEqualToIgnoringCase("");
+ Assertions.assertThat(requesterSecret.get().getKey()).isEqualTo(requesterSecretKey);
+ Assertions.assertThat(requesterSecret.get().getValue()).isNotEqualTo(secretValue);
+ Assertions.assertThat(requesterSecret.get().getValue()).isEqualTo(encryptionService.encrypt(secretValue));
+
+ // We shouldn't be able to add a new secrets to the database with the same IDs
+ try {
+ final String authorization = getAuthorizationForAppDeveloper(appAddress, appDeveloperSecretIndex, secretValue);
+ apiClient.addAppDeveloperAppComputeSecret(authorization, appAddress, secretValue);
+ Assertions.fail("A second app developer secret with the same app address and index should be rejected.");
+ } catch (FeignException.Conflict ignored) {
+ // Having a Conflict exception is what we expect there.
+ }
+ try {
+ final String authorization = getAuthorizationForRequester(requesterAddress, requesterSecretKey, secretValue);
+ apiClient.addRequesterAppComputeSecret(authorization, requesterAddress, requesterSecretKey, secretValue);
+ Assertions.fail("A second app requester secret with the same app address and index should be rejected.");
+ } catch (FeignException.Conflict ignored) {
+ // Having a Conflict exception is what we expect there.
+ }
+
+ // We shouldn't be able to add a new secret to the database with the same index
+ // and an appAddress whose only difference is the case.
+ try {
+ when(iexecHubService.getOwner(UPPER_CASE_APP_ADDRESS)).thenReturn(ownerAddress);
+
+ final String authorization = getAuthorizationForAppDeveloper(UPPER_CASE_APP_ADDRESS, appDeveloperSecretIndex, secretValue);
+ apiClient.addAppDeveloperAppComputeSecret(authorization, UPPER_CASE_APP_ADDRESS, secretValue);
+ Assertions.fail("A second app developer secret with the same index " +
+ "and an app address whose only difference is the case should be rejected.");
+ } catch (FeignException.Conflict ignored) {
+ // Having a Conflict exception is what we expect there.
+ }
+ }
+
+ @Test
+ void addMultipleRequesterSecrets() {
+ List keys = List.of("secret-key-1", "secret-key-2", "secret-key-3");
+ for (String key : keys) {
+ addNewRequesterSecret(REQUESTER_ADDRESS, key, SECRET_VALUE);
+ }
+ Assertions.assertThat(repository.count()).isEqualTo(keys.size());
+ List secrets = repository.findAll();
+ Assertions.assertThat(secrets.stream().map(TeeTaskComputeSecret::getKey).collect(Collectors.toList()))
+ .containsExactlyInAnyOrder("secret-key-1", "secret-key-2", "secret-key-3");
+
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "this-is-a-really-long-key-with-far-too-many-characters-in-its-name",
+ "this-is-a-key-with-invalid-characters:!*~"
+ })
+ void checkInvalidRequesterSecretKey(String secretKey) {
+ Assertions.assertThatThrownBy(() -> addNewRequesterSecret(REQUESTER_ADDRESS, secretKey, SECRET_VALUE))
+ .isInstanceOf(FeignException.BadRequest.class);
+ Assertions.assertThat(repository.count()).isZero();
+ }
+
+ /**
+ * Checks no application developer secret already exists with given appAddress/index couple
+ * and adds a new application developer secret to the database
+ */
+ @SuppressWarnings("SameParameterValue")
+ private void addNewAppDeveloperSecret(String appAddress, String secretIndex, String secretValue, String ownerAddress) {
+ when(iexecHubService.getOwner(appAddress)).thenReturn(ownerAddress);
+
+ final String authorization = getAuthorizationForAppDeveloper(appAddress, secretIndex, secretValue);
+
+ // At first, no secret should be in the database
+ try {
+ apiClient.isAppDeveloperAppComputeSecretPresent(appAddress, secretIndex);
+ Assertions.fail("No application developer secret was expected but one has been retrieved.");
+ } catch (FeignException.NotFound ignored) {
+ // Having a Not Found exception is what we expect there.
+ }
+
+ // Add a new secret to the database
+ try {
+ apiClient.addAppDeveloperAppComputeSecret(authorization, appAddress, secretValue);
+ } catch (FeignException e) {
+ Assertions.assertThat(e.status()).isEqualTo(HttpStatus.NO_CONTENT.value());
+ }
+ }
+
+ /**
+ * Checks no requester secret already exists with given appAddress/index couple
+ * and adds a new requester secret to the database
+ */
+ @SuppressWarnings("SameParameterValue")
+ private void addNewRequesterSecret(String requesterAddress,
+ String secretKey,
+ String secretValue) {
+ final String authorization = getAuthorizationForRequester(requesterAddress, secretKey, secretValue);
+
+ // At first, no secret should be in the database
+ try {
+ apiClient.isRequesterAppComputeSecretPresent(requesterAddress, secretKey);
+ Assertions.fail("No application requester secret was expected but one has been retrieved.");
+ } catch (FeignException.NotFound ignored) {
+ // Having a Not Found exception is what we expect there.
+ }
+
+ // Add a new secret to the database
+ try {
+ apiClient.addRequesterAppComputeSecret(authorization, requesterAddress, secretKey, secretValue);
+ } catch (FeignException e) {
+ Assertions.assertThat(e.status()).isEqualTo(HttpStatus.NO_CONTENT.value());
+ }
+ }
+
+ /**
+ * Forges an authorization that'll permit adding
+ * given application developer secret to database.
+ */
+ private String getAuthorizationForAppDeveloper(
+ String appAddress,
+ String secretIndex,
+ String secretValue) {
+ final String challenge = HashUtils.concatenateAndHash(
+ Hash.sha3String(DOMAIN),
+ appAddress,
+ Hash.sha3String(secretIndex),
+ Hash.sha3String(secretValue));
+ return signMessageHashAndGetSignature(challenge, APP_DEVELOPER_PRIVATE_KEY).getValue();
+ }
+
+ /**
+ * Forges an authorization that'll permit adding
+ * given requester secret to database.
+ */
+ private String getAuthorizationForRequester(
+ String requesterAddress,
+ String secretKey,
+ String secretValue) {
+
+ final String challenge = HashUtils.concatenateAndHash(
+ Hash.sha3String(DOMAIN),
+ requesterAddress,
+ Hash.sha3String(secretKey),
+ Hash.sha3String(secretValue));
+ return signMessageHashAndGetSignature(challenge, REQUESTER_PRIVATE_KEY).getValue();
+ }
+}
diff --git a/src/itest/resources/application-test.yml b/src/itest/resources/application-test.yml
new file mode 100644
index 00000000..775bcdc0
--- /dev/null
+++ b/src/itest/resources/application-test.yml
@@ -0,0 +1,21 @@
+# Those `server` properties are needed so that a random port is chosen for the HTTP listener
+server:
+ http:
+ enabled: false
+ ssl:
+ enabled: false
+
+# Run database in-mem
+spring:
+ datasource:
+ url: jdbc:h2:mem:db
+
+# `TeeWorkflowConfiguration` needs some dummy value to be instantiated
+tee.workflow:
+ las-image: "none"
+ pre-compute:
+ image: "none"
+ fingerprint: "none"
+ post-compute:
+ image: "none"
+ fingerprint: "none"
diff --git a/src/main/java/com/iexec/sms/AppController.java b/src/main/java/com/iexec/sms/AppController.java
index b98e656e..ba2b2583 100644
--- a/src/main/java/com/iexec/sms/AppController.java
+++ b/src/main/java/com/iexec/sms/AppController.java
@@ -26,11 +26,8 @@
@RestController
public class AppController {
- public AppController() {
- }
-
@GetMapping(value = "/up")
- public static ResponseEntity isUp() {
+ public ResponseEntity isUp() {
String message = String.format("Up! (%s)", new Date());
return ResponseEntity.ok(message);
}
diff --git a/src/main/java/com/iexec/sms/authorization/AuthorizationError.java b/src/main/java/com/iexec/sms/authorization/AuthorizationError.java
new file mode 100644
index 00000000..025fd3b2
--- /dev/null
+++ b/src/main/java/com/iexec/sms/authorization/AuthorizationError.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.authorization;
+
+public enum AuthorizationError {
+ EMPTY_PARAMS_UNAUTHORIZED,
+ NO_MATCH_ONCHAIN_TYPE,
+ GET_CHAIN_TASK_FAILED,
+ TASK_NOT_ACTIVE,
+ GET_CHAIN_DEAL_FAILED,
+ INVALID_SIGNATURE;
+}
diff --git a/src/main/java/com/iexec/sms/authorization/AuthorizationService.java b/src/main/java/com/iexec/sms/authorization/AuthorizationService.java
index f0330f68..31399575 100644
--- a/src/main/java/com/iexec/sms/authorization/AuthorizationService.java
+++ b/src/main/java/com/iexec/sms/authorization/AuthorizationService.java
@@ -17,10 +17,6 @@
package com.iexec.sms.authorization;
-import static com.iexec.sms.App.DOMAIN;
-
-import java.util.Optional;
-
import com.iexec.common.chain.ChainDeal;
import com.iexec.common.chain.ChainTask;
import com.iexec.common.chain.ChainTaskStatus;
@@ -30,27 +26,38 @@
import com.iexec.common.utils.HashUtils;
import com.iexec.common.utils.SignatureUtils;
import com.iexec.sms.blockchain.IexecHubService;
-
-import org.springframework.stereotype.Service;
-
import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
import org.web3j.crypto.Hash;
+import java.util.Optional;
+
+import static com.iexec.sms.App.DOMAIN;
+import static com.iexec.sms.authorization.AuthorizationError.*;
+
@Slf4j
@Service
public class AuthorizationService {
-
- private IexecHubService iexecHubService;
+ private final IexecHubService iexecHubService;
public AuthorizationService(IexecHubService iexecHubService) {
this.iexecHubService = iexecHubService;
}
public boolean isAuthorizedOnExecution(WorkerpoolAuthorization workerpoolAuthorization, boolean isTeeTask) {
+ return isAuthorizedOnExecutionWithDetailedIssue(workerpoolAuthorization, isTeeTask).isEmpty();
+ }
+
+ /**
+ * Checks whether this execution is authorized.
+ * If not authorized, return the reason.
+ * Otherwise, returns an empty {@link Optional}.
+ */
+ public Optional isAuthorizedOnExecutionWithDetailedIssue(WorkerpoolAuthorization workerpoolAuthorization, boolean isTeeTask) {
if (workerpoolAuthorization == null || workerpoolAuthorization.getChainTaskId().isEmpty()) {
log.error("Not authorized with empty params");
- return false;
+ return Optional.of(EMPTY_PARAMS_UNAUTHORIZED);
}
String chainTaskId = workerpoolAuthorization.getChainTaskId();
@@ -59,13 +66,13 @@ public boolean isAuthorizedOnExecution(WorkerpoolAuthorization workerpoolAuthori
log.error("Could not match onchain task type [isTeeTask:{}, isTeeTaskOnchain:{},"
+ "chainTaskId:{}, walletAddress:{}]",isTeeTask, isTeeTaskOnchain,
chainTaskId, workerpoolAuthorization.getWorkerWallet());
- return false;
+ return Optional.of(NO_MATCH_ONCHAIN_TYPE);
}
Optional optionalChainTask = iexecHubService.getChainTask(chainTaskId);
- if (!optionalChainTask.isPresent()) {
+ if (optionalChainTask.isEmpty()) {
log.error("Could not get chainTask [chainTaskId:{}]", chainTaskId);
- return false;
+ return Optional.of(GET_CHAIN_TASK_FAILED);
}
ChainTask chainTask = optionalChainTask.get();
ChainTaskStatus taskStatus = chainTask.getStatus();
@@ -74,13 +81,13 @@ public boolean isAuthorizedOnExecution(WorkerpoolAuthorization workerpoolAuthori
if (!taskStatus.equals(ChainTaskStatus.ACTIVE)) {
log.error("Task not active onchain [chainTaskId:{}, status:{}]",
chainTaskId, taskStatus);
- return false;
+ return Optional.of(TASK_NOT_ACTIVE);
}
Optional optionalChainDeal = iexecHubService.getChainDeal(chainDealId);
- if (!optionalChainDeal.isPresent()) {
+ if (optionalChainDeal.isEmpty()) {
log.error("isAuthorizedOnExecution failed (getChainDeal failed) [chainTaskId:{}]", chainTaskId);
- return false;
+ return Optional.of(GET_CHAIN_DEAL_FAILED);
}
ChainDeal chainDeal = optionalChainDeal.get();
String workerpoolAddress = chainDeal.getPoolOwner();
@@ -90,10 +97,10 @@ public boolean isAuthorizedOnExecution(WorkerpoolAuthorization workerpoolAuthori
if (!isSignerByWorkerpool) {
log.error("isAuthorizedOnExecution failed (invalid signature) [chainTaskId:{}, isWorkerpoolSignatureValid:{}]",
chainTaskId, isSignerByWorkerpool);
- return false;
+ return Optional.of(INVALID_SIGNATURE);
}
- return true;
+ return Optional.empty();
}
public boolean isSignedByHimself(String message, String signature, String address) {
@@ -132,6 +139,26 @@ public String getChallengeForSetWeb3Secret(String secretAddress,
Hash.sha3String(secretValue));
}
+ public String getChallengeForSetAppDeveloperAppComputeSecret(String appAddress,
+ String secretIndex,
+ String secretValue) {
+ return HashUtils.concatenateAndHash(
+ Hash.sha3String(DOMAIN),
+ appAddress,
+ Hash.sha3String(secretIndex),
+ Hash.sha3String(secretValue));
+ }
+
+ public String getChallengeForSetRequesterAppComputeSecret(
+ String requesterAddress,
+ String secretKey,
+ String secretValue) {
+ return HashUtils.concatenateAndHash(
+ Hash.sha3String(DOMAIN),
+ requesterAddress,
+ Hash.sha3String(secretKey),
+ Hash.sha3String(secretValue));
+ }
public String getChallengeForSetWeb2Secret(String ownerAddress,
String secretKey,
diff --git a/src/main/java/com/iexec/sms/config/SwaggerConfig.java b/src/main/java/com/iexec/sms/config/OpenApiConfig.java
similarity index 54%
rename from src/main/java/com/iexec/sms/config/SwaggerConfig.java
rename to src/main/java/com/iexec/sms/config/OpenApiConfig.java
index 156e5a99..68975e9a 100644
--- a/src/main/java/com/iexec/sms/config/SwaggerConfig.java
+++ b/src/main/java/com/iexec/sms/config/OpenApiConfig.java
@@ -16,29 +16,29 @@
package com.iexec.sms.config;
+import com.iexec.sms.utils.version.VersionService;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
-@EnableSwagger2
-public class SwaggerConfig {
+public class OpenApiConfig {
+
+ private final VersionService versionService;
+
+ public OpenApiConfig(VersionService versionService) {
+ this.versionService = versionService;
+ }
/*
- *
- * Swagger link:
- * http://localhost:13300/swagger-ui.html
- * */
+ * Swagger URI: /swagger-ui/index.html
+ */
@Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2)
- .select()
- .apis(RequestHandlerSelectors.any())
- .paths(PathSelectors.any())
- .build();
- }
-}
\ No newline at end of file
+ public OpenAPI api() {
+ return new OpenAPI().info(
+ new Info()
+ .title("iExec SMS")
+ .version(versionService.getVersion())
+ );
+ }}
\ No newline at end of file
diff --git a/src/main/java/com/iexec/sms/secret/AbstractSecretService.java b/src/main/java/com/iexec/sms/secret/AbstractSecretService.java
index 2e4669f8..2a1271ee 100644
--- a/src/main/java/com/iexec/sms/secret/AbstractSecretService.java
+++ b/src/main/java/com/iexec/sms/secret/AbstractSecretService.java
@@ -18,14 +18,13 @@
import com.iexec.sms.encryption.EncryptionService;
-import lombok.AllArgsConstructor;
-import lombok.NoArgsConstructor;
-
-@NoArgsConstructor
-@AllArgsConstructor
public abstract class AbstractSecretService {
- public EncryptionService encryptionService;
+ private final EncryptionService encryptionService;
+
+ public AbstractSecretService(EncryptionService encryptionService) {
+ this.encryptionService = encryptionService;
+ }
public Secret encryptSecret(Secret secret) {
if (!secret.isEncryptedValue()) {
diff --git a/src/main/java/com/iexec/sms/secret/Secret.java b/src/main/java/com/iexec/sms/secret/Secret.java
index 9cd184c7..096f238d 100644
--- a/src/main/java/com/iexec/sms/secret/Secret.java
+++ b/src/main/java/com/iexec/sms/secret/Secret.java
@@ -26,7 +26,6 @@
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
-
import java.util.Objects;
@Data
@@ -72,5 +71,15 @@ public String getTrimmedValue() {
Objects.requireNonNull(this.value, "Secret value must not be null");
return this.value.trim();
}
+
+ @Override
+ public String toString() {
+ return "Secret{" +
+ "id='" + id + '\'' +
+ ", address='" + address + '\'' +
+ ", value='" + (isEncryptedValue ? value : "") + '\'' +
+ ", isEncryptedValue=" + isEncryptedValue +
+ '}';
+ }
}
diff --git a/src/main/java/com/iexec/sms/secret/SecretController.java b/src/main/java/com/iexec/sms/secret/SecretController.java
index 1f45ca86..f61af5b0 100644
--- a/src/main/java/com/iexec/sms/secret/SecretController.java
+++ b/src/main/java/com/iexec/sms/secret/SecretController.java
@@ -22,7 +22,6 @@
import com.iexec.sms.secret.web2.Web2SecretsService;
import com.iexec.sms.secret.web3.Web3Secret;
import com.iexec.sms.secret.web3.Web3SecretService;
-import com.iexec.sms.utils.version.VersionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -38,16 +37,13 @@
@RequestMapping("/secrets")
public class SecretController {
- private AuthorizationService authorizationService;
- private Web3SecretService web3SecretService;
- private VersionService versionService;
- private Web2SecretsService web2SecretsService;
+ private final AuthorizationService authorizationService;
+ private final Web3SecretService web3SecretService;
+ private final Web2SecretsService web2SecretsService;
- public SecretController(VersionService versionService,
- AuthorizationService authorizationService,
+ public SecretController(AuthorizationService authorizationService,
Web2SecretsService web2SecretsService,
Web3SecretService web3SecretService) {
- this.versionService = versionService;
this.web2SecretsService = web2SecretsService;
this.authorizationService = authorizationService;
this.web3SecretService = web3SecretService;
@@ -65,14 +61,12 @@ public ResponseEntity> isWeb3SecretSet(@RequestParam String secretAddress) {
public ResponseEntity getWeb3Secret(@RequestHeader("Authorization") String authorization,
@RequestParam String secretAddress,
@RequestParam(required = false, defaultValue = "false") boolean shouldDecryptSecret) {
- if (isInProduction(authorization)) {
- String challenge = authorizationService.getChallengeForGetWeb3Secret(secretAddress);
-
- //TODO: also isAuthorizedOnExecution(..)
- if (!authorizationService.isSignedByOwner(challenge, authorization, secretAddress)) {
- log.error("Unauthorized to getWeb3Secret [expectedChallenge:{}]", challenge);
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
- }
+ String challenge = authorizationService.getChallengeForGetWeb3Secret(secretAddress);
+
+ //TODO: also isAuthorizedOnExecution(..)
+ if (!authorizationService.isSignedByOwner(challenge, authorization, secretAddress)) {
+ log.error("Unauthorized to getWeb3Secret [expectedChallenge:{}]", challenge);
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
Optional secret = web3SecretService.getSecret(secretAddress, shouldDecryptSecret);
@@ -83,13 +77,15 @@ public ResponseEntity getWeb3Secret(@RequestHeader("Authorization")
public ResponseEntity addWeb3Secret(@RequestHeader("Authorization") String authorization,
@RequestParam String secretAddress,
@RequestBody String secretValue) {
- if (isInProduction(authorization)) {
- String challenge = authorizationService.getChallengeForSetWeb3Secret(secretAddress, secretValue);
+ if (!SecretUtils.isSecretSizeValid(secretValue)) {
+ return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE).build();
+ }
- if (!authorizationService.isSignedByOwner(challenge, authorization, secretAddress)) {
- log.error("Unauthorized to addWeb3Secret [expectedChallenge:{}]", challenge);
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
- }
+ String challenge = authorizationService.getChallengeForSetWeb3Secret(secretAddress, secretValue);
+
+ if (!authorizationService.isSignedByOwner(challenge, authorization, secretAddress)) {
+ log.error("Unauthorized to addWeb3Secret [expectedChallenge:{}]", challenge);
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
if (web3SecretService.getSecret(secretAddress).isPresent()) {
@@ -114,13 +110,11 @@ public ResponseEntity getWeb2Secret(@RequestHeader("Authorization") Stri
@RequestParam String ownerAddress,
@RequestParam String secretName,
@RequestParam(required = false, defaultValue = "false") boolean shouldDecryptSecret) {
- if (isInProduction(authorization)) {
- String challenge = authorizationService.getChallengeForGetWeb2Secret(ownerAddress, secretName);
+ String challenge = authorizationService.getChallengeForGetWeb2Secret(ownerAddress, secretName);
- if (!authorizationService.isSignedByHimself(challenge, authorization, ownerAddress)) {
- log.error("Unauthorized to getWeb2Secret [expectedChallenge:{}]", challenge);
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
- }
+ if (!authorizationService.isSignedByHimself(challenge, authorization, ownerAddress)) {
+ log.error("Unauthorized to getWeb2Secret [expectedChallenge:{}]", challenge);
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
Optional secret = web2SecretsService.getSecret(ownerAddress, secretName, shouldDecryptSecret);
@@ -132,13 +126,15 @@ public ResponseEntity addWeb2Secret(@RequestHeader("Authorization") Stri
@RequestParam String ownerAddress,
@RequestParam String secretName,
@RequestBody String secretValue) {
- if (isInProduction(authorization)) {
- String challenge = authorizationService.getChallengeForSetWeb2Secret(ownerAddress, secretName, secretValue);
+ if (!SecretUtils.isSecretSizeValid(secretValue)) {
+ return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE).build();
+ }
- if (!authorizationService.isSignedByHimself(challenge, authorization, ownerAddress)) {
- log.error("Unauthorized to addWeb2Secret [expectedChallenge:{}]", challenge);
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
- }
+ String challenge = authorizationService.getChallengeForSetWeb2Secret(ownerAddress, secretName, secretValue);
+
+ if (!authorizationService.isSignedByHimself(challenge, authorization, ownerAddress)) {
+ log.error("Unauthorized to addWeb2Secret [expectedChallenge:{}]", challenge);
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
if (web2SecretsService.getSecret(ownerAddress, secretName).isPresent()) {
@@ -154,13 +150,11 @@ public ResponseEntity updateWeb2Secret(@RequestHeader("Authorization") S
@RequestParam String ownerAddress,
@RequestParam String secretName,
@RequestBody String newSecretValue) {
- if (isInProduction(authorization)) {
- String challenge = authorizationService.getChallengeForSetWeb2Secret(ownerAddress, secretName, newSecretValue);
+ String challenge = authorizationService.getChallengeForSetWeb2Secret(ownerAddress, secretName, newSecretValue);
- if (!authorizationService.isSignedByHimself(challenge, authorization, ownerAddress)) {
- log.error("Unauthorized to updateWeb2Secret [expectedChallenge:{}]", challenge);
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
- }
+ if (!authorizationService.isSignedByHimself(challenge, authorization, ownerAddress)) {
+ log.error("Unauthorized to updateWeb2Secret [expectedChallenge:{}]", challenge);
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
if (web2SecretsService.getSecret(ownerAddress, secretName).isEmpty()) {
@@ -175,7 +169,7 @@ public ResponseEntity updateWeb2Secret(@RequestHeader("Authorization") S
* Server-side signature of a messageHash
* */
@PostMapping("/delegate/signature")
- private ResponseEntity signMessageHashOnServerSide(@RequestParam String messageHash,
+ public ResponseEntity signMessageHashOnServerSide(@RequestParam String messageHash,
@RequestBody String privateKey) {
Signature signature = signMessageHashAndGetSignature(messageHash, privateKey);
@@ -185,11 +179,5 @@ private ResponseEntity signMessageHashOnServerSide(@RequestParam String
return ResponseEntity.ok(signature.getValue());
}
-
- private boolean isInProduction(String authorization) {
- boolean canAvoidAuthorization = versionService.isSnapshot() && authorization.equals("*");
- return !canAvoidAuthorization;
- }
-
}
diff --git a/src/main/java/com/iexec/sms/secret/SecretUtils.java b/src/main/java/com/iexec/sms/secret/SecretUtils.java
new file mode 100644
index 00000000..660b98e3
--- /dev/null
+++ b/src/main/java/com/iexec/sms/secret/SecretUtils.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.secret;
+
+import java.nio.charset.StandardCharsets;
+
+public abstract class SecretUtils {
+ /**
+ * Max size (in kBs) a secret can have.
+ */
+ public static final int SECRET_MAX_SIZE = 4096;
+
+ public static boolean isSecretSizeValid(String secretValue) {
+ return secretValue.getBytes(StandardCharsets.UTF_8).length <= SECRET_MAX_SIZE;
+ }
+}
diff --git a/src/main/java/com/iexec/sms/secret/compute/AppComputeSecretController.java b/src/main/java/com/iexec/sms/secret/compute/AppComputeSecretController.java
new file mode 100644
index 00000000..f52a3810
--- /dev/null
+++ b/src/main/java/com/iexec/sms/secret/compute/AppComputeSecretController.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2021 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.secret.compute;
+
+import com.iexec.common.web.ApiResponseBody;
+import com.iexec.sms.authorization.AuthorizationService;
+import com.iexec.sms.secret.SecretUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+@Slf4j
+@CrossOrigin
+@RestController
+public class AppComputeSecretController {
+ private final AuthorizationService authorizationService;
+ private final TeeTaskComputeSecretService teeTaskComputeSecretService;
+
+ private static final ApiResponseBody> invalidAuthorizationPayload = createErrorPayload("Invalid authorization");
+
+ static final String INVALID_SECRET_INDEX_FORMAT_MSG = "Secret index should be a positive number";
+ static final String INVALID_SECRET_KEY_FORMAT_MSG = "Secret key should contain at most 64 characters from [0-9A-Za-z-_]";
+
+ private static final Pattern secretKeyPattern = Pattern.compile("^[\\p{Alnum}-_]{"
+ + TeeTaskComputeSecret.SECRET_KEY_MIN_LENGTH + ","
+ + TeeTaskComputeSecret.SECRET_KEY_MAX_LENGTH + "}$");
+
+ public AppComputeSecretController(AuthorizationService authorizationService,
+ TeeTaskComputeSecretService teeTaskComputeSecretService) {
+ this.authorizationService = authorizationService;
+ this.teeTaskComputeSecretService = teeTaskComputeSecretService;
+ }
+
+ // region App developer endpoints
+ @PostMapping("/apps/{appAddress}/secrets/1")
+ public ResponseEntity>> addAppDeveloperAppComputeSecret(@RequestHeader("Authorization") String authorization,
+ @PathVariable String appAddress,
+ @RequestBody String secretValue) {
+ appAddress = appAddress.toLowerCase();
+ String secretIndex = "1";
+
+ try {
+ checkSecretIndex(secretIndex);
+ } catch (NumberFormatException e) {
+ log.error(INVALID_SECRET_INDEX_FORMAT_MSG, e);
+ return ResponseEntity
+ .badRequest()
+ .body(createErrorPayload(INVALID_SECRET_INDEX_FORMAT_MSG));
+ }
+
+ if (!SecretUtils.isSecretSizeValid(secretValue)) {
+ return ResponseEntity
+ .status(HttpStatus.PAYLOAD_TOO_LARGE)
+ .body(createErrorPayload("Secret size should not exceed 4 Kb"));
+ }
+
+ String challenge = authorizationService.getChallengeForSetAppDeveloperAppComputeSecret(appAddress, secretIndex, secretValue);
+
+ if (!authorizationService.isSignedByOwner(challenge, authorization, appAddress)) {
+ log.error("Unauthorized to addAppDeveloperComputeComputeSecret" +
+ " [appAddress: {}, expectedChallenge: {}]",
+ appAddress, challenge);
+ return ResponseEntity
+ .status(HttpStatus.UNAUTHORIZED)
+ .body(invalidAuthorizationPayload);
+ }
+
+ if (teeTaskComputeSecretService.isSecretPresent(
+ OnChainObjectType.APPLICATION,
+ appAddress,
+ SecretOwnerRole.APPLICATION_DEVELOPER,
+ "",
+ secretIndex)) {
+ log.error("Can't add app developer secret as it already exists" +
+ " [appAddress:{}, secretIndex:{}]",
+ appAddress, secretIndex);
+ return ResponseEntity
+ .status(HttpStatus.CONFLICT)
+ .body(createErrorPayload("Secret already exists"));
+ }
+
+ teeTaskComputeSecretService.encryptAndSaveSecret(
+ OnChainObjectType.APPLICATION,
+ appAddress,
+ SecretOwnerRole.APPLICATION_DEVELOPER,
+ "",
+ secretIndex,
+ secretValue
+ );
+ return ResponseEntity.noContent().build();
+ }
+
+ @RequestMapping(method = RequestMethod.HEAD, path = "/apps/{appAddress}/secrets/{secretIndex}")
+ public ResponseEntity>> isAppDeveloperAppComputeSecretPresent(@PathVariable String appAddress,
+ @PathVariable String secretIndex) {
+ appAddress = appAddress.toLowerCase();
+ try {
+ checkSecretIndex(secretIndex);
+ } catch (NumberFormatException e) {
+ log.error(INVALID_SECRET_INDEX_FORMAT_MSG, e);
+ return ResponseEntity
+ .badRequest()
+ .body(createErrorPayload(INVALID_SECRET_INDEX_FORMAT_MSG));
+ }
+
+ final boolean isSecretPresent = teeTaskComputeSecretService.isSecretPresent(
+ OnChainObjectType.APPLICATION,
+ appAddress,
+ SecretOwnerRole.APPLICATION_DEVELOPER,
+ "",
+ secretIndex
+ );
+ if (isSecretPresent) {
+ log.debug("App developer secret found [appAddress: {}, secretIndex: {}]", appAddress, secretIndex);
+ return ResponseEntity.noContent().build();
+ }
+
+ log.debug("App developer secret not found [appAddress: {}, secretIndex: {}]", appAddress, secretIndex);
+ return ResponseEntity
+ .status(HttpStatus.NOT_FOUND)
+ .body(createErrorPayload("Secret not found"));
+ }
+
+ /**
+ * Checks provided application developer index is in a valid range.
+ * A valid index is a positive number.
+ * @param secretIndex Secret index value to check.
+ */
+ private void checkSecretIndex(String secretIndex) {
+ int idx = Integer.parseInt(secretIndex);
+ if (idx <= 0) {
+ throw new NumberFormatException();
+ }
+ }
+ // endregion
+
+ // region App requester endpoint
+ @PostMapping("/requesters/{requesterAddress}/secrets/{secretKey}")
+ public ResponseEntity>> addRequesterAppComputeSecret(@RequestHeader("Authorization") String authorization,
+ @PathVariable String requesterAddress,
+ @PathVariable String secretKey,
+ @RequestBody String secretValue) {
+ requesterAddress = requesterAddress.toLowerCase();
+ if (!secretKeyPattern.matcher(secretKey).matches()) {
+ return ResponseEntity
+ .badRequest()
+ .body(createErrorPayload(INVALID_SECRET_KEY_FORMAT_MSG));
+ }
+
+ String challenge = authorizationService.getChallengeForSetRequesterAppComputeSecret(
+ requesterAddress,
+ secretKey,
+ secretValue
+ );
+
+ if (!authorizationService.isSignedByHimself(challenge, authorization, requesterAddress)) {
+ log.error("Unauthorized to addRequesterAppComputeSecret" +
+ " [requesterAddress:{}, expectedChallenge:{}]",
+ requesterAddress, challenge);
+ return ResponseEntity
+ .status(HttpStatus.UNAUTHORIZED)
+ .body(invalidAuthorizationPayload);
+ }
+
+ final List badRequestErrors = validateRequesterAppComputeSecret(requesterAddress, secretKey, secretValue);
+ if (!badRequestErrors.isEmpty()) {
+ return ResponseEntity
+ .badRequest()
+ .body(createErrorPayload(badRequestErrors));
+ }
+
+ if (teeTaskComputeSecretService.isSecretPresent(
+ OnChainObjectType.APPLICATION,
+ "",
+ SecretOwnerRole.REQUESTER,
+ requesterAddress,
+ secretKey)) {
+ log.debug("Can't add requester secret as it already exists" +
+ " [requesterAddress:{}, secretIndex:{}]",
+ requesterAddress, secretKey);
+ return ResponseEntity
+ .status(HttpStatus.CONFLICT)
+ .body(createErrorPayload("Secret already exists"));
+ }
+
+ teeTaskComputeSecretService.encryptAndSaveSecret(
+ OnChainObjectType.APPLICATION,
+ "",
+ SecretOwnerRole.REQUESTER,
+ requesterAddress,
+ secretKey,
+ secretValue
+ );
+ return ResponseEntity.noContent().build();
+ }
+
+ private List validateRequesterAppComputeSecret(
+ String requesterAddress,
+ String secretKey,
+ String secretValue) {
+ List errors = new ArrayList<>();
+
+ if (!SecretUtils.isSecretSizeValid(secretValue)) {
+ final String errorMessage = "Secret size should not exceed 4 Kb";
+ log.debug("{} [requesterAddress:{}, secretIndex:{}, secretLength:{}]",
+ errorMessage, requesterAddress, secretKey, secretValue.length()
+ );
+ errors.add(errorMessage);
+ }
+
+ return errors;
+ }
+
+ @RequestMapping(method = RequestMethod.HEAD, path = "/requesters/{requesterAddress}/secrets/{secretKey}")
+ public ResponseEntity>> isRequesterAppComputeSecretPresent(
+ @PathVariable String requesterAddress,
+ @PathVariable String secretKey) {
+ requesterAddress = requesterAddress.toLowerCase();
+
+ if (!secretKeyPattern.matcher(secretKey).matches()) {
+ return ResponseEntity
+ .badRequest()
+ .body(createErrorPayload(INVALID_SECRET_KEY_FORMAT_MSG));
+ }
+
+ final boolean isSecretPresent = teeTaskComputeSecretService.isSecretPresent(
+ OnChainObjectType.APPLICATION,
+ "",
+ SecretOwnerRole.REQUESTER,
+ requesterAddress,
+ secretKey
+ );
+
+ String messageDetails = MessageFormat.format("[requester: {0}, secretIndex: {1}]",
+ requesterAddress, secretKey);
+ if (isSecretPresent) {
+ log.debug("App requester secret found {}", messageDetails);
+ return ResponseEntity.noContent().build();
+ }
+
+ log.debug("App requester secret not found {}", messageDetails);
+ return ResponseEntity
+ .status(HttpStatus.NOT_FOUND)
+ .body(createErrorPayload("Secret not found"));
+ }
+ // endregion
+
+ private static ApiResponseBody> createErrorPayload(String errorMessage) {
+ return createErrorPayload(List.of(errorMessage));
+ }
+
+ private static ApiResponseBody> createErrorPayload(List errors) {
+ return ApiResponseBody
+ .>builder()
+ .error(errors)
+ .build();
+ }
+}
diff --git a/src/main/java/com/iexec/sms/secret/compute/OnChainObjectType.java b/src/main/java/com/iexec/sms/secret/compute/OnChainObjectType.java
new file mode 100644
index 00000000..55782b1a
--- /dev/null
+++ b/src/main/java/com/iexec/sms/secret/compute/OnChainObjectType.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2021 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.secret.compute;
+
+public enum OnChainObjectType {
+ APPLICATION
+}
diff --git a/src/main/java/com/iexec/sms/secret/compute/SecretOwnerRole.java b/src/main/java/com/iexec/sms/secret/compute/SecretOwnerRole.java
new file mode 100644
index 00000000..c0415dcf
--- /dev/null
+++ b/src/main/java/com/iexec/sms/secret/compute/SecretOwnerRole.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.secret.compute;
+
+/**
+ * Defines which role a tee task compute secret owner can have.
+ */
+public enum SecretOwnerRole {
+ APPLICATION_DEVELOPER,
+ REQUESTER
+}
diff --git a/src/main/java/com/iexec/sms/secret/compute/TeeTaskComputeSecret.java b/src/main/java/com/iexec/sms/secret/compute/TeeTaskComputeSecret.java
new file mode 100644
index 00000000..7658abb1
--- /dev/null
+++ b/src/main/java/com/iexec/sms/secret/compute/TeeTaskComputeSecret.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2021 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.secret.compute;
+
+import com.iexec.sms.secret.SecretUtils;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+
+/**
+ * Define a secret that can be used during the execution of a TEE task.
+ * Currently, only secrets for application developers and requesters are supported.
+ *
+ * In this implementation, a unique constraint has been added on onChainObjectAddress,
+ * fixedSecretOwner and key columns.
+ * This constraint has been defined in such a way because:
+ *
+ * - For application developers, fixedSecretOwner will always be "". Each application developer
+ * secret is uniquely identified with onChainObjectAddress and key (long parsed as String) values.
+ *
- For requesters, onChainObjectAddress will always be "". Each requester secret is uniquely
+ * identified with fixedSecretOwner and key values.
+ *
+ *
+ * The key parameter must be a String compliant with the following constraints:
+ *
+ * - For application developers, it must be a positive number, the characters must be digits [0-9].
+ *
- For requesters, it must be a String of at most 64 characters from [0-9A-Za-z-_].
+ *
+ */
+@Data
+@NoArgsConstructor
+@Entity
+@Table(uniqueConstraints = { @UniqueConstraint(columnNames = {"onChainObjectAddress", "fixedSecretOwner", "key"}) })
+public class TeeTaskComputeSecret implements Serializable {
+
+ public static final int SECRET_KEY_MIN_LENGTH = 1;
+ public static final int SECRET_KEY_MAX_LENGTH = 64;
+
+ @Id
+ @GeneratedValue(generator = "system-uuid")
+ @GenericGenerator(name = "system-uuid", strategy = "uuid")
+ private String id;
+
+ /**
+ * Represents the blockchain address of the deployed object
+ * (0xapplication, 0xdataset, 0xworkerpool)
+ *
+ * In a future release, it should also handle ENS names.
+ */
+ @NotNull
+ private String onChainObjectAddress; // Will be empty for a secret belonging to a requester
+ @NotNull
+ private OnChainObjectType onChainObjectType;
+ @NotNull
+ private SecretOwnerRole secretOwnerRole;
+ @NotNull
+ private String fixedSecretOwner; // Will be empty for a secret belonging to an application developer
+ @NotNull
+ @Size(min = SECRET_KEY_MIN_LENGTH, max = SECRET_KEY_MAX_LENGTH)
+ private String key;
+ @NotNull
+ /*
+ * Expected behavior of AES encryption is to not expand the data very much.
+ * Final size might be padded to the next block, plus another padding might
+ * be necessary for the IV (https://stackoverflow.com/a/93463).
+ * In addition to that, it is worth mentioning that current implementation
+ * encrypts the input and produces a Base64 result (stored as-is in
+ * database) which causes an overhead of ~33%
+ * (https://en.wikipedia.org/wiki/Base64).
+ *
+ * For these reasons and for simplicity purposes, we reserve twice the size
+ * of `SECRET_MAX_SIZE` in storage.
+ */
+ @Column(length = SecretUtils.SECRET_MAX_SIZE * 2)
+ private String value;
+
+ @Builder
+ public TeeTaskComputeSecret(
+ OnChainObjectType onChainObjectType,
+ String onChainObjectAddress,
+ SecretOwnerRole secretOwnerRole,
+ String fixedSecretOwner,
+ String key,
+ String value) {
+ this.onChainObjectType = onChainObjectType;
+ this.onChainObjectAddress = onChainObjectAddress;
+ this.secretOwnerRole = secretOwnerRole;
+ this.fixedSecretOwner = fixedSecretOwner;
+ this.key = key;
+ this.value = value;
+ }
+}
diff --git a/src/main/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretRepository.java b/src/main/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretRepository.java
new file mode 100644
index 00000000..88180feb
--- /dev/null
+++ b/src/main/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretRepository.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.secret.compute;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TeeTaskComputeSecretRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretService.java b/src/main/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretService.java
new file mode 100644
index 00000000..0c321b04
--- /dev/null
+++ b/src/main/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretService.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2021 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.secret.compute;
+
+import com.iexec.sms.encryption.EncryptionService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.SerializationUtils;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.ExampleMatcher;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Slf4j
+@Service
+public class TeeTaskComputeSecretService {
+ private final TeeTaskComputeSecretRepository teeTaskComputeSecretRepository;
+ private final EncryptionService encryptionService;
+
+ protected TeeTaskComputeSecretService(
+ TeeTaskComputeSecretRepository teeTaskComputeSecretRepository,
+ EncryptionService encryptionService) {
+ this.teeTaskComputeSecretRepository = teeTaskComputeSecretRepository;
+ this.encryptionService = encryptionService;
+ }
+
+ /**
+ * Retrieve a secret.
+ * Decrypt if required.
+ */
+ public Optional getSecret(
+ OnChainObjectType onChainObjectType,
+ String onChainObjectAddress,
+ SecretOwnerRole secretOwnerRole,
+ String secretOwner,
+ String secretKey) {
+ onChainObjectAddress = onChainObjectAddress.toLowerCase();
+ final TeeTaskComputeSecret wantedSecret = TeeTaskComputeSecret
+ .builder()
+ .onChainObjectType(onChainObjectType)
+ .onChainObjectAddress(onChainObjectAddress)
+ .secretOwnerRole(secretOwnerRole)
+ .fixedSecretOwner(secretOwner)
+ .key(secretKey)
+ .build();
+ final ExampleMatcher exampleMatcher = ExampleMatcher.matching()
+ .withIgnorePaths("value");
+ final Optional oSecret = teeTaskComputeSecretRepository
+ .findOne(Example.of(wantedSecret, exampleMatcher));
+ if (oSecret.isEmpty()) {
+ return Optional.empty();
+ }
+ final TeeTaskComputeSecret secret = oSecret.get();
+ final String decryptedValue = encryptionService.decrypt(secret.getValue());
+ // deep copy to avoid altering original object
+ //TODO: Improve this out-of-the box cloning to get better performances
+ TeeTaskComputeSecret decryptedSecret = SerializationUtils.clone(secret);
+ decryptedSecret.setValue(decryptedValue);
+ return Optional.of(decryptedSecret);
+ }
+
+ /**
+ * Check whether a secret exists.
+ *
+ * @return {@code true} if the secret exists in the database, {@code false} otherwise.
+ */
+ public boolean isSecretPresent(OnChainObjectType onChainObjectType,
+ String deployedObjectAddress,
+ SecretOwnerRole secretOwnerRole,
+ String secretOwner,
+ String secretKey) {
+ return getSecret(
+ onChainObjectType,
+ deployedObjectAddress,
+ secretOwnerRole,
+ secretOwner,
+ secretKey
+ ).isPresent();
+ }
+
+ /**
+ * Encrypt a secret and store it if it doesn't already exist.
+ *
+ * @return {@code false} if the secret already exists, {@code true} otherwise.
+ */
+ public boolean encryptAndSaveSecret(OnChainObjectType onChainObjectType,
+ String onChainObjectAddress,
+ SecretOwnerRole secretOwnerRole,
+ String secretOwner,
+ String secretKey,
+ String secretValue) {
+ if (isSecretPresent(onChainObjectType, onChainObjectAddress, secretOwnerRole, secretOwner, secretKey)) {
+ final TeeTaskComputeSecret secret = TeeTaskComputeSecret
+ .builder()
+ .onChainObjectType(onChainObjectType)
+ .onChainObjectAddress(onChainObjectAddress)
+ .secretOwnerRole(secretOwnerRole)
+ .fixedSecretOwner(secretOwner)
+ .key(secretKey)
+ .build();
+ log.info("Tee task compute secret already exists, can't update it." +
+ " [secret:{}]", secret);
+ return false;
+ }
+ onChainObjectAddress = onChainObjectAddress.toLowerCase();
+ final TeeTaskComputeSecret secret = TeeTaskComputeSecret
+ .builder()
+ .onChainObjectType(onChainObjectType)
+ .onChainObjectAddress(onChainObjectAddress)
+ .secretOwnerRole(secretOwnerRole)
+ .fixedSecretOwner(secretOwner)
+ .key(secretKey)
+ .value(encryptionService.encrypt(secretValue))
+ .build();
+ log.info("Adding new tee task compute secret" +
+ " [secret:{}]", secret);
+ teeTaskComputeSecretRepository.save(secret);
+ return true;
+ }
+}
diff --git a/src/main/java/com/iexec/sms/secret/web2/Web2SecretsService.java b/src/main/java/com/iexec/sms/secret/web2/Web2SecretsService.java
index 438d39ee..4122451f 100644
--- a/src/main/java/com/iexec/sms/secret/web2/Web2SecretsService.java
+++ b/src/main/java/com/iexec/sms/secret/web2/Web2SecretsService.java
@@ -29,7 +29,7 @@
@Service
public class Web2SecretsService extends AbstractSecretService {
- private Web2SecretsRepository web2SecretsRepository;
+ private final Web2SecretsRepository web2SecretsRepository;
public Web2SecretsService(Web2SecretsRepository web2SecretsRepository,
EncryptionService encryptionService) {
@@ -43,14 +43,13 @@ public Optional getWeb2Secrets(String ownerAddress) {
}
public Optional getSecret(String ownerAddress, String secretAddress) {
- ownerAddress = ownerAddress.toLowerCase();
return getSecret(ownerAddress, secretAddress, false);
}
public Optional getSecret(String ownerAddress, String secretAddress, boolean shouldDecryptValue) {
ownerAddress = ownerAddress.toLowerCase();
Optional web2Secrets = getWeb2Secrets(ownerAddress);
- if (!web2Secrets.isPresent()) {
+ if (web2Secrets.isEmpty()) {
return Optional.empty();
}
Secret secret = web2Secrets.get().getSecret(secretAddress);
@@ -73,7 +72,7 @@ public void addSecret(String ownerAddress, String secretAddress, String secretVa
Secret secret = new Secret(secretAddress, secretValue);
encryptSecret(secret);
- log.info("Adding new secret [ownerAddress:{}, secretAddress:{}, secretValueHash:{}]",
+ log.info("Adding new secret [ownerAddress:{}, secretAddress:{}, encryptedSecretValue:{}]",
ownerAddress, secretAddress, secret.getValue());
web2Secrets.getSecrets().add(secret);
web2SecretsRepository.save(web2Secrets);
@@ -91,7 +90,7 @@ public void updateSecret(String ownerAddress, String secretAddress, String newSe
return;
}
- log.info("Updating secret [ownerAddress:{}, secretAddress:{}, oldSecretValueHash:{}, newSecretValueHash:{}]",
+ log.info("Updating secret [ownerAddress:{}, secretAddress:{}, oldEncryptedSecretValue:{}, newEncryptedSecretValue:{}]",
ownerAddress, secretAddress, existingSecret.getValue(), newSecret.getValue());
existingSecret.setValue(newSecret.getValue(), true);
web2SecretsRepository.save(web2Secrets.get());
diff --git a/src/main/java/com/iexec/sms/secret/web3/Web3SecretService.java b/src/main/java/com/iexec/sms/secret/web3/Web3SecretService.java
index 8f42599a..ecf197b8 100644
--- a/src/main/java/com/iexec/sms/secret/web3/Web3SecretService.java
+++ b/src/main/java/com/iexec/sms/secret/web3/Web3SecretService.java
@@ -28,7 +28,7 @@
@Service
public class Web3SecretService extends AbstractSecretService {
- private Web3SecretRepository web3SecretRepository;
+ private final Web3SecretRepository web3SecretRepository;
public Web3SecretService(Web3SecretRepository web3SecretRepository,
EncryptionService encryptionService) {
@@ -45,11 +45,10 @@ public Optional getSecret(String secretAddress, boolean shouldDecryp
if (shouldDecryptValue) {
decryptSecret(secret.get());
}
- return Optional.of(secret.get());
+ return secret;
}
public Optional getSecret(String secretAddress) {
- secretAddress = secretAddress.toLowerCase();
return getSecret(secretAddress, false);
}
@@ -61,7 +60,7 @@ public void addSecret(String secretAddress, String secretValue) {
secretAddress = secretAddress.toLowerCase();
Web3Secret web3Secret = new Web3Secret(secretAddress, secretValue);
encryptSecret(web3Secret);
- log.info("Adding new web3 secret [secretAddress:{}, secretValueHash:{}]",
+ log.info("Adding new web3 secret [secretAddress:{}, encryptedSecretValue:{}]",
secretAddress, web3Secret.getValue());
web3SecretRepository.save(web3Secret);
}
diff --git a/src/main/java/com/iexec/sms/tee/TeeController.java b/src/main/java/com/iexec/sms/tee/TeeController.java
index 9e6c4211..917eb875 100644
--- a/src/main/java/com/iexec/sms/tee/TeeController.java
+++ b/src/main/java/com/iexec/sms/tee/TeeController.java
@@ -17,26 +17,42 @@
package com.iexec.sms.tee;
-import java.util.Optional;
-
import com.iexec.common.chain.WorkerpoolAuthorization;
+import com.iexec.sms.api.TeeSessionGenerationError;
import com.iexec.common.tee.TeeWorkflowSharedConfiguration;
+import com.iexec.common.web.ApiResponseBody;
+import com.iexec.sms.authorization.AuthorizationError;
import com.iexec.sms.authorization.AuthorizationService;
import com.iexec.sms.tee.challenge.TeeChallenge;
import com.iexec.sms.tee.challenge.TeeChallengeService;
+import com.iexec.sms.tee.session.TeeSessionGenerationException;
import com.iexec.sms.tee.session.TeeSessionService;
import com.iexec.sms.tee.workflow.TeeWorkflowConfiguration;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.web3j.crypto.Keys;
-import lombok.extern.slf4j.Slf4j;
+import java.util.Map;
+import java.util.Optional;
+
+import static com.iexec.sms.api.TeeSessionGenerationError.*;
+import static com.iexec.sms.authorization.AuthorizationError.*;
@Slf4j
@RestController
@RequestMapping("/tee")
public class TeeController {
+ private static final Map authorizationToGenerationError =
+ Map.of(
+ EMPTY_PARAMS_UNAUTHORIZED, EXECUTION_NOT_AUTHORIZED_EMPTY_PARAMS_UNAUTHORIZED,
+ NO_MATCH_ONCHAIN_TYPE, EXECUTION_NOT_AUTHORIZED_NO_MATCH_ONCHAIN_TYPE,
+ GET_CHAIN_TASK_FAILED, EXECUTION_NOT_AUTHORIZED_GET_CHAIN_TASK_FAILED,
+ TASK_NOT_ACTIVE, EXECUTION_NOT_AUTHORIZED_TASK_NOT_ACTIVE,
+ GET_CHAIN_DEAL_FAILED, EXECUTION_NOT_AUTHORIZED_GET_CHAIN_DEAL_FAILED,
+ INVALID_SIGNATURE, EXECUTION_NOT_AUTHORIZED_INVALID_SIGNATURE
+ );
private final AuthorizationService authorizationService;
private final TeeChallengeService teeChallengeService;
@@ -95,16 +111,35 @@ public ResponseEntity generateTeeChallenge(@PathVariable String chainTas
* 500 INTERNAL_SERVER_ERROR otherwise.
*/
@PostMapping("/sessions")
- public ResponseEntity generateTeeSession(
+ public ResponseEntity> generateTeeSession(
@RequestHeader("Authorization") String authorization,
@RequestBody WorkerpoolAuthorization workerpoolAuthorization) {
String workerAddress = workerpoolAuthorization.getWorkerWallet();
String challenge = authorizationService.getChallengeForWorker(workerpoolAuthorization);
if (!authorizationService.isSignedByHimself(challenge, authorization, workerAddress)) {
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+ final ApiResponseBody body =
+ ApiResponseBody.builder()
+ .error(INVALID_AUTHORIZATION)
+ .build();
+
+ return ResponseEntity
+ .status(HttpStatus.UNAUTHORIZED)
+ .body(body);
}
- if (!authorizationService.isAuthorizedOnExecution(workerpoolAuthorization, true)) {
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+ final Optional authorizationError =
+ authorizationService.isAuthorizedOnExecutionWithDetailedIssue(workerpoolAuthorization, true);
+ if (authorizationError.isPresent()) {
+ final TeeSessionGenerationError teeSessionGenerationError =
+ authorizationToGenerationError.get(authorizationError.get());
+
+ final ApiResponseBody body =
+ ApiResponseBody.builder()
+ .error(teeSessionGenerationError)
+ .build();
+
+ return ResponseEntity
+ .status(HttpStatus.UNAUTHORIZED)
+ .body(body);
}
String taskId = workerpoolAuthorization.getChainTaskId();
workerAddress = Keys.toChecksumAddress(workerAddress);
@@ -114,13 +149,32 @@ public ResponseEntity generateTeeSession(
try {
String sessionId = teeSessionService
.generateTeeSession(taskId, workerAddress, attestingEnclave);
- return sessionId.isEmpty()
- ? ResponseEntity.notFound().build()
- : ResponseEntity.ok(sessionId);
- } catch(Exception e) {
+
+ if (sessionId.isEmpty()) {
+ return ResponseEntity.notFound().build();
+ }
+
+ return ResponseEntity.ok(ApiResponseBody.builder().data(sessionId).build());
+ } catch(TeeSessionGenerationException e) {
log.error("Failed to generate secure session [taskId:{}, workerAddress:{}]",
taskId, workerAddress, e);
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+ final ApiResponseBody body =
+ ApiResponseBody.builder()
+ .error(e.getError())
+ .build();
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(body);
+ } catch (Exception e) {
+ log.error("Failed to generate secure session with unknown reason [taskId:{}, workerAddress:{}]",
+ taskId, workerAddress, e);
+ final ApiResponseBody body =
+ ApiResponseBody.builder()
+ .error(SECURE_SESSION_GENERATION_FAILED)
+ .build();
+ return ResponseEntity
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(body);
}
}
}
diff --git a/src/main/java/com/iexec/sms/tee/session/TeeSessionGenerationException.java b/src/main/java/com/iexec/sms/tee/session/TeeSessionGenerationException.java
new file mode 100644
index 00000000..04fa9761
--- /dev/null
+++ b/src/main/java/com/iexec/sms/tee/session/TeeSessionGenerationException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.tee.session;
+
+import com.iexec.sms.api.TeeSessionGenerationError;
+
+public class TeeSessionGenerationException extends Exception {
+ private final TeeSessionGenerationError error;
+
+ public TeeSessionGenerationException(TeeSessionGenerationError error, String message) {
+ super(message);
+ this.error = error;
+ }
+
+ public TeeSessionGenerationError getError() {
+ return error;
+ }
+}
diff --git a/src/main/java/com/iexec/sms/tee/session/TeeSessionService.java b/src/main/java/com/iexec/sms/tee/session/TeeSessionService.java
index 019b53bd..22f552ea 100644
--- a/src/main/java/com/iexec/sms/tee/session/TeeSessionService.java
+++ b/src/main/java/com/iexec/sms/tee/session/TeeSessionService.java
@@ -26,7 +26,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
-import static java.util.Objects.requireNonNull;
+import static com.iexec.sms.api.TeeSessionGenerationError.*;
@Slf4j
@Service
@@ -52,12 +52,16 @@ public TeeSessionService(
public String generateTeeSession(
String taskId,
String workerAddress,
- String teeChallenge) throws Exception {
+ String teeChallenge) throws TeeSessionGenerationException {
String sessionId = createSessionId(taskId);
TaskDescription taskDescription = iexecHubService.getTaskDescription(taskId);
- requireNonNull(taskDescription,
- "Failed to get task description - taskId: " + taskId);
+ if (taskDescription == null) {
+ throw new TeeSessionGenerationException(
+ GET_TASK_DESCRIPTION_FAILED,
+ String.format("Failed to get task description [taskId:%s]", taskId)
+ );
+ }
PalaemonSessionRequest request = PalaemonSessionRequest.builder()
.sessionId(sessionId)
.taskDescription(taskDescription)
@@ -66,8 +70,9 @@ public String generateTeeSession(
.build();
String sessionYmlAsString = palaemonSessionService.getSessionYml(request);
if (sessionYmlAsString.isEmpty()) {
- throw new Exception("Failed to get session yml [taskId:" + taskId + "," +
- " workerAddress:" + workerAddress);
+ throw new TeeSessionGenerationException(
+ GET_SESSION_YML_FAILED,
+ String.format("Failed to get session yml [taskId:%s, workerAddress:%s]", taskId, workerAddress));
}
log.info("Session yml is ready [taskId:{}]", taskId);
if (shouldDisplayDebugSession){
@@ -78,7 +83,13 @@ public String generateTeeSession(
.generateSecureSession(sessionYmlAsString.getBytes())
.getStatusCode()
.is2xxSuccessful();
- return isSessionGenerated ? sessionId : "";
+ if (!isSessionGenerated) {
+ throw new TeeSessionGenerationException(
+ SECURE_SESSION_CAS_CALL_FAILED,
+ String.format("Failed to generate secure session [taskId:%s, workerAddress:%s]", taskId, workerAddress)
+ );
+ }
+ return sessionId;
}
private String createSessionId(String taskId) {
diff --git a/src/main/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionService.java b/src/main/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionService.java
index 7d41c778..d273976e 100644
--- a/src/main/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionService.java
+++ b/src/main/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionService.java
@@ -22,10 +22,15 @@
import com.iexec.common.utils.FileHelper;
import com.iexec.common.utils.IexecEnvUtils;
import com.iexec.sms.secret.Secret;
+import com.iexec.sms.secret.compute.OnChainObjectType;
+import com.iexec.sms.secret.compute.SecretOwnerRole;
+import com.iexec.sms.secret.compute.TeeTaskComputeSecret;
+import com.iexec.sms.secret.compute.TeeTaskComputeSecretService;
import com.iexec.sms.secret.web2.Web2SecretsService;
import com.iexec.sms.secret.web3.Web3SecretService;
import com.iexec.sms.tee.challenge.TeeChallenge;
import com.iexec.sms.tee.challenge.TeeChallengeService;
+import com.iexec.sms.tee.session.TeeSessionGenerationException;
import com.iexec.sms.tee.session.attestation.AttestationSecurityConfig;
import com.iexec.sms.tee.workflow.TeeWorkflowConfiguration;
import com.iexec.sms.utils.EthereumCredentials;
@@ -38,9 +43,9 @@
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
-
import java.io.FileNotFoundException;
import java.io.StringWriter;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -52,7 +57,7 @@
import static com.iexec.common.sms.secret.ReservedSecretKeyName.IEXEC_RESULT_ENCRYPTION_PUBLIC_KEY;
import static com.iexec.common.tee.TeeUtils.booleanToYesNo;
import static com.iexec.common.worker.result.ResultUtils.*;
-import static java.util.Objects.requireNonNull;
+import static com.iexec.sms.api.TeeSessionGenerationError.*;
@Slf4j
@Service
@@ -77,6 +82,8 @@ public class PalaemonSessionService {
// PostCompute
static final String POST_COMPUTE_MRENCLAVE = "POST_COMPUTE_MRENCLAVE";
static final String POST_COMPUTE_ENTRYPOINT = "POST_COMPUTE_ENTRYPOINT";
+ // Secrets
+ static final String REQUESTER_SECRETS = "REQUESTER_SECRETS";
// Env
private static final String ENV_PROPERTY = "env";
@@ -85,6 +92,7 @@ public class PalaemonSessionService {
private final TeeChallengeService teeChallengeService;
private final TeeWorkflowConfiguration teeWorkflowConfig;
private final AttestationSecurityConfig attestationSecurityConfig;
+ private final TeeTaskComputeSecretService teeTaskComputeSecretService;
@Value("${scone.cas.palaemon}")
private String palaemonTemplateFilePath;
@@ -94,16 +102,18 @@ public PalaemonSessionService(
Web2SecretsService web2SecretsService,
TeeChallengeService teeChallengeService,
TeeWorkflowConfiguration teeWorkflowConfig,
- AttestationSecurityConfig attestationSecurityConfig) {
+ AttestationSecurityConfig attestationSecurityConfig,
+ TeeTaskComputeSecretService teeTaskComputeSecretService) {
this.web3SecretService = web3SecretService;
this.web2SecretsService = web2SecretsService;
this.teeChallengeService = teeChallengeService;
this.teeWorkflowConfig = teeWorkflowConfig;
this.attestationSecurityConfig = attestationSecurityConfig;
+ this.teeTaskComputeSecretService = teeTaskComputeSecretService;
}
@PostConstruct
- void postConstruct() throws Exception {
+ void postConstruct() throws FileNotFoundException {
if (StringUtils.isEmpty(palaemonTemplateFilePath)) {
throw new IllegalArgumentException("Missing palaemon template filepath");
}
@@ -119,14 +129,24 @@ void postConstruct() throws Exception {
* TODO: Read onchain available infos from enclave instead of copying
* public vars to palaemon.yml. It needs ssl call from enclave to eth
* node (only ethereum node address required inside palaemon.yml)
- *
+ *
* @param request session request details
* @return session config in yaml string format
- * @throws Exception
*/
- public String getSessionYml(PalaemonSessionRequest request) throws Exception {
- requireNonNull(request, "Session request must not be null");
- requireNonNull(request.getTaskDescription(), "Task description must not be null");
+ public String getSessionYml(PalaemonSessionRequest request) throws TeeSessionGenerationException {
+ if (request == null) {
+ throw new TeeSessionGenerationException(
+ NO_SESSION_REQUEST,
+ "Session request must not be null"
+ );
+ }
+ if (request.getTaskDescription() == null) {
+ throw new TeeSessionGenerationException(
+ NO_TASK_DESCRIPTION,
+ "Task description must not be null"
+ );
+ }
+
TaskDescription taskDescription = request.getTaskDescription();
Map palaemonTokens = new HashMap<>();
palaemonTokens.put(SESSION_ID, request.getSessionId());
@@ -159,13 +179,12 @@ public String getSessionYml(PalaemonSessionRequest request) throws Exception {
/**
* Get tokens to be injected in the pre-compute enclave.
- *
- * @param request
+ *
* @return map of pre-compute tokens
- * @throws Exception if dataset secret is not found.
+ * @throws TeeSessionGenerationException if dataset secret is not found.
*/
Map getPreComputePalaemonTokens(PalaemonSessionRequest request)
- throws Exception {
+ throws TeeSessionGenerationException {
TaskDescription taskDescription = request.getTaskDescription();
String taskId = taskDescription.getChainTaskId();
Map tokens = new HashMap<>();
@@ -178,7 +197,10 @@ Map getPreComputePalaemonTokens(PalaemonSessionRequest request)
if (taskDescription.containsDataset()) {
String datasetKey = web3SecretService
.getSecret(taskDescription.getDatasetAddress(), true)
- .orElseThrow(() -> new Exception("Empty dataset secret - taskId: " + taskId))
+ .orElseThrow(() -> new TeeSessionGenerationException(
+ PRE_COMPUTE_GET_DATASET_SECRET_FAILED,
+ "Empty dataset secret - taskId: " + taskId
+ ))
.getTrimmedValue();
tokens.put(IEXEC_DATASET_KEY, datasetKey);
} else {
@@ -190,7 +212,7 @@ Map getPreComputePalaemonTokens(PalaemonSessionRequest request)
.entrySet()
.stream()
.filter(e -> e.getKey().contains(IexecEnvUtils.IEXEC_INPUT_FILE_URL_PREFIX))
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
tokens.put(INPUT_FILE_URLS, inputFileUrls);
return tokens;
}
@@ -199,16 +221,31 @@ Map getPreComputePalaemonTokens(PalaemonSessionRequest request)
* Compute (App)
*/
Map getAppPalaemonTokens(PalaemonSessionRequest request)
- throws Exception {
+ throws TeeSessionGenerationException{
TaskDescription taskDescription = request.getTaskDescription();
- requireNonNull(taskDescription, "Task description must no be null");
+ if (taskDescription == null) {
+ throw new TeeSessionGenerationException(
+ NO_TASK_DESCRIPTION,
+ "Task description must no be null"
+ );
+ }
+
Map tokens = new HashMap<>();
TeeEnclaveConfiguration enclaveConfig = taskDescription.getAppEnclaveConfiguration();
- requireNonNull(enclaveConfig, "Enclave configuration must no be null");
+ if (enclaveConfig == null) {
+ throw new TeeSessionGenerationException(
+ APP_COMPUTE_NO_ENCLAVE_CONFIG,
+ "Enclave configuration must no be null"
+ );
+ }
if (!enclaveConfig.getValidator().isValid()){
- throw new IllegalArgumentException("Invalid enclave configuration: " +
- enclaveConfig.getValidator().validate().toString());
+ throw new TeeSessionGenerationException(
+ APP_COMPUTE_INVALID_ENCLAVE_CONFIG,
+ "Invalid enclave configuration: " +
+ enclaveConfig.getValidator().validate().toString()
+ );
}
+
tokens.put(APP_MRENCLAVE, enclaveConfig.getFingerprint());
String appArgs = enclaveConfig.getEntrypoint();
if (!StringUtils.isEmpty(taskDescription.getCmd())) {
@@ -221,8 +258,64 @@ Map getAppPalaemonTokens(PalaemonSessionRequest request)
.entrySet()
.stream()
.filter(e -> e.getKey().contains(IexecEnvUtils.IEXEC_INPUT_FILE_NAME_PREFIX))
- .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
tokens.put(INPUT_FILE_NAMES, inputFileNames);
+
+ final Map computeSecrets = getApplicationComputeSecrets(taskDescription);
+ tokens.putAll(computeSecrets);
+
+ return tokens;
+ }
+
+ private Map getApplicationComputeSecrets(TaskDescription taskDescription) {
+ final Map tokens = new HashMap<>();
+ final String applicationAddress = taskDescription.getAppAddress();
+
+ if (applicationAddress != null) {
+ final String secretIndex = "1";
+ String appDeveloperSecret =
+ teeTaskComputeSecretService.getSecret(
+ OnChainObjectType.APPLICATION,
+ applicationAddress.toLowerCase(),
+ SecretOwnerRole.APPLICATION_DEVELOPER,
+ "",
+ secretIndex)
+ .map(TeeTaskComputeSecret::getValue)
+ .orElse(EMPTY_YML_VALUE);
+ tokens.put(IexecEnvUtils.IEXEC_APP_DEVELOPER_SECRET_PREFIX + secretIndex, appDeveloperSecret);
+ }
+
+ if (taskDescription.getSecrets() == null || taskDescription.getRequester() == null) {
+ tokens.put(REQUESTER_SECRETS, Collections.emptyMap());
+ return tokens;
+ }
+
+ final HashMap requesterSecrets = new HashMap<>();
+ for (Map.Entry secretEntry: taskDescription.getSecrets().entrySet()) {
+ try {
+ int requesterSecretIndex = Integer.parseInt(secretEntry.getKey());
+ if (requesterSecretIndex <= 0) {
+ String message = "Application secret indices provided in the deal parameters must be positive numbers"
+ + " [providedApplicationSecretIndex:" + requesterSecretIndex + "]";
+ log.warn(message);
+ throw new NumberFormatException(message);
+ }
+ } catch(NumberFormatException e) {
+ log.warn("Invalid entry found in deal parameters secrets map", e);
+ continue;
+ }
+ String requesterSecret = teeTaskComputeSecretService.getSecret(
+ OnChainObjectType.APPLICATION,
+ "",
+ SecretOwnerRole.REQUESTER,
+ taskDescription.getRequester().toLowerCase(),
+ secretEntry.getValue())
+ .map(TeeTaskComputeSecret::getValue)
+ .orElse(EMPTY_YML_VALUE);
+ requesterSecrets.put(IexecEnvUtils.IEXEC_REQUESTER_SECRET_PREFIX + secretEntry.getKey(), requesterSecret);
+ }
+ tokens.put(REQUESTER_SECRETS, requesterSecrets);
+
return tokens;
}
@@ -230,10 +323,12 @@ Map getAppPalaemonTokens(PalaemonSessionRequest request)
* Post-Compute (Result)
*/
Map getPostComputePalaemonTokens(PalaemonSessionRequest request)
- throws Exception {
+ throws TeeSessionGenerationException {
TaskDescription taskDescription = request.getTaskDescription();
- requireNonNull(taskDescription, "Task description must no be null");
- String taskId = taskDescription.getChainTaskId();
+ if (taskDescription == null) {
+ throw new TeeSessionGenerationException(NO_TASK_DESCRIPTION, "Task description must not be null");
+ }
+
Map tokens = new HashMap<>();
String teePostComputeFingerprint = teeWorkflowConfig.getPostComputeFingerprint();
// ###############################################################################
@@ -250,27 +345,18 @@ Map getPostComputePalaemonTokens(PalaemonSessionRequest request)
tokens.put(POST_COMPUTE_ENTRYPOINT, entrypoint);
// encryption
Map encryptionTokens = getPostComputeEncryptionTokens(request);
- if (encryptionTokens.isEmpty()) {
- throw new Exception("Failed to get post-compute encryption tokens - taskId: " + taskId);
- }
tokens.putAll(encryptionTokens);
// storage
Map storageTokens = getPostComputeStorageTokens(request);
- if (storageTokens.isEmpty()) {
- throw new Exception("Failed to get post-compute storage tokens - taskId: " + taskId);
- }
tokens.putAll(storageTokens);
// enclave signature
Map signTokens = getPostComputeSignTokens(request);
- if (signTokens.isEmpty()) {
- throw new Exception("Failed to get post-compute signature tokens - taskId: " + taskId);
- }
tokens.putAll(signTokens);
return tokens;
}
Map getPostComputeEncryptionTokens(PalaemonSessionRequest request)
- throws Exception {
+ throws TeeSessionGenerationException {
TaskDescription taskDescription = request.getTaskDescription();
String taskId = taskDescription.getChainTaskId();
Map tokens = new HashMap<>();
@@ -286,7 +372,10 @@ Map getPostComputeEncryptionTokens(PalaemonSessionRequest reques
IEXEC_RESULT_ENCRYPTION_PUBLIC_KEY,
true);
if (beneficiaryResultEncryptionKeySecret.isEmpty()) {
- throw new Exception("Empty beneficiary encryption key - taskId: " + taskId);
+ throw new TeeSessionGenerationException(
+ POST_COMPUTE_GET_ENCRYPTION_TOKENS_FAILED_EMPTY_BENEFICIARY_KEY,
+ "Empty beneficiary encryption key - taskId: " + taskId
+ );
}
String publicKeyValue = beneficiaryResultEncryptionKeySecret.get().getTrimmedValue();
tokens.put(RESULT_ENCRYPTION_PUBLIC_KEY, publicKeyValue); // base64 encoded by client
@@ -298,7 +387,7 @@ Map getPostComputeEncryptionTokens(PalaemonSessionRequest reques
// that feature we only allow to push to the requester
// private storage space
Map getPostComputeStorageTokens(PalaemonSessionRequest request)
- throws Exception {
+ throws TeeSessionGenerationException {
TaskDescription taskDescription = request.getTaskDescription();
String taskId = taskDescription.getChainTaskId();
Map tokens = new HashMap<>();
@@ -315,12 +404,14 @@ Map getPostComputeStorageTokens(PalaemonSessionRequest request)
String keyName = storageProvider.equals(DROPBOX_RESULT_STORAGE_PROVIDER)
? ReservedSecretKeyName.IEXEC_RESULT_DROPBOX_TOKEN
: ReservedSecretKeyName.IEXEC_RESULT_IEXEC_IPFS_TOKEN;
- Optional requesterStorageTokenSecret =
+ Optional requesterStorageTokenSecret =
web2SecretsService.getSecret(taskDescription.getRequester(), keyName, true);
if (requesterStorageTokenSecret.isEmpty()) {
log.error("Failed to get storage token [taskId:{}, storageProvider:{}, requester:{}]",
taskId, storageProvider, taskDescription.getRequester());
- throw new Exception("Empty requester storage token - taskId: " + taskId);
+ throw new TeeSessionGenerationException(
+ POST_COMPUTE_GET_STORAGE_TOKENS_FAILED,
+ "Empty requester storage token - taskId: " + taskId);
}
String requesterStorageToken = requesterStorageTokenSecret.get().getTrimmedValue();
tokens.put(RESULT_STORAGE_PROVIDER, storageProvider);
@@ -330,23 +421,35 @@ Map getPostComputeStorageTokens(PalaemonSessionRequest request)
}
Map getPostComputeSignTokens(PalaemonSessionRequest request)
- throws Exception {
+ throws TeeSessionGenerationException {
String taskId = request.getTaskDescription().getChainTaskId();
String workerAddress = request.getWorkerAddress();
Map tokens = new HashMap<>();
if (StringUtils.isEmpty(workerAddress)) {
- throw new Exception("Empty worker address - taskId: " + taskId);
+ throw new TeeSessionGenerationException(
+ POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_WORKER_ADDRESS,
+ "Empty worker address - taskId: " + taskId
+ );
}
if (StringUtils.isEmpty(request.getEnclaveChallenge())) {
- throw new Exception("Empty public enclave challenge - taskId: " + taskId);
+ throw new TeeSessionGenerationException(
+ POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_PUBLIC_ENCLAVE_CHALLENGE,
+ "Empty public enclave challenge - taskId: " + taskId
+ );
}
Optional teeChallenge = teeChallengeService.getOrCreate(taskId, true);
if (teeChallenge.isEmpty()) {
- throw new Exception("Empty TEE challenge - taskId: " + taskId);
+ throw new TeeSessionGenerationException(
+ POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_TEE_CHALLENGE,
+ "Empty TEE challenge - taskId: " + taskId
+ );
}
EthereumCredentials enclaveCredentials = teeChallenge.get().getCredentials();
if (enclaveCredentials == null || enclaveCredentials.getPrivateKey().isEmpty()) {
- throw new Exception("Empty TEE challenge credentials - taskId: " + taskId);
+ throw new TeeSessionGenerationException(
+ POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_TEE_CREDENTIALS,
+ "Empty TEE challenge credentials - taskId: " + taskId
+ );
}
tokens.put(RESULT_TASK_ID, taskId);
tokens.put(RESULT_SIGN_WORKER_ADDRESS, workerAddress);
diff --git a/src/main/java/com/iexec/sms/untee/secret/UnTeeSecretService.java b/src/main/java/com/iexec/sms/untee/secret/UnTeeSecretService.java
index 62e558f5..794872f7 100644
--- a/src/main/java/com/iexec/sms/untee/secret/UnTeeSecretService.java
+++ b/src/main/java/com/iexec/sms/untee/secret/UnTeeSecretService.java
@@ -60,13 +60,13 @@ public Optional getUnTeeTaskSecrets(String chainTaskId) {
// TODO use taskDescription instead of chainDeal
Optional oChainTask = iexecHubService.getChainTask(chainTaskId);
- if (!oChainTask.isPresent()) {
+ if (oChainTask.isEmpty()) {
log.error("getUnTeeTaskSecrets failed (getChainTask failed) [chainTaskId:{}]", chainTaskId);
return Optional.empty();
}
ChainTask chainTask = oChainTask.get();
Optional oChainDeal = iexecHubService.getChainDeal(chainTask.getDealid());
- if (!oChainDeal.isPresent()) {
+ if (oChainDeal.isEmpty()) {
log.error("getUnTeeTaskSecrets failed (getChainDeal failed) [chainTaskId:{}]", chainTaskId);
return Optional.empty();
}
@@ -74,7 +74,7 @@ public Optional getUnTeeTaskSecrets(String chainTaskId) {
String chainDatasetId = chainDeal.getChainDataset().getChainDatasetId();
Optional datasetSecret = web3SecretService.getSecret(chainDatasetId, true);
- if (!datasetSecret.isPresent()) {
+ if (datasetSecret.isEmpty()) {
log.error("getUnTeeTaskSecrets failed (datasetSecret failed) [chainTaskId:{}]", chainTaskId);
return Optional.empty();
}
diff --git a/src/main/java/com/iexec/sms/utils/version/VersionController.java b/src/main/java/com/iexec/sms/utils/version/VersionController.java
index cfde5fcd..d27e650a 100644
--- a/src/main/java/com/iexec/sms/utils/version/VersionController.java
+++ b/src/main/java/com/iexec/sms/utils/version/VersionController.java
@@ -20,11 +20,10 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
-
@RestController
public class VersionController {
- private VersionService versionService;
+ private final VersionService versionService;
public VersionController(VersionService versionService) {
this.versionService = versionService;
diff --git a/src/main/java/com/iexec/sms/utils/version/VersionService.java b/src/main/java/com/iexec/sms/utils/version/VersionService.java
index 2171afe6..574e2f27 100644
--- a/src/main/java/com/iexec/sms/utils/version/VersionService.java
+++ b/src/main/java/com/iexec/sms/utils/version/VersionService.java
@@ -17,19 +17,24 @@
package com.iexec.sms.utils.version;
import com.iexec.common.utils.VersionUtils;
+import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Service;
@Service
public class VersionService {
- private String version = Version.PROJECT_VERSION;
+ private final BuildProperties buildProperties;
+
+ VersionService(BuildProperties buildProperties) {
+ this.buildProperties = buildProperties;
+ }
public String getVersion() {
- return version;
+ return buildProperties.getVersion();
}
public boolean isSnapshot() {
- return VersionUtils.isSnapshot(version);
+ return VersionUtils.isSnapshot(buildProperties.getVersion());
}
}
diff --git a/src/main/resources/Dockerfile b/src/main/resources/Dockerfile
index a9ddca04..7327ed5c 100644
--- a/src/main/resources/Dockerfile
+++ b/src/main/resources/Dockerfile
@@ -1,10 +1,12 @@
FROM nexus.iex.ec/sconecuratedimages-apps-java:jdk-alpine-scone3.0
-ARG JAR_NAME
+ARG jar
-COPY build/libs/$JAR_NAME /app/iexec-sms.jar
-COPY build/resources/main/palaemonTemplate.vm /app/palaemonTemplate.vm
-COPY src/main/resources/ssl-keystore-dev.p12 /app/ssl-keystore-dev.p12
+RUN test -n "$jar"
+
+COPY $jar /app/iexec-sms.jar
+COPY build/resources/main/palaemonTemplate.vm /app/palaemonTemplate.vm
+COPY src/main/resources/ssl-keystore-dev.p12 /app/ssl-keystore-dev.p12
# #### Docker ENV vars should be placed in palaemon conf for Scone ###
diff --git a/src/main/resources/Dockerfile.untrusted b/src/main/resources/Dockerfile.untrusted
index fc331c03..76063921 100644
--- a/src/main/resources/Dockerfile.untrusted
+++ b/src/main/resources/Dockerfile.untrusted
@@ -1,8 +1,10 @@
-FROM openjdk:11.0.7-jre-slim
+FROM openjdk:11.0.15-jre-slim
-ARG JAR_NAME
+ARG jar
-COPY build/libs/$JAR_NAME /app/iexec-sms.jar
+RUN test -n "$jar"
+
+COPY $jar /app/iexec-sms.jar
COPY build/resources/main/palaemonTemplate.vm /app/palaemonTemplate.vm
COPY src/main/resources/ssl-keystore-dev.p12 /app/ssl-keystore-dev.p12
@@ -13,4 +15,4 @@ ENV IEXEC_SMS_BLOCKCHAIN_NODE_ADDRESS=http://chain:8545
ENV IEXEC_SCONE_CAS_HOST=iexec-cas
ENV IEXEC_SMS_H2_URL=jdbc:h2:file:/scone/sms-h2
-ENTRYPOINT [ "/bin/sh", "-c", "java -jar /app/iexec-sms.jar" ]
\ No newline at end of file
+ENTRYPOINT [ "/bin/sh", "-c", "java -jar /app/iexec-sms.jar" ]
diff --git a/src/main/resources/Version.java.template b/src/main/resources/Version.java.template
deleted file mode 100644
index 28ee11e7..00000000
--- a/src/main/resources/Version.java.template
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.iexec.sms.utils.version;
-
-class Version {
- static final String PROJECT_VERSION = "@projectversion@";
-}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index b52a5d65..610ac599 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -79,3 +79,7 @@ logging:
# level:
# org.springframework: DEBUG
# org.apache.http: DEBUG
+
+springdoc:
+ packagesToScan: com.iexec.sms
+ pathsToMatch: /**
\ No newline at end of file
diff --git a/src/main/resources/palaemonTemplate.vm b/src/main/resources/palaemonTemplate.vm
index 175f4740..645c75fe 100644
--- a/src/main/resources/palaemonTemplate.vm
+++ b/src/main/resources/palaemonTemplate.vm
@@ -73,6 +73,11 @@ services:
#foreach($key in $INPUT_FILE_NAMES.keySet())
$key: '$INPUT_FILE_NAMES.get($key)'
#end
+ IEXEC_APP_DEVELOPER_SECRET: '$IEXEC_APP_DEVELOPER_SECRET_1'
+ IEXEC_APP_DEVELOPER_SECRET_1: '$IEXEC_APP_DEVELOPER_SECRET_1'
+ #foreach($key in $REQUESTER_SECRETS.keySet())
+ $key: '$REQUESTER_SECRETS.get($key)'
+ #end
#*
Post-compute enclave
diff --git a/src/test/java/com/iexec/sms/Web3jUtils.java b/src/test/java/com/iexec/sms/Web3jUtils.java
new file mode 100644
index 00000000..3757a53c
--- /dev/null
+++ b/src/test/java/com/iexec/sms/Web3jUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms;
+
+import org.web3j.crypto.Credentials;
+import org.web3j.crypto.ECKeyPair;
+import org.web3j.crypto.Keys;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+public class Web3jUtils {
+
+ private Web3jUtils() {}
+
+ /**
+ * Create an Ethereum address after generating a new ECKeyPair.
+ * @return A valid Ethereum address
+ */
+ public static String createEthereumAddress() {
+ try {
+ ECKeyPair ecKeyPair = Keys.createEcKeyPair();
+ return Credentials.create(ecKeyPair).getAddress();
+ } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchProviderException e) {
+ throw new IllegalStateException("Failed to create ECKeyPair and to return a valid Ethereum address", e);
+ }
+ }
+
+}
diff --git a/src/test/java/com/iexec/sms/authorization/AuthorizationServiceTests.java b/src/test/java/com/iexec/sms/authorization/AuthorizationServiceTests.java
index 54bf020e..67322d33 100644
--- a/src/test/java/com/iexec/sms/authorization/AuthorizationServiceTests.java
+++ b/src/test/java/com/iexec/sms/authorization/AuthorizationServiceTests.java
@@ -18,6 +18,7 @@
import static com.iexec.common.chain.ChainTaskStatus.ACTIVE;
import static com.iexec.common.chain.ChainTaskStatus.UNSET;
+import static com.iexec.sms.authorization.AuthorizationError.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@@ -37,7 +38,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-public class AuthorizationServiceTests {
+class AuthorizationServiceTests {
@Mock
IexecHubService iexecHubService;
@@ -46,12 +47,13 @@ public class AuthorizationServiceTests {
private AuthorizationService authorizationService;
@BeforeEach
- public void beforeEach() {
+ void beforeEach() {
MockitoAnnotations.openMocks(this);
}
+ // region isAuthorizedOnExecution
@Test
- public void shouldBeAuthorizedOnExecutionOfTeeTask() {
+ void shouldBeAuthorizedOnExecutionOfTeeTask() {
ChainDeal chainDeal = TestUtils.getChainDeal();
ChainTask chainTask = TestUtils.getChainTask(ACTIVE);
WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
@@ -64,13 +66,13 @@ public void shouldBeAuthorizedOnExecutionOfTeeTask() {
}
@Test
- public void shouldNotBeAuthorizedOnExecutionOfTeeTaskWithNullAuthorization() {
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWithNullAuthorization() {
boolean isAuth = authorizationService.isAuthorizedOnExecution(null, true);
assertThat(isAuth).isFalse();
}
@Test
- public void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenTaskTypeNotMatchedOnchain() {
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenTaskTypeNotMatchedOnchain() {
WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
@@ -80,7 +82,7 @@ public void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenTaskTypeNotMatchedOncha
}
@Test
- public void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenTaskNotActive() {
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenTaskNotActive() {
WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
ChainTask chainTask = TestUtils.getChainTask(UNSET);
when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
@@ -91,7 +93,7 @@ public void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenTaskNotActive() {
}
@Test
- public void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenPoolSignatureIsNotValid() {
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenPoolSignatureIsNotValid() {
ChainDeal chainDeal = TestUtils.getChainDeal();
ChainTask chainTask = TestUtils.getChainTask(ACTIVE);
WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
@@ -104,6 +106,109 @@ public void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenPoolSignatureIsNotValid
boolean isAuth = authorizationService.isAuthorizedOnExecution(auth, true);
assertThat(isAuth).isFalse();
}
+ // endregion
+
+ // region isAuthorizedOnExecutionWithDetailedIssue
+ @Test
+ void shouldBeAuthorizedOnExecutionOfTeeTaskWithDetails() {
+ ChainDeal chainDeal = TestUtils.getChainDeal();
+ ChainTask chainTask = TestUtils.getChainTask(ACTIVE);
+ WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
+ when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
+ when(iexecHubService.getChainTask(auth.getChainTaskId())).thenReturn(Optional.of(chainTask));
+ when(iexecHubService.getChainDeal(chainTask.getDealid())).thenReturn(Optional.of(chainDeal));
+
+ Optional isAuth = authorizationService.isAuthorizedOnExecutionWithDetailedIssue(auth, true);
+ assertThat(isAuth).isEmpty();
+ }
+
+ @Test
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWithNullAuthorizationWithDetails() {
+ Optional isAuth = authorizationService.isAuthorizedOnExecutionWithDetailedIssue(null, true);
+ assertThat(isAuth).isNotEmpty()
+ .get()
+ .isEqualTo(EMPTY_PARAMS_UNAUTHORIZED);
+ }
+
+ @Test
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenTaskTypeNotMatchedOnchainWithDetails() {
+ WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
+ when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
+ when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
+
+ Optional isAuth = authorizationService.isAuthorizedOnExecutionWithDetailedIssue(auth, false);
+ assertThat(isAuth).isNotEmpty()
+ .get()
+ .isEqualTo(NO_MATCH_ONCHAIN_TYPE);
+ }
+
+ @Test
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenGetTaskFailedWithDetails() {
+ WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
+ when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
+ when(iexecHubService.getChainTask(auth.getChainTaskId())).thenReturn(Optional.empty());
+
+ Optional isAuth = authorizationService.isAuthorizedOnExecutionWithDetailedIssue(auth, true);
+ assertThat(isAuth).isNotEmpty()
+ .get()
+ .isEqualTo(GET_CHAIN_TASK_FAILED);
+ }
+
+ @Test
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenTaskNotActiveWithDetails() {
+ WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
+ ChainTask chainTask = TestUtils.getChainTask(UNSET);
+ when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
+ when(iexecHubService.getChainTask(auth.getChainTaskId())).thenReturn(Optional.of(chainTask));
+
+ Optional isAuth = authorizationService.isAuthorizedOnExecutionWithDetailedIssue(auth, true);
+ assertThat(isAuth).isNotEmpty()
+ .get()
+ .isEqualTo(TASK_NOT_ACTIVE);
+ }
+
+ @Test
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenGetDealFailedWithDetails() {
+ ChainDeal chainDeal = TestUtils.getChainDeal();
+ ChainTask chainTask = TestUtils.getChainTask(ACTIVE);
+ WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
+ auth.setSignature(new Signature(TestUtils.POOL_WRONG_SIGNATURE));
+
+ when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
+ when(iexecHubService.getChainTask(auth.getChainTaskId())).thenReturn(Optional.of(chainTask));
+ when(iexecHubService.getChainDeal(chainTask.getDealid())).thenReturn(Optional.empty());
+
+ Optional isAuth = authorizationService.isAuthorizedOnExecutionWithDetailedIssue(auth, true);
+ assertThat(isAuth).isNotEmpty()
+ .get()
+ .isEqualTo(GET_CHAIN_DEAL_FAILED);
+ }
+
+ @Test
+ void shouldNotBeAuthorizedOnExecutionOfTeeTaskWhenPoolSignatureIsNotValidWithDetails() {
+ ChainDeal chainDeal = TestUtils.getChainDeal();
+ ChainTask chainTask = TestUtils.getChainTask(ACTIVE);
+ WorkerpoolAuthorization auth = TestUtils.getTeeWorkerpoolAuth();
+ auth.setSignature(new Signature(TestUtils.POOL_WRONG_SIGNATURE));
+
+ when(iexecHubService.isTeeTask(auth.getChainTaskId())).thenReturn(true);
+ when(iexecHubService.getChainTask(auth.getChainTaskId())).thenReturn(Optional.of(chainTask));
+ when(iexecHubService.getChainDeal(chainTask.getDealid())).thenReturn(Optional.of(chainDeal));
+
+ Optional isAuth = authorizationService.isAuthorizedOnExecutionWithDetailedIssue(auth, true);
+ assertThat(isAuth).isNotEmpty()
+ .get()
+ .isEqualTo(INVALID_SIGNATURE);
+ }
+ // endregion
+
+ @Test
+ void getChallengeForSetRequesterAppComputeSecret() {
+ String challenge = authorizationService.getChallengeForSetRequesterAppComputeSecret(
+ "", "0", "");
+ Assertions.assertEquals("0x31991eefc2731228bdd25dbc5a242722eda3869f9b06536dbd96a774e5228509",
+ challenge);
+ }
@Test
void getChallengeForSetWeb3Secret() {
diff --git a/src/test/java/com/iexec/sms/encryption/EncryptionServiceTests.java b/src/test/java/com/iexec/sms/encryption/EncryptionServiceTests.java
index 6a58b18a..776d6f41 100644
--- a/src/test/java/com/iexec/sms/encryption/EncryptionServiceTests.java
+++ b/src/test/java/com/iexec/sms/encryption/EncryptionServiceTests.java
@@ -25,7 +25,7 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
-public class EncryptionServiceTests {
+class EncryptionServiceTests {
@TempDir
public File tempDir;
@@ -37,7 +37,7 @@ public class EncryptionServiceTests {
private EncryptionService encryptionService;
@Test
- public void shouldCreateAesKey() {
+ void shouldCreateAesKey() {
String data = "data mock";
// File createdFile = new File(tempDir, "aesKey");
String aesKeyPath = tempDir.getAbsolutePath() + "aesKey";
diff --git a/src/test/java/com/iexec/sms/secret/compute/AppComputeSecretControllerTest.java b/src/test/java/com/iexec/sms/secret/compute/AppComputeSecretControllerTest.java
new file mode 100644
index 00000000..65326e01
--- /dev/null
+++ b/src/test/java/com/iexec/sms/secret/compute/AppComputeSecretControllerTest.java
@@ -0,0 +1,506 @@
+package com.iexec.sms.secret.compute;
+
+import com.iexec.common.web.ApiResponseBody;
+import com.iexec.sms.authorization.AuthorizationService;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.iexec.sms.Web3jUtils.createEthereumAddress;
+import static org.mockito.Mockito.*;
+
+class AppComputeSecretControllerTest {
+ private static final String AUTHORIZATION = "authorization";
+ private static final String COMMON_SECRET_VALUE = "I'm a secret.";
+ private static final String EXACT_MAX_SIZE_SECRET_VALUE = new String(new byte[4096]);
+ private static final String TOO_LONG_SECRET_VALUE = new String(new byte[4097]);
+ private static final String CHALLENGE = "challenge";
+ private static final ApiResponseBody> INVALID_AUTHORIZATION_PAYLOAD = createErrorResponse("Invalid authorization");
+
+ @Mock
+ TeeTaskComputeSecretService teeTaskComputeSecretService;
+
+ @Mock
+ AuthorizationService authorizationService;
+
+ @InjectMocks
+ AppComputeSecretController appComputeSecretController;
+
+ @BeforeEach
+ void beforeEach() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ // region addAppDeveloperAppComputeSecret
+
+ @Test
+ void shouldAddAppDeveloperSecret() {
+ final String appAddress = createEthereumAddress();
+ final String secretIndex = "1";
+ final String secretValue = COMMON_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetAppDeveloperAppComputeSecret(appAddress, secretIndex, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByOwner(CHALLENGE, AUTHORIZATION, appAddress))
+ .thenReturn(true);
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex))
+ .thenReturn(false);
+ doReturn(true).when(teeTaskComputeSecretService)
+ .encryptAndSaveSecret(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex, secretValue);
+
+ ResponseEntity>> result = appComputeSecretController.addAppDeveloperAppComputeSecret(
+ AUTHORIZATION,
+ appAddress,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.noContent().build());
+ verify(teeTaskComputeSecretService, times(1))
+ .isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex);
+ verify(teeTaskComputeSecretService, times(1))
+ .encryptAndSaveSecret(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex, secretValue);
+ }
+
+ @Test
+ void shouldNotAddAppDeveloperSecretSinceNotSignedByOwner() {
+ final String appAddress = createEthereumAddress();
+ final String secretIndex = "1";
+ final String secretValue = COMMON_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetAppDeveloperAppComputeSecret(appAddress, secretIndex, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByOwner(CHALLENGE, AUTHORIZATION, appAddress))
+ .thenReturn(false);
+
+
+ ResponseEntity>> result = appComputeSecretController.addAppDeveloperAppComputeSecret(
+ AUTHORIZATION,
+ appAddress,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(INVALID_AUTHORIZATION_PAYLOAD));
+
+ verify(teeTaskComputeSecretService, never())
+ .isSecretPresent(any(), any(), any(), any(), any());
+ verify(teeTaskComputeSecretService, never())
+ .encryptAndSaveSecret(any(), any(), any(), any(), any(), any());
+ }
+
+ @Test
+ void shouldNotAddAppDeveloperSecretSinceSecretAlreadyExists() {
+ final String appAddress = createEthereumAddress();
+ final String secretIndex = "1";
+ final String secretValue = COMMON_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetAppDeveloperAppComputeSecret(appAddress, secretIndex, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByOwner(CHALLENGE, AUTHORIZATION, appAddress))
+ .thenReturn(true);
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex))
+ .thenReturn(true);
+
+ ResponseEntity>> result = appComputeSecretController.addAppDeveloperAppComputeSecret(
+ AUTHORIZATION,
+ appAddress,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.status(HttpStatus.CONFLICT).body(createErrorResponse("Secret already exists")));
+
+ verify(teeTaskComputeSecretService, times(1))
+ .isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex);
+ verify(teeTaskComputeSecretService, never())
+ .encryptAndSaveSecret(any(), any(), any(), any(), any(), any());
+ }
+
+ @Test
+ void shouldNotAddAppDeveloperSecretSinceSecretValueTooLong() {
+ final String appAddress = createEthereumAddress();
+ final String secretIndex = "1";
+ final String secretValue = TOO_LONG_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetAppDeveloperAppComputeSecret(appAddress, secretIndex, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByOwner(CHALLENGE, AUTHORIZATION, appAddress))
+ .thenReturn(true);
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex))
+ .thenReturn(false);
+
+ ResponseEntity>> result = appComputeSecretController.addAppDeveloperAppComputeSecret(
+ AUTHORIZATION,
+ appAddress,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE).body(createErrorResponse("Secret size should not exceed 4 Kb")));
+
+ verifyNoInteractions(authorizationService, teeTaskComputeSecretService);
+ }
+
+ // TODO enable this test when supporting multiple application developer secrets
+ @Test
+ @Disabled
+ void shouldNotAddAppDeveloperSecretSinceBadSecretIndexFormat() {
+ final String appAddress = createEthereumAddress();
+ final String secretIndex = "bad-secret-index";
+ final String secretValue = COMMON_SECRET_VALUE;
+
+ ResponseEntity>> result = appComputeSecretController.addAppDeveloperAppComputeSecret(
+ AUTHORIZATION,
+ appAddress,
+ //secretIndex, // TODO uncomment this when supporting multiple application developer secrets
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.badRequest()
+ .body(createErrorResponse(AppComputeSecretController.INVALID_SECRET_INDEX_FORMAT_MSG)));
+ verifyNoInteractions(authorizationService, teeTaskComputeSecretService);
+ }
+
+ // TODO enable this test when supporting multiple application developer secrets
+ @Test
+ @Disabled
+ void shouldNotAddAppDeveloperSecretSinceBadSecretValue() {
+ final String appAddress = createEthereumAddress();
+ final String secretIndex = "-10";
+ final String secretValue = COMMON_SECRET_VALUE;
+
+ ResponseEntity>> result = appComputeSecretController.addAppDeveloperAppComputeSecret(
+ AUTHORIZATION,
+ appAddress,
+ //secretIndex, // TODO uncomment this when supporting multiple application developer secrets
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.badRequest()
+ .body(createErrorResponse(AppComputeSecretController.INVALID_SECRET_INDEX_FORMAT_MSG)));
+ verifyNoInteractions(authorizationService, teeTaskComputeSecretService);
+ }
+
+ @Test
+ void shouldAddMaxSizeAppDeveloperSecret() {
+ final String appAddress = createEthereumAddress();
+ final String secretIndex = "1";
+ final String secretValue = EXACT_MAX_SIZE_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetAppDeveloperAppComputeSecret(appAddress, secretIndex, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByOwner(CHALLENGE, AUTHORIZATION, appAddress))
+ .thenReturn(true);
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex))
+ .thenReturn(false);
+ doReturn(true).when(teeTaskComputeSecretService)
+ .encryptAndSaveSecret(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex, secretValue);
+
+ ResponseEntity>> result = appComputeSecretController.addAppDeveloperAppComputeSecret(
+ AUTHORIZATION,
+ appAddress,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.noContent().build());
+ verify(teeTaskComputeSecretService, times(1))
+ .isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex);
+ verify(teeTaskComputeSecretService, times(1))
+ .encryptAndSaveSecret(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex, secretValue);
+ }
+
+ // endregion
+
+ // region isAppDeveloperAppComputeSecretPresent
+ @Test
+ void appDeveloperSecretShouldExist() {
+ final String appAddress = createEthereumAddress();
+ final String secretIndex = "1";
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex))
+ .thenReturn(true);
+
+ ResponseEntity>> result =
+ appComputeSecretController.isAppDeveloperAppComputeSecretPresent(appAddress, secretIndex);
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.noContent().build());
+ verify(teeTaskComputeSecretService, times(1))
+ .isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex);
+ verifyNoInteractions(authorizationService);
+ }
+
+ @Test
+ void appDeveloperSecretShouldNotExist() {
+ final String appAddress = createEthereumAddress();
+ final String secretIndex = "1";
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex))
+ .thenReturn(false);
+
+ ResponseEntity>> result =
+ appComputeSecretController.isAppDeveloperAppComputeSecretPresent(appAddress, secretIndex);
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.status(HttpStatus.NOT_FOUND).body(createErrorResponse("Secret not found")));
+ verify(teeTaskComputeSecretService, times(1))
+ .isSecretPresent(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", secretIndex);
+ verifyNoInteractions(authorizationService);
+ }
+
+ @Test
+ void isAppDeveloperAppComputeSecretPresentShouldFailWhenIndexNotANumber() {
+ final String secretIndex = "bad-secret-index";
+ final String appAddress = createEthereumAddress();
+ ResponseEntity>> result =
+ appComputeSecretController.isAppDeveloperAppComputeSecretPresent(appAddress, secretIndex);
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.badRequest()
+ .body(createErrorResponse(AppComputeSecretController.INVALID_SECRET_INDEX_FORMAT_MSG)));
+ verifyNoInteractions(authorizationService, teeTaskComputeSecretService);
+ }
+
+ @Test
+ void isAppDeveloperAppComputeSecretPresentShouldFailWhenIndexLowerThanZero() {
+ final String secretIndex = "-1";
+ final String appAddress = createEthereumAddress();
+ ResponseEntity>> result =
+ appComputeSecretController.isAppDeveloperAppComputeSecretPresent(appAddress, secretIndex);
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.badRequest()
+ .body(createErrorResponse(AppComputeSecretController.INVALID_SECRET_INDEX_FORMAT_MSG)));
+ verifyNoInteractions(authorizationService, teeTaskComputeSecretService);
+ }
+ // endregion
+
+ // region addRequesterAppComputeSecret
+ @Test
+ void shouldAddRequesterSecret() {
+ final String requesterAddress = createEthereumAddress();
+ final String secretKey = "valid-requester-secret";
+ final String secretValue = COMMON_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress))
+ .thenReturn(true);
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey))
+ .thenReturn(false);
+ when(teeTaskComputeSecretService.encryptAndSaveSecret(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey, secretValue))
+ .thenReturn(true);
+
+ ResponseEntity>> result = appComputeSecretController.addRequesterAppComputeSecret(
+ AUTHORIZATION,
+ requesterAddress,
+ secretKey,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.noContent().build());
+ verify(authorizationService)
+ .getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue);
+ verify(authorizationService)
+ .isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress);
+ verify(teeTaskComputeSecretService)
+ .isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey);
+ verify(teeTaskComputeSecretService)
+ .encryptAndSaveSecret(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey, secretValue);
+ }
+
+ @Test
+ void shouldNotAddRequesterSecretSinceNotSignedByRequester() {
+ final String requesterAddress = createEthereumAddress();
+ final String secretKey = "not-signed-secret";
+ final String secretValue = COMMON_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress))
+ .thenReturn(false);
+
+ ResponseEntity>> result = appComputeSecretController.addRequesterAppComputeSecret(
+ AUTHORIZATION,
+ requesterAddress,
+ secretKey,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(INVALID_AUTHORIZATION_PAYLOAD));
+
+ verify(authorizationService)
+ .getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue);
+ verify(authorizationService)
+ .isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress);
+ verifyNoInteractions(teeTaskComputeSecretService);
+ }
+
+ @Test
+ void shouldNotAddRequesterSecretSinceSecretAlreadyExists() {
+ final String requesterAddress = createEthereumAddress();
+ final String secretKey = "secret-already-exists";
+ final String secretValue = COMMON_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress))
+ .thenReturn(true);
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey))
+ .thenReturn(true);
+
+ ResponseEntity>> result = appComputeSecretController.addRequesterAppComputeSecret(
+ AUTHORIZATION,
+ requesterAddress,
+ secretKey,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.status(HttpStatus.CONFLICT).body(createErrorResponse("Secret already exists")));
+
+ verify(authorizationService)
+ .getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue);
+ verify(authorizationService)
+ .isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress);
+ verify(teeTaskComputeSecretService)
+ .isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey);
+ verify(teeTaskComputeSecretService, never())
+ .encryptAndSaveSecret(any(), any(), any(), any(), any(), any());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "this-is-a-really-long-key-with-far-too-many-characters-in-its-name",
+ "this-is-a-key-with-invalid-characters:!*~"
+ })
+ void shouldNotAddRequesterSecretSinceInvalidSecretKey(String secretKey) {
+ final String requesterAddress = createEthereumAddress();
+
+ ResponseEntity>> result = appComputeSecretController.addRequesterAppComputeSecret(
+ AUTHORIZATION,
+ requesterAddress,
+ secretKey,
+ COMMON_SECRET_VALUE
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.badRequest()
+ .body(createErrorResponse(AppComputeSecretController.INVALID_SECRET_KEY_FORMAT_MSG)));
+ verifyNoInteractions(teeTaskComputeSecretService);
+ }
+
+ @Test
+ void shouldNotAddRequesterSecretSinceSecretValueTooLong() {
+ final String requesterAddress = createEthereumAddress();
+ final String secretKey = "too-long-secret-value";
+ final String secretValue = TOO_LONG_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress))
+ .thenReturn(true);
+
+ ResponseEntity>> result = appComputeSecretController.addRequesterAppComputeSecret(
+ AUTHORIZATION,
+ requesterAddress,
+ secretKey,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.badRequest().body(createErrorResponse("Secret size should not exceed 4 Kb")));
+
+ verify(authorizationService)
+ .getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue);
+ verify(authorizationService)
+ .isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress);
+ verifyNoInteractions(teeTaskComputeSecretService);
+ }
+
+ @Test
+ void shouldAddMaxSizeRequesterSecret() {
+ final String requesterAddress = createEthereumAddress();
+ final String secretKey = "max-size-secret-value";
+ final String secretValue = EXACT_MAX_SIZE_SECRET_VALUE;
+
+ when(authorizationService.getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue))
+ .thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress))
+ .thenReturn(true);
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey))
+ .thenReturn(false);
+ when(teeTaskComputeSecretService
+ .encryptAndSaveSecret(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey, secretValue))
+ .thenReturn(true);
+
+ ResponseEntity>> result = appComputeSecretController.addRequesterAppComputeSecret(
+ AUTHORIZATION,
+ requesterAddress,
+ secretKey,
+ secretValue
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.noContent().build());
+ verify(authorizationService)
+ .getChallengeForSetRequesterAppComputeSecret(requesterAddress, secretKey, secretValue);
+ verify(authorizationService)
+ .isSignedByHimself(CHALLENGE, AUTHORIZATION, requesterAddress);
+ verify(teeTaskComputeSecretService)
+ .isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey);
+ verify(teeTaskComputeSecretService)
+ .encryptAndSaveSecret(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey, secretValue);
+ }
+ // endregion
+
+ // region isRequesterAppComputeSecretPresent
+ @Test
+ void requesterSecretShouldExist() {
+ final String requesterAddress = createEthereumAddress();
+ final String secretKey = "exist";
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey))
+ .thenReturn(true);
+
+ ResponseEntity>> result =
+ appComputeSecretController.isRequesterAppComputeSecretPresent(requesterAddress, secretKey);
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.noContent().build());
+ verify(teeTaskComputeSecretService, times(1))
+ .isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey);
+ }
+
+ @Test
+ void requesterSecretShouldNotExist() {
+ final String requesterAddress = createEthereumAddress();
+ final String secretKey = "empty";
+ when(teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey))
+ .thenReturn(false);
+
+ ResponseEntity>> result =
+ appComputeSecretController.isRequesterAppComputeSecretPresent(requesterAddress, secretKey);
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.status(HttpStatus.NOT_FOUND).body(createErrorResponse("Secret not found")));
+ verify(teeTaskComputeSecretService, times(1))
+ .isSecretPresent(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "this-is-a-really-long-key-with-far-too-many-characters-in-its-name",
+ "this-is-a-key-with-invalid-characters:!*~"
+ })
+ void shouldNotReadRequesterSecretSinceInvalidSecretKey(String secretKey) {
+ final String requesterAddress = createEthereumAddress();
+ ResponseEntity>> result = appComputeSecretController.isRequesterAppComputeSecretPresent(
+ requesterAddress,
+ secretKey
+ );
+
+ Assertions.assertThat(result).isEqualTo(ResponseEntity.badRequest()
+ .body(createErrorResponse(AppComputeSecretController.INVALID_SECRET_KEY_FORMAT_MSG)));
+ verifyNoInteractions(teeTaskComputeSecretService);
+ }
+ // endregion
+
+ private static ApiResponseBody> createErrorResponse(String... errorMessages) {
+ return ApiResponseBody.>builder().error(Arrays.asList(errorMessages)).build();
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretServiceTest.java b/src/test/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretServiceTest.java
new file mode 100644
index 00000000..f586952a
--- /dev/null
+++ b/src/test/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretServiceTest.java
@@ -0,0 +1,110 @@
+package com.iexec.sms.secret.compute;
+
+import com.iexec.sms.encryption.EncryptionService;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.*;
+
+import java.util.Optional;
+
+import static org.mockito.Mockito.*;
+
+class TeeTaskComputeSecretServiceTest {
+ private static final String APP_ADDRESS = "appAddress";
+ private static final String DECRYPTED_SECRET_VALUE = "I'm a secret.";
+ private static final String ENCRYPTED_SECRET_VALUE = "I'm an encrypted secret.";
+ private static final TeeTaskComputeSecret COMPUTE_SECRET = TeeTaskComputeSecret
+ .builder()
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .onChainObjectAddress(APP_ADDRESS.toLowerCase())
+ .secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ .key("0")
+ .value(ENCRYPTED_SECRET_VALUE)
+ .build();
+
+ @Mock
+ TeeTaskComputeSecretRepository teeTaskComputeSecretRepository;
+
+ @Mock
+ EncryptionService encryptionService;
+
+ @InjectMocks
+ @Spy
+ TeeTaskComputeSecretService teeTaskComputeSecretService;
+
+ @Captor
+ ArgumentCaptor computeSecretCaptor;
+
+ @BeforeEach
+ void beforeEach() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ // region encryptAndSaveSecret
+ @Test
+ void shouldAddSecret() {
+ doReturn(false).when(teeTaskComputeSecretService)
+ .isSecretPresent(OnChainObjectType.APPLICATION, APP_ADDRESS, SecretOwnerRole.APPLICATION_DEVELOPER, "", "0");
+ when(encryptionService.encrypt(DECRYPTED_SECRET_VALUE))
+ .thenReturn(ENCRYPTED_SECRET_VALUE);
+
+ teeTaskComputeSecretService.encryptAndSaveSecret(OnChainObjectType.APPLICATION, APP_ADDRESS, SecretOwnerRole.APPLICATION_DEVELOPER, "", "0", DECRYPTED_SECRET_VALUE);
+
+ verify(teeTaskComputeSecretRepository, times(1)).save(computeSecretCaptor.capture());
+ final TeeTaskComputeSecret savedTeeTaskComputeSecret = computeSecretCaptor.getValue();
+ Assertions.assertThat(savedTeeTaskComputeSecret.getKey()).isEqualTo("0");
+ Assertions.assertThat(savedTeeTaskComputeSecret.getOnChainObjectAddress()).isEqualTo(APP_ADDRESS.toLowerCase());
+ Assertions.assertThat(savedTeeTaskComputeSecret.getValue()).isEqualTo(ENCRYPTED_SECRET_VALUE);
+ }
+
+ @Test
+ void shouldNotAddSecretSinceAlreadyExist() {
+ doReturn(true).when(teeTaskComputeSecretService)
+ .isSecretPresent(OnChainObjectType.APPLICATION, APP_ADDRESS, SecretOwnerRole.APPLICATION_DEVELOPER, "", "0");
+
+ teeTaskComputeSecretService.encryptAndSaveSecret(OnChainObjectType.APPLICATION, APP_ADDRESS, SecretOwnerRole.APPLICATION_DEVELOPER, "", "0", DECRYPTED_SECRET_VALUE);
+
+ verify(teeTaskComputeSecretRepository, times(0)).save(computeSecretCaptor.capture());
+ }
+ // endregion
+
+ // region getSecret
+ @Test
+ void shouldGetSecret() {
+ when(teeTaskComputeSecretRepository.findOne(any()))
+ .thenReturn(Optional.of(COMPUTE_SECRET));
+ when(encryptionService.decrypt(ENCRYPTED_SECRET_VALUE))
+ .thenReturn(DECRYPTED_SECRET_VALUE);
+
+ Optional decryptedSecret = teeTaskComputeSecretService.getSecret(OnChainObjectType.APPLICATION, APP_ADDRESS, SecretOwnerRole.APPLICATION_DEVELOPER, "", "0");
+ Assertions.assertThat(decryptedSecret).isPresent();
+ Assertions.assertThat(decryptedSecret.get().getKey()).isEqualTo("0");
+ Assertions.assertThat(decryptedSecret.get().getOnChainObjectAddress()).isEqualTo(APP_ADDRESS.toLowerCase());
+ Assertions.assertThat(decryptedSecret.get().getValue()).isEqualTo(DECRYPTED_SECRET_VALUE);
+ verify(encryptionService, Mockito.times(1)).decrypt(any());
+ }
+ // endregion
+
+ // region isSecretPresent
+ @Test
+ void secretShouldExist() {
+ when(teeTaskComputeSecretService.getSecret(OnChainObjectType.APPLICATION, APP_ADDRESS, SecretOwnerRole.APPLICATION_DEVELOPER, "", "0"))
+ .thenReturn(Optional.of(COMPUTE_SECRET));
+
+ final boolean isSecretPresent = teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, APP_ADDRESS, SecretOwnerRole.APPLICATION_DEVELOPER, "", "0");
+
+ Assertions.assertThat(isSecretPresent).isTrue();
+ }
+
+ @Test
+ void secretShouldNotExist() {
+ when(teeTaskComputeSecretService.getSecret(OnChainObjectType.APPLICATION, APP_ADDRESS, SecretOwnerRole.APPLICATION_DEVELOPER, "", "0"))
+ .thenReturn(Optional.empty());
+
+ final boolean isSecretPresent = teeTaskComputeSecretService.isSecretPresent(OnChainObjectType.APPLICATION, APP_ADDRESS, SecretOwnerRole.APPLICATION_DEVELOPER, "", "0");
+
+ Assertions.assertThat(isSecretPresent).isFalse();
+ }
+ // endregion
+}
\ No newline at end of file
diff --git a/src/test/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretTest.java b/src/test/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretTest.java
new file mode 100644
index 00000000..8fff237f
--- /dev/null
+++ b/src/test/java/com/iexec/sms/secret/compute/TeeTaskComputeSecretTest.java
@@ -0,0 +1,170 @@
+package com.iexec.sms.secret.compute;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.dao.DataIntegrityViolationException;
+
+import javax.validation.ConstraintViolationException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+@Slf4j
+@DataJpaTest
+public class TeeTaskComputeSecretTest {
+
+ private final TeeTaskComputeSecretRepository teeTaskComputeSecretRepository;
+
+ TeeTaskComputeSecretTest(@Autowired TeeTaskComputeSecretRepository teeTaskComputeSecretRepository) {
+ this.teeTaskComputeSecretRepository = teeTaskComputeSecretRepository;
+ }
+
+ private TeeTaskComputeSecret getAppDeveloperSecret() {
+ return TeeTaskComputeSecret.builder()
+ .secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ .onChainObjectAddress("0x1")
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .fixedSecretOwner("")
+ .key("0")
+ .value("secretValue")
+ .build();
+ }
+
+ private TeeTaskComputeSecret getRequesterSecret() {
+ return TeeTaskComputeSecret.builder()
+ .secretOwnerRole(SecretOwnerRole.REQUESTER)
+ .onChainObjectAddress("")
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .fixedSecretOwner("0x1")
+ .key("secret-key")
+ .value("secretValue")
+ .build();
+ }
+
+ @Test
+ void shouldNotSaveSecretFromDefaultBuilder() {
+ log.info("shouldNotSaveEntity");
+ TeeTaskComputeSecret secret = TeeTaskComputeSecret.builder().build();
+ assertThat(secret).hasAllNullFieldsOrProperties();
+ assertThatThrownBy(() -> teeTaskComputeSecretRepository.saveAndFlush(secret))
+ .isInstanceOf(ConstraintViolationException.class);
+ }
+
+ @Test
+ void shouldNotSaveSecretWhenOnChainObjectAddressIsNull() {
+ TeeTaskComputeSecret secret = TeeTaskComputeSecret.builder()
+ //.onChainObjectAddress("")
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ .fixedSecretOwner("")
+ .key("")
+ .value("")
+ .build();
+ assertThatThrownBy(() -> teeTaskComputeSecretRepository.saveAndFlush(secret))
+ .isInstanceOf(ConstraintViolationException.class);
+ }
+
+ @Test
+ void shouldNotSaveSecretWhenOnChainObjectTypeIsNull() {
+ TeeTaskComputeSecret secret = TeeTaskComputeSecret.builder()
+ .onChainObjectAddress("")
+ //.onChainObjectType(OnChainObjectType.APPLICATION)
+ .secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ .fixedSecretOwner("")
+ .key("")
+ .value("")
+ .build();
+ assertThatThrownBy(() -> teeTaskComputeSecretRepository.saveAndFlush(secret))
+ .isInstanceOf(ConstraintViolationException.class);
+ }
+
+ @Test
+ void shouldNotSaveSecretWhenSecretOwnerRoleIsNull() {
+ TeeTaskComputeSecret secret = TeeTaskComputeSecret.builder()
+ .onChainObjectAddress("")
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ //.secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ .fixedSecretOwner("")
+ .key("")
+ .value("")
+ .build();
+ assertThatThrownBy(() -> teeTaskComputeSecretRepository.saveAndFlush(secret))
+ .isInstanceOf(ConstraintViolationException.class);
+ }
+
+ @Test
+ void shouldNotSaveSecretWhenFixedSecretOwnerIsNull() {
+ TeeTaskComputeSecret secret = TeeTaskComputeSecret.builder()
+ .onChainObjectAddress("")
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ //.fixedSecretOwner("")
+ .key("")
+ .value("")
+ .build();
+ assertThatThrownBy(() -> teeTaskComputeSecretRepository.saveAndFlush(secret))
+ .isInstanceOf(ConstraintViolationException.class);
+ }
+
+ @Test
+ void shouldNotSaveSecretWhenKeyIsNull() {
+ TeeTaskComputeSecret secret = TeeTaskComputeSecret.builder()
+ .onChainObjectAddress("")
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ .fixedSecretOwner("")
+ //.key("")
+ .value("")
+ .build();
+ assertThatThrownBy(() -> teeTaskComputeSecretRepository.saveAndFlush(secret))
+ .isInstanceOf(ConstraintViolationException.class);
+ }
+
+ @Test
+ void shouldNotSaveSecretWhenValueIsNull() {
+ TeeTaskComputeSecret secret = TeeTaskComputeSecret.builder()
+ .onChainObjectAddress("")
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ .fixedSecretOwner("")
+ .key("")
+ //.value("")
+ .build();
+ assertThatThrownBy(() -> teeTaskComputeSecretRepository.saveAndFlush(secret))
+ .isInstanceOf(ConstraintViolationException.class);
+ }
+
+ @Test
+ void shouldFailToSaveSameAppDeveloperSecretTwice() {
+ log.info("AppDeveloperSecret");
+ teeTaskComputeSecretRepository.saveAndFlush(getAppDeveloperSecret());
+ assertThatThrownBy(() -> teeTaskComputeSecretRepository.saveAndFlush(getAppDeveloperSecret()))
+ .isInstanceOf(DataIntegrityViolationException.class);
+ }
+
+ @Test
+ void shouldFailToSaveSameRequesterSecretTwice() {
+ log.info("RequesterSecret");
+ teeTaskComputeSecretRepository.saveAndFlush(getRequesterSecret());
+ assertThatThrownBy(() -> teeTaskComputeSecretRepository.saveAndFlush(getRequesterSecret()))
+ .isInstanceOf(DataIntegrityViolationException.class);
+ }
+
+ @Test
+ void shouldSaveAppDeveloperAndRequesterSecrets() {
+ TeeTaskComputeSecret appDeveloperSecret = getAppDeveloperSecret();
+ teeTaskComputeSecretRepository.save(appDeveloperSecret);
+ String appDeveloperSecretId = appDeveloperSecret.getId();
+ TeeTaskComputeSecret requesterSecret = getRequesterSecret();
+ teeTaskComputeSecretRepository.save(requesterSecret);
+ String requesterSecretId = requesterSecret.getId();
+ assertThat(teeTaskComputeSecretRepository.count()).isEqualTo(2);
+ assertThat(teeTaskComputeSecretRepository.getById(appDeveloperSecretId))
+ .isEqualTo(appDeveloperSecret);
+ assertThat(teeTaskComputeSecretRepository.getById(requesterSecretId))
+ .isEqualTo(requesterSecret);
+ }
+
+}
diff --git a/src/test/java/com/iexec/sms/secret/web2/Web2SecretsServiceTests.java b/src/test/java/com/iexec/sms/secret/web2/Web2SecretsServiceTests.java
index 0e71fed9..1d17bec1 100644
--- a/src/test/java/com/iexec/sms/secret/web2/Web2SecretsServiceTests.java
+++ b/src/test/java/com/iexec/sms/secret/web2/Web2SecretsServiceTests.java
@@ -28,10 +28,9 @@
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
-public class Web2SecretsServiceTests {
+class Web2SecretsServiceTests {
String ownerAddress = "ownerAddress";
String secretAddress = "secretAddress";
@@ -48,12 +47,12 @@ public class Web2SecretsServiceTests {
private Web2SecretsService web2SecretsService;
@BeforeEach
- public void beforeEach() {
+ void beforeEach() {
MockitoAnnotations.openMocks(this);
}
@Test
- public void shouldGetAndDecryptWeb2Secrets() {
+ void shouldGetAndDecryptWeb2Secrets() {
ownerAddress = ownerAddress.toLowerCase();
Secret encryptedSecret = new Secret(secretAddress, encryptedSecretValue);
encryptedSecret.setEncryptedValue(true);
@@ -71,14 +70,14 @@ public void shouldGetAndDecryptWeb2Secrets() {
}
@Test
- public void shouldAddSecret() {
+ void shouldAddSecret() {
ownerAddress = ownerAddress.toLowerCase();
web2SecretsService.addSecret(ownerAddress, secretAddress, plainSecretValue);
verify(web2SecretsRepository, times(1)).save(any());
}
@Test
- public void shouldUpdateSecret() {
+ void shouldUpdateSecret() {
ownerAddress = ownerAddress.toLowerCase();
Secret encryptedSecret = new Secret(secretAddress, encryptedSecretValue);
encryptedSecret.setEncryptedValue(true);
diff --git a/src/test/java/com/iexec/sms/secret/web3/Web3SecretsServiceTests.java b/src/test/java/com/iexec/sms/secret/web3/Web3SecretsServiceTests.java
index cf9114de..15135ea0 100644
--- a/src/test/java/com/iexec/sms/secret/web3/Web3SecretsServiceTests.java
+++ b/src/test/java/com/iexec/sms/secret/web3/Web3SecretsServiceTests.java
@@ -16,6 +16,6 @@
package com.iexec.sms.secret.web3;
-public class Web3SecretsServiceTests {
+class Web3SecretsServiceTests {
}
\ No newline at end of file
diff --git a/src/test/java/com/iexec/sms/tee/TeeControllerTests.java b/src/test/java/com/iexec/sms/tee/TeeControllerTests.java
new file mode 100644
index 00000000..2b2d09d6
--- /dev/null
+++ b/src/test/java/com/iexec/sms/tee/TeeControllerTests.java
@@ -0,0 +1,197 @@
+package com.iexec.sms.tee;
+
+import com.iexec.common.chain.WorkerpoolAuthorization;
+import com.iexec.sms.api.TeeSessionGenerationError;
+import com.iexec.common.web.ApiResponseBody;
+import com.iexec.sms.authorization.AuthorizationError;
+import com.iexec.sms.authorization.AuthorizationService;
+import com.iexec.sms.tee.challenge.TeeChallengeService;
+import com.iexec.sms.tee.session.TeeSessionGenerationException;
+import com.iexec.sms.tee.session.TeeSessionService;
+import com.iexec.sms.tee.workflow.TeeWorkflowConfiguration;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.web3j.crypto.Keys;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import static com.iexec.sms.api.TeeSessionGenerationError.*;
+import static com.iexec.sms.authorization.AuthorizationError.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
+
+class TeeControllerTests {
+ private final static String TASK_ID = "0x0";
+ private final static String WORKER_ADDRESS = "0x1";
+ private final static String ENCLAVE_CHALLENGE = "0x2";
+ private final static String AUTHORIZATION = "0x2";
+ private final static String CHALLENGE = "CHALLENGE";
+ private final static String SESSION_ID = "SESSION_ID";
+
+ @Mock
+ AuthorizationService authorizationService;
+ @Mock
+ TeeChallengeService teeChallengeService;
+ @Mock
+ TeeSessionService teeSessionService;
+ @Mock
+ TeeWorkflowConfiguration teeWorkflowConfig;
+
+ @InjectMocks
+ TeeController teeController;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ // region generateTeeSession
+ @Test
+ void shouldGenerateTeeSession() throws TeeSessionGenerationException {
+ final WorkerpoolAuthorization workerpoolAuthorization = WorkerpoolAuthorization
+ .builder()
+ .chainTaskId(TASK_ID)
+ .workerWallet(WORKER_ADDRESS)
+ .enclaveChallenge(ENCLAVE_CHALLENGE)
+ .build();
+
+ when(authorizationService.getChallengeForWorker(workerpoolAuthorization)).thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, WORKER_ADDRESS)).thenReturn(true);
+ when(authorizationService.isAuthorizedOnExecutionWithDetailedIssue(workerpoolAuthorization, true)).thenReturn(Optional.empty());
+ when(teeSessionService.generateTeeSession(TASK_ID, Keys.toChecksumAddress(WORKER_ADDRESS), ENCLAVE_CHALLENGE)).thenReturn(SESSION_ID);
+
+ final ResponseEntity> response =
+ teeController.generateTeeSession(AUTHORIZATION, workerpoolAuthorization);
+ assertEquals(HttpStatus.OK, response.getStatusCode());
+ assertNotEquals(null, response.getBody());
+ assertNotEquals(null, response.getBody().getData());
+ assertNotEquals("", response.getBody().getData());
+ assertNull(response.getBody().getError());
+ }
+
+ @Test
+ void shouldNotGenerateTeeSessionSinceNotSignedByHimself() {
+ final WorkerpoolAuthorization workerpoolAuthorization = WorkerpoolAuthorization
+ .builder()
+ .chainTaskId(TASK_ID)
+ .workerWallet(WORKER_ADDRESS)
+ .enclaveChallenge(ENCLAVE_CHALLENGE)
+ .build();
+
+ when(authorizationService.getChallengeForWorker(workerpoolAuthorization)).thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, WORKER_ADDRESS)).thenReturn(false);
+
+ final ResponseEntity> response =
+ teeController.generateTeeSession(AUTHORIZATION, workerpoolAuthorization);
+ assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
+ assertNotEquals(null, response.getBody());
+ assertNull(response.getBody().getData());
+ assertNotEquals(null, response.getBody().getError());
+ assertEquals(TeeSessionGenerationError.INVALID_AUTHORIZATION, response.getBody().getError());
+ }
+
+ private static Stream notAuthorizedParams() {
+ return Stream.of(
+ Arguments.of(EMPTY_PARAMS_UNAUTHORIZED, EXECUTION_NOT_AUTHORIZED_EMPTY_PARAMS_UNAUTHORIZED),
+ Arguments.of(NO_MATCH_ONCHAIN_TYPE, EXECUTION_NOT_AUTHORIZED_NO_MATCH_ONCHAIN_TYPE),
+ Arguments.of(GET_CHAIN_TASK_FAILED, EXECUTION_NOT_AUTHORIZED_GET_CHAIN_TASK_FAILED),
+ Arguments.of(TASK_NOT_ACTIVE, EXECUTION_NOT_AUTHORIZED_TASK_NOT_ACTIVE),
+ Arguments.of(GET_CHAIN_DEAL_FAILED, EXECUTION_NOT_AUTHORIZED_GET_CHAIN_DEAL_FAILED),
+ Arguments.of(INVALID_SIGNATURE, EXECUTION_NOT_AUTHORIZED_INVALID_SIGNATURE)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("notAuthorizedParams")
+ void shouldNotGenerateTeeSessionSinceNotAuthorized(AuthorizationError cause, TeeSessionGenerationError consequence) {
+ final WorkerpoolAuthorization workerpoolAuthorization = WorkerpoolAuthorization
+ .builder()
+ .chainTaskId(TASK_ID)
+ .workerWallet(WORKER_ADDRESS)
+ .enclaveChallenge(ENCLAVE_CHALLENGE)
+ .build();
+
+ when(authorizationService.getChallengeForWorker(workerpoolAuthorization)).thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, WORKER_ADDRESS)).thenReturn(true);
+ when(authorizationService.isAuthorizedOnExecutionWithDetailedIssue(workerpoolAuthorization, true)).thenReturn(Optional.of(cause));
+
+ final ResponseEntity> response =
+ teeController.generateTeeSession(AUTHORIZATION, workerpoolAuthorization);
+ assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
+ assertNotEquals(null, response.getBody());
+ assertNull(response.getBody().getData());
+ assertNotEquals(null, response.getBody().getError());
+ assertEquals(consequence, response.getBody().getError());
+ }
+
+ @Test
+ void shouldNotGenerateTeeSessionSinceEmptySessionId() throws TeeSessionGenerationException {
+ final WorkerpoolAuthorization workerpoolAuthorization = WorkerpoolAuthorization
+ .builder()
+ .chainTaskId(TASK_ID)
+ .workerWallet(WORKER_ADDRESS)
+ .enclaveChallenge(ENCLAVE_CHALLENGE)
+ .build();
+
+ when(authorizationService.getChallengeForWorker(workerpoolAuthorization)).thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, WORKER_ADDRESS)).thenReturn(true);
+ when(authorizationService.isAuthorizedOnExecutionWithDetailedIssue(workerpoolAuthorization, true)).thenReturn(Optional.empty());
+ when(teeSessionService.generateTeeSession(TASK_ID, Keys.toChecksumAddress(WORKER_ADDRESS), ENCLAVE_CHALLENGE)).thenReturn("");
+
+ final ResponseEntity> response =
+ teeController.generateTeeSession(AUTHORIZATION, workerpoolAuthorization);
+ assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+ assertNull(response.getBody());
+ }
+
+ private static Stream exceptionOnSessionIdGeneration() {
+ return Stream.of(
+ Arguments.of(
+ new TeeSessionGenerationException(
+ SECURE_SESSION_GENERATION_FAILED,
+ String.format("Failed to generate secure session [taskId:%s, workerAddress:%s]", TASK_ID, WORKER_ADDRESS)
+ )
+ ),
+ Arguments.of(new RuntimeException())
+ );
+ }
+
+ /**
+ * {@link TeeController#generateTeeSession(String, WorkerpoolAuthorization)}
+ * should catch every error thrown
+ * by {@link TeeSessionService#generateTeeSession(String, String, String)}.
+ */
+ @ParameterizedTest
+ @MethodSource("exceptionOnSessionIdGeneration")
+ void shouldNotGenerateTeeSessionSinceSessionIdGenerationFailed(Exception exception) throws TeeSessionGenerationException {
+ final WorkerpoolAuthorization workerpoolAuthorization = WorkerpoolAuthorization
+ .builder()
+ .chainTaskId(TASK_ID)
+ .workerWallet(WORKER_ADDRESS)
+ .enclaveChallenge(ENCLAVE_CHALLENGE)
+ .build();
+
+ when(authorizationService.getChallengeForWorker(workerpoolAuthorization)).thenReturn(CHALLENGE);
+ when(authorizationService.isSignedByHimself(CHALLENGE, AUTHORIZATION, WORKER_ADDRESS)).thenReturn(true);
+ when(authorizationService.isAuthorizedOnExecutionWithDetailedIssue(workerpoolAuthorization, true)).thenReturn(Optional.empty());
+ when(teeSessionService.generateTeeSession(TASK_ID, Keys.toChecksumAddress(WORKER_ADDRESS), ENCLAVE_CHALLENGE)).thenThrow(exception);
+
+ final ResponseEntity> response =
+ teeController.generateTeeSession(AUTHORIZATION, workerpoolAuthorization);
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
+ assertNotEquals(null, response.getBody());
+ assertNull(response.getBody().getData());
+ assertNotEquals(null, response.getBody().getError());
+ assertEquals(SECURE_SESSION_GENERATION_FAILED, response.getBody().getError());
+ }
+ // endregion
+}
\ No newline at end of file
diff --git a/src/test/java/com/iexec/sms/tee/challenge/TeeChallengeServiceTests.java b/src/test/java/com/iexec/sms/tee/challenge/TeeChallengeServiceTests.java
index 50f96a84..6cc471b4 100644
--- a/src/test/java/com/iexec/sms/tee/challenge/TeeChallengeServiceTests.java
+++ b/src/test/java/com/iexec/sms/tee/challenge/TeeChallengeServiceTests.java
@@ -34,7 +34,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-public class TeeChallengeServiceTests {
+class TeeChallengeServiceTests {
private final static String TASK_ID = "0x123";
private final static String PLAIN_PRIVATE = "plainPrivate";
@@ -50,7 +50,7 @@ public class TeeChallengeServiceTests {
private TeeChallengeService teeChallengeService;
@BeforeEach
- public void beforeEach() {
+ void beforeEach() {
MockitoAnnotations.openMocks(this);
}
@@ -61,7 +61,7 @@ private TeeChallenge getEncryptedTeeChallengeStub() throws Exception {
}
@Test
- public void shouldGetExistingChallengeWithoutDecryptingKeys() throws Exception {
+ void shouldGetExistingChallengeWithoutDecryptingKeys() throws Exception {
TeeChallenge encryptedTeeChallengeStub = getEncryptedTeeChallengeStub();
when(teeChallengeRepository.findByTaskId(TASK_ID)).thenReturn(Optional.of(encryptedTeeChallengeStub));
@@ -72,7 +72,7 @@ public void shouldGetExistingChallengeWithoutDecryptingKeys() throws Exception {
}
@Test
- public void shouldGetExistingChallengeAndDecryptKeys() throws Exception {
+ void shouldGetExistingChallengeAndDecryptKeys() throws Exception {
TeeChallenge encryptedTeeChallengeStub = getEncryptedTeeChallengeStub();
when(teeChallengeRepository.findByTaskId(TASK_ID)).thenReturn(Optional.of(encryptedTeeChallengeStub));
when(encryptionService.decrypt(anyString())).thenReturn(PLAIN_PRIVATE);
@@ -84,7 +84,7 @@ public void shouldGetExistingChallengeAndDecryptKeys() throws Exception {
}
@Test
- public void shouldCreateNewChallengeWithoutDecryptingKeys() throws Exception {
+ void shouldCreateNewChallengeWithoutDecryptingKeys() throws Exception {
TeeChallenge encryptedTeeChallengeStub = getEncryptedTeeChallengeStub();
when(teeChallengeRepository.findByTaskId(TASK_ID)).thenReturn(Optional.empty());
when(encryptionService.encrypt(anyString())).thenReturn(ENC_PRIVATE);
@@ -97,7 +97,7 @@ public void shouldCreateNewChallengeWithoutDecryptingKeys() throws Exception {
}
@Test
- public void shouldCreateNewChallengeAndDecryptKeys() throws Exception {
+ void shouldCreateNewChallengeAndDecryptKeys() throws Exception {
TeeChallenge encryptedTeeChallengeStub = getEncryptedTeeChallengeStub();
when(teeChallengeRepository.findByTaskId(TASK_ID)).thenReturn(Optional.empty());
when(encryptionService.encrypt(anyString())).thenReturn(ENC_PRIVATE);
@@ -110,7 +110,7 @@ public void shouldCreateNewChallengeAndDecryptKeys() throws Exception {
}
@Test
- public void shouldEncryptChallengeKeys() throws Exception {
+ void shouldEncryptChallengeKeys() throws Exception {
TeeChallenge teeChallenge = new TeeChallenge(TASK_ID);
when(encryptionService.encrypt(anyString())).thenReturn(ENC_PRIVATE);
teeChallengeService.encryptChallengeKeys(teeChallenge);
@@ -119,7 +119,7 @@ public void shouldEncryptChallengeKeys() throws Exception {
}
@Test
- public void shouldDecryptChallengeKeys() throws Exception {
+ void shouldDecryptChallengeKeys() throws Exception {
TeeChallenge teeChallenge = new TeeChallenge(TASK_ID);
teeChallenge.getCredentials().setEncrypted(true);
when(encryptionService.decrypt(anyString())).thenReturn(PLAIN_PRIVATE);
diff --git a/src/test/java/com/iexec/sms/tee/session/TeeSessionServiceTests.java b/src/test/java/com/iexec/sms/tee/session/TeeSessionServiceTests.java
new file mode 100644
index 00000000..30225d59
--- /dev/null
+++ b/src/test/java/com/iexec/sms/tee/session/TeeSessionServiceTests.java
@@ -0,0 +1,87 @@
+package com.iexec.sms.tee.session;
+
+import com.iexec.common.task.TaskDescription;
+import com.iexec.sms.blockchain.IexecHubService;
+import com.iexec.sms.tee.session.cas.CasClient;
+import com.iexec.sms.tee.session.palaemon.PalaemonSessionService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.ResponseEntity;
+
+import static com.iexec.sms.api.TeeSessionGenerationError.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+class TeeSessionServiceTests {
+ private final static String TASK_ID = "0x0";
+ private final static String WORKER_ADDRESS = "0x1";
+ private final static String TEE_CHALLENGE = "0x2";
+
+ @Mock
+ IexecHubService iexecHubService;
+
+ @Mock
+ CasClient casClient;
+
+ @Mock
+ PalaemonSessionService palaemonSessionService;
+
+ TeeSessionService teeSessionService;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ teeSessionService = new TeeSessionService(iexecHubService, palaemonSessionService, casClient, false);
+ }
+
+ @Test
+ void shouldGenerateTeeSession() throws TeeSessionGenerationException {
+ final TaskDescription taskDescription = TaskDescription.builder().chainTaskId(TASK_ID).build();
+ final String sessionYmlAsString = "YML session";
+
+ when(iexecHubService.getTaskDescription(TASK_ID)).thenReturn(taskDescription);
+ when(palaemonSessionService.getSessionYml(any())).thenReturn(sessionYmlAsString);
+ when(casClient.generateSecureSession(sessionYmlAsString.getBytes())).thenReturn(ResponseEntity.ok(null));
+
+ final String teeSession = assertDoesNotThrow(() -> teeSessionService.generateTeeSession(TASK_ID, WORKER_ADDRESS, TEE_CHALLENGE));
+ assertNotNull(teeSession);
+ }
+
+ @Test
+ void shouldNotGenerateTeeSessionSinceCantGetTaskDescription() {
+ when(iexecHubService.getTaskDescription(TASK_ID)).thenReturn(null);
+
+ final TeeSessionGenerationException teeSessionGenerationException = assertThrows(TeeSessionGenerationException.class, () -> teeSessionService.generateTeeSession(TASK_ID, WORKER_ADDRESS, TEE_CHALLENGE));
+ assertEquals(GET_TASK_DESCRIPTION_FAILED, teeSessionGenerationException.getError());
+ assertEquals(String.format("Failed to get task description [taskId:%s]", TASK_ID), teeSessionGenerationException.getMessage());
+ }
+
+ @Test
+ void shouldNotGenerateTeeSessionSinceCantGetSessionYml() throws TeeSessionGenerationException {
+ final TaskDescription taskDescription = TaskDescription.builder().chainTaskId(TASK_ID).build();
+
+ when(iexecHubService.getTaskDescription(TASK_ID)).thenReturn(taskDescription);
+ when(palaemonSessionService.getSessionYml(any())).thenReturn("");
+
+ final TeeSessionGenerationException teeSessionGenerationException = assertThrows(TeeSessionGenerationException.class, () -> teeSessionService.generateTeeSession(TASK_ID, WORKER_ADDRESS, TEE_CHALLENGE));
+ assertEquals(GET_SESSION_YML_FAILED, teeSessionGenerationException.getError());
+ assertEquals(String.format("Failed to get session yml [taskId:%s, workerAddress:%s]", TASK_ID, WORKER_ADDRESS), teeSessionGenerationException.getMessage());
+ }
+
+ @Test
+ void shouldNotGenerateTeeSessionSinceCantGenerateSecureSession() throws TeeSessionGenerationException {
+ final TaskDescription taskDescription = TaskDescription.builder().chainTaskId(TASK_ID).build();
+ final String sessionYmlAsString = "YML session";
+
+ when(iexecHubService.getTaskDescription(TASK_ID)).thenReturn(taskDescription);
+ when(palaemonSessionService.getSessionYml(any())).thenReturn(sessionYmlAsString);
+ when(casClient.generateSecureSession(sessionYmlAsString.getBytes())).thenReturn(ResponseEntity.notFound().build());
+
+ final TeeSessionGenerationException teeSessionGenerationException = assertThrows(TeeSessionGenerationException.class, () -> teeSessionService.generateTeeSession(TASK_ID, WORKER_ADDRESS, TEE_CHALLENGE));
+ assertEquals(SECURE_SESSION_CAS_CALL_FAILED, teeSessionGenerationException.getError());
+ assertEquals(String.format("Failed to generate secure session [taskId:%s, workerAddress:%s]", TASK_ID, WORKER_ADDRESS), teeSessionGenerationException.getMessage());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionServiceTests.java b/src/test/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionServiceTests.java
index 51ffb39a..feb11146 100644
--- a/src/test/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionServiceTests.java
+++ b/src/test/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionServiceTests.java
@@ -24,13 +24,18 @@
import com.iexec.common.utils.FileHelper;
import com.iexec.common.utils.IexecEnvUtils;
import com.iexec.common.worker.result.ResultUtils;
-import com.iexec.sms.blockchain.IexecHubService;
+import com.iexec.sms.api.TeeSessionGenerationError;
import com.iexec.sms.secret.Secret;
+import com.iexec.sms.secret.compute.OnChainObjectType;
+import com.iexec.sms.secret.compute.SecretOwnerRole;
+import com.iexec.sms.secret.compute.TeeTaskComputeSecret;
+import com.iexec.sms.secret.compute.TeeTaskComputeSecretService;
import com.iexec.sms.secret.web2.Web2SecretsService;
import com.iexec.sms.secret.web3.Web3Secret;
import com.iexec.sms.secret.web3.Web3SecretService;
import com.iexec.sms.tee.challenge.TeeChallenge;
import com.iexec.sms.tee.challenge.TeeChallengeService;
+import com.iexec.sms.tee.session.TeeSessionGenerationException;
import com.iexec.sms.tee.session.attestation.AttestationSecurityConfig;
import com.iexec.sms.tee.workflow.TeeWorkflowConfiguration;
import com.iexec.sms.utils.EthereumCredentials;
@@ -39,25 +44,30 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.util.ReflectionTestUtils;
import org.yaml.snakeyaml.Yaml;
+import java.security.GeneralSecurityException;
import java.util.*;
-import static com.iexec.common.precompute.PreComputeUtils.IEXEC_DATASET_KEY;
-import static com.iexec.common.precompute.PreComputeUtils.INPUT_FILE_URLS;
-import static com.iexec.common.precompute.PreComputeUtils.IS_DATASET_REQUIRED;
+import static com.iexec.common.chain.DealParams.DROPBOX_RESULT_STORAGE_PROVIDER;
+import static com.iexec.common.sms.secret.ReservedSecretKeyName.IEXEC_RESULT_DROPBOX_TOKEN;
+import static com.iexec.common.sms.secret.ReservedSecretKeyName.IEXEC_RESULT_ENCRYPTION_PUBLIC_KEY;
import static com.iexec.common.worker.result.ResultUtils.*;
+import static com.iexec.sms.Web3jUtils.createEthereumAddress;
+import static com.iexec.sms.api.TeeSessionGenerationError.*;
import static com.iexec.sms.tee.session.palaemon.PalaemonSessionService.*;
-import static com.iexec.sms.tee.session.palaemon.PalaemonSessionService.INPUT_FILE_NAMES;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@Slf4j
-public class PalaemonSessionServiceTests {
+class PalaemonSessionServiceTests {
private static final String TEMPLATE_SESSION_FILE = "src/main/resources/palaemonTemplate.vm";
private static final String EXPECTED_SESSION_FILE = "src/test/resources/tee-session.yml";
@@ -66,7 +76,6 @@ public class PalaemonSessionServiceTests {
private static final String SESSION_ID = "sessionId";
private static final String WORKER_ADDRESS = "workerAddress";
private static final String ENCLAVE_CHALLENGE = "enclaveChallenge";
- private static final String REQUESTER = "requester";
// pre-compute
private static final String PRE_COMPUTE_FINGERPRINT = "mrEnclave1";
private static final String PRE_COMPUTE_ENTRYPOINT = "entrypoint1";
@@ -77,16 +86,22 @@ public class PalaemonSessionServiceTests {
// keys with leading/trailing \n should not break the workflow
private static final String DATASET_KEY = "\ndatasetKey\n";
// app
+ private static final String APP_DEVELOPER_SECRET_INDEX = "1";
+ private static final String APP_DEVELOPER_SECRET_VALUE = "appDeveloperSecretValue";
+ private static final String REQUESTER_SECRET_KEY_1 = "requesterSecretKey1";
+ private static final String REQUESTER_SECRET_VALUE_1 = "requesterSecretValue1";
+ private static final String REQUESTER_SECRET_KEY_2 = "requesterSecretKey2";
+ private static final String REQUESTER_SECRET_VALUE_2 = "requesterSecretValue2";
private static final String APP_URI = "appUri";
private static final String APP_FINGERPRINT = "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b";
private static final String APP_ENTRYPOINT = "appEntrypoint";
private static final TeeEnclaveConfiguration enclaveConfig =
mock(TeeEnclaveConfiguration.class);
private static final String ARGS = "args";
+ private static final String IEXEC_APP_DEVELOPER_SECRET_1 = "IEXEC_APP_DEVELOPER_SECRET_1";
// post-compute
private static final String POST_COMPUTE_FINGERPRINT = "mrEnclave3";
private static final String POST_COMPUTE_ENTRYPOINT = "entrypoint3";
- private static final String POST_COMPUTE_IMAGE = "postComputeImage";
private static final String STORAGE_PROVIDER = "ipfs";
private static final String STORAGE_PROXY = "storageProxy";
private static final String STORAGE_TOKEN = "storageToken";
@@ -98,8 +113,9 @@ public class PalaemonSessionServiceTests {
private static final String INPUT_FILE_URL_2 = "http://host/file2";
private static final String INPUT_FILE_NAME_2 = "file2";
- @Mock
- private IexecHubService iexecHubService;
+ private String appAddress;
+ private String requesterAddress;
+
@Mock
private Web3SecretService web3SecretService;
@Mock
@@ -110,11 +126,12 @@ public class PalaemonSessionServiceTests {
private TeeWorkflowConfiguration teeWorkflowConfig;
@Mock
private AttestationSecurityConfig attestationSecurityConfig;
+ @Mock private TeeTaskComputeSecretService teeTaskComputeSecretService;
private PalaemonSessionService palaemonSessionService;
@BeforeEach
- void beforeEach() throws Exception {
+ void beforeEach() {
MockitoAnnotations.openMocks(this);
// spy is needed to mock some internal calls of the tested
// class when relevant
@@ -123,15 +140,18 @@ void beforeEach() throws Exception {
web2SecretsService,
teeChallengeService,
teeWorkflowConfig,
- attestationSecurityConfig));
+ attestationSecurityConfig,
+ teeTaskComputeSecretService
+ ));
ReflectionTestUtils.setField(palaemonSessionService, "palaemonTemplateFilePath", TEMPLATE_SESSION_FILE);
when(enclaveConfig.getFingerprint()).thenReturn(APP_FINGERPRINT);
when(enclaveConfig.getEntrypoint()).thenReturn(APP_ENTRYPOINT);
}
+ //region getSessionYml
@Test
- public void shouldGetSessionYml() throws Exception {
- PalaemonSessionRequest request = createSessionRequest();
+ void shouldGetSessionYml() throws Exception {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
doReturn(getPreComputeTokens()).when(palaemonSessionService)
.getPreComputePalaemonTokens(request);
doReturn(getAppTokens()).when(palaemonSessionService)
@@ -150,11 +170,33 @@ public void shouldGetSessionYml() throws Exception {
assertRecursively(expectedYmlMap, actualYmlMap);
}
- // pre-compute
+ @Test
+ void shouldNotGetSessionYmlSinceRequestIsNull() {
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getSessionYml(null)
+ );
+ assertEquals(NO_SESSION_REQUEST, exception.getError());
+ assertEquals("Session request must not be null", exception.getMessage());
+ }
@Test
- public void shouldGetPreComputePalaemonTokens() throws Exception {
- PalaemonSessionRequest request = createSessionRequest();
+ void shouldNotGetSessionYmlSinceTaskDescriptionIsMissing() {
+ PalaemonSessionRequest request = PalaemonSessionRequest.builder().build();
+
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getSessionYml(request)
+ );
+ assertEquals(NO_TASK_DESCRIPTION, exception.getError());
+ assertEquals("Task description must not be null", exception.getMessage());
+ }
+ //endregion
+
+ //region getPreComputePalaemonTokens
+ @Test
+ void shouldGetPreComputePalaemonTokens() throws Exception {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
when(teeWorkflowConfig.getPreComputeFingerprint())
.thenReturn(PRE_COMPUTE_FINGERPRINT);
when(teeWorkflowConfig.getPreComputeEntrypoint())
@@ -165,57 +207,225 @@ public void shouldGetPreComputePalaemonTokens() throws Exception {
Map tokens =
palaemonSessionService.getPreComputePalaemonTokens(request);
- assertThat(tokens).isNotEmpty();
- assertThat(tokens.get(PalaemonSessionService.PRE_COMPUTE_MRENCLAVE))
- .isEqualTo(PRE_COMPUTE_FINGERPRINT);
- assertThat(tokens.get(PalaemonSessionService.PRE_COMPUTE_ENTRYPOINT))
- .isEqualTo(PRE_COMPUTE_ENTRYPOINT);
- assertThat(tokens.get(PreComputeUtils.IEXEC_DATASET_KEY))
- .isEqualTo(secret.getTrimmedValue());
- assertThat(tokens.get(PalaemonSessionService.INPUT_FILE_URLS))
- .isEqualTo(Map.of(
- IexecEnvUtils.IEXEC_INPUT_FILE_URL_PREFIX + "1", INPUT_FILE_URL_1,
- IexecEnvUtils.IEXEC_INPUT_FILE_URL_PREFIX + "2", INPUT_FILE_URL_2));
+ assertThat(tokens)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ PalaemonSessionService.PRE_COMPUTE_MRENCLAVE, PRE_COMPUTE_FINGERPRINT,
+ PalaemonSessionService.PRE_COMPUTE_ENTRYPOINT, PRE_COMPUTE_ENTRYPOINT,
+ PreComputeUtils.IEXEC_DATASET_KEY, secret.getTrimmedValue(),
+ PreComputeUtils.IS_DATASET_REQUIRED, true,
+ PalaemonSessionService.INPUT_FILE_URLS,
+ Map.of(
+ IexecEnvUtils.IEXEC_INPUT_FILE_URL_PREFIX + "1", INPUT_FILE_URL_1,
+ IexecEnvUtils.IEXEC_INPUT_FILE_URL_PREFIX + "2", INPUT_FILE_URL_2)
+
+ )
+ );
}
- // app
+ @Test
+ void shouldGetPreComputePalaemonTokensWithoutDataset() throws Exception {
+ PalaemonSessionRequest request = PalaemonSessionRequest.builder()
+ .sessionId(SESSION_ID)
+ .workerAddress(WORKER_ADDRESS)
+ .enclaveChallenge(ENCLAVE_CHALLENGE)
+ .taskDescription(TaskDescription.builder()
+ .chainTaskId(TASK_ID)
+ .inputFiles(List.of(INPUT_FILE_URL_1, INPUT_FILE_URL_2))
+ .build())
+ .build();
+ when(teeWorkflowConfig.getPreComputeFingerprint())
+ .thenReturn(PRE_COMPUTE_FINGERPRINT);
+ when(teeWorkflowConfig.getPreComputeEntrypoint())
+ .thenReturn(PRE_COMPUTE_ENTRYPOINT);
+ Map tokens =
+ palaemonSessionService.getPreComputePalaemonTokens(request);
+ assertThat(tokens).isNotEmpty()
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ PalaemonSessionService.PRE_COMPUTE_MRENCLAVE, PRE_COMPUTE_FINGERPRINT,
+ PalaemonSessionService.PRE_COMPUTE_ENTRYPOINT, PRE_COMPUTE_ENTRYPOINT,
+ PreComputeUtils.IEXEC_DATASET_KEY, "",
+ PreComputeUtils.IS_DATASET_REQUIRED, false,
+ PalaemonSessionService.INPUT_FILE_URLS,
+ Map.of(
+ IexecEnvUtils.IEXEC_INPUT_FILE_URL_PREFIX + "1", INPUT_FILE_URL_1,
+ IexecEnvUtils.IEXEC_INPUT_FILE_URL_PREFIX + "2", INPUT_FILE_URL_2)
+
+ )
+ );
+ }
+ //endregion
+
+ //region getAppPalaemonTokens
@Test
- public void shouldGetAppPalaemonTokens() throws Exception {
- PalaemonSessionRequest request = createSessionRequest();
+ void shouldGetAppPalaemonTokensForAdvancedTaskDescription() {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
TeeEnclaveConfigurationValidator validator = mock(TeeEnclaveConfigurationValidator.class);
when(enclaveConfig.getValidator()).thenReturn(validator);
when(validator.isValid()).thenReturn(true);
- Map tokens =
- palaemonSessionService.getAppPalaemonTokens(request);
- assertThat(tokens).isNotEmpty();
- assertThat(tokens.get(PalaemonSessionService.APP_MRENCLAVE))
- .isEqualTo(APP_FINGERPRINT);
- assertThat(tokens.get(PalaemonSessionService.APP_ARGS))
- .isEqualTo(APP_ENTRYPOINT + " " + ARGS);
- assertThat(tokens.get(PalaemonSessionService.INPUT_FILE_NAMES))
- .isEqualTo(Map.of(
- IexecEnvUtils.IEXEC_INPUT_FILE_NAME_PREFIX + "1", "file1",
- IexecEnvUtils.IEXEC_INPUT_FILE_NAME_PREFIX + "2", "file2"));
+ addApplicationDeveloperSecret();
+ addRequesterSecret(REQUESTER_SECRET_KEY_1, REQUESTER_SECRET_VALUE_1);
+ addRequesterSecret(REQUESTER_SECRET_KEY_2, REQUESTER_SECRET_VALUE_2);
+
+ Map tokens = assertDoesNotThrow(() -> palaemonSessionService.getAppPalaemonTokens(request));
+
+ verify(teeTaskComputeSecretService).getSecret(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", APP_DEVELOPER_SECRET_INDEX);
+ verify(teeTaskComputeSecretService).getSecret(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, REQUESTER_SECRET_KEY_1);
+ verify(teeTaskComputeSecretService).getSecret(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, REQUESTER_SECRET_KEY_2);
+
+ assertThat(tokens)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ PalaemonSessionService.APP_MRENCLAVE, APP_FINGERPRINT,
+ PalaemonSessionService.APP_ARGS, APP_ENTRYPOINT + " " + ARGS,
+ PalaemonSessionService.INPUT_FILE_NAMES,
+ Map.of(
+ IexecEnvUtils.IEXEC_INPUT_FILE_NAME_PREFIX + "1", "file1",
+ IexecEnvUtils.IEXEC_INPUT_FILE_NAME_PREFIX + "2", "file2"
+ ),
+ IEXEC_APP_DEVELOPER_SECRET_1, APP_DEVELOPER_SECRET_VALUE,
+ REQUESTER_SECRETS,
+ Map.of(
+ IexecEnvUtils.IEXEC_REQUESTER_SECRET_PREFIX + "1", REQUESTER_SECRET_VALUE_1,
+ IexecEnvUtils.IEXEC_REQUESTER_SECRET_PREFIX + "2", REQUESTER_SECRET_VALUE_2
+ )
+
+ )
+ );
+ }
+
+ @Test
+ void shouldGetPalaemonTokensWithEmptyAppComputeSecretWhenSecretsDoNotExist() {
+ final String appAddress = createEthereumAddress();
+ final String requesterAddress = createEthereumAddress();
+ final TaskDescription taskDescription = TaskDescription.builder()
+ .chainTaskId(TASK_ID)
+ .appUri(APP_URI)
+ .appAddress(appAddress)
+ .appEnclaveConfiguration(enclaveConfig)
+ .datasetAddress(DATASET_ADDRESS)
+ .datasetUri(DATASET_URL)
+ .datasetName(DATASET_NAME)
+ .datasetChecksum(DATASET_CHECKSUM)
+ .requester(requesterAddress)
+ .cmd(ARGS)
+ .inputFiles(List.of(INPUT_FILE_URL_1, INPUT_FILE_URL_2))
+ .isResultEncryption(true)
+ .resultStorageProvider(STORAGE_PROVIDER)
+ .resultStorageProxy(STORAGE_PROXY)
+ .botSize(1)
+ .botFirstIndex(0)
+ .botIndex(0)
+ .build();
+ PalaemonSessionRequest request = createSessionRequest(taskDescription);
+ TeeEnclaveConfigurationValidator validator = mock(TeeEnclaveConfigurationValidator.class);
+ when(enclaveConfig.getValidator()).thenReturn(validator);
+ when(validator.isValid()).thenReturn(true);
+ when(teeTaskComputeSecretService.getSecret(
+ OnChainObjectType.APPLICATION,
+ appAddress,
+ SecretOwnerRole.APPLICATION_DEVELOPER,
+ "",
+ APP_DEVELOPER_SECRET_INDEX))
+ .thenReturn(Optional.empty());
+
+ Map tokens = assertDoesNotThrow(() -> palaemonSessionService.getAppPalaemonTokens(request));
+ verify(teeTaskComputeSecretService).getSecret(eq(OnChainObjectType.APPLICATION), eq(appAddress), eq(SecretOwnerRole.APPLICATION_DEVELOPER), eq(""), any());
+ verify(teeTaskComputeSecretService, never()).getSecret(eq(OnChainObjectType.APPLICATION), eq(""), eq(SecretOwnerRole.REQUESTER), any(), any());
+
+ assertThat(tokens)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ PalaemonSessionService.APP_MRENCLAVE, APP_FINGERPRINT,
+ PalaemonSessionService.APP_ARGS, APP_ENTRYPOINT + " " + ARGS,
+ PalaemonSessionService.INPUT_FILE_NAMES,
+ Map.of(
+ IexecEnvUtils.IEXEC_INPUT_FILE_NAME_PREFIX + "1", "file1",
+ IexecEnvUtils.IEXEC_INPUT_FILE_NAME_PREFIX + "2", "file2"
+ ),
+ IEXEC_APP_DEVELOPER_SECRET_1, "",
+ REQUESTER_SECRETS, Collections.emptyMap()
+ )
+ );
}
@Test
- public void shouldFailToGetAppPalaemonTokensInvalidEnclaveConfig(){
- PalaemonSessionRequest request = createSessionRequest();
+ void shouldFailToGetAppPalaemonTokensSinceNoTaskDescription() {
+ PalaemonSessionRequest request = PalaemonSessionRequest.builder()
+ .build();
+ TeeSessionGenerationException exception = assertThrows(TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getAppPalaemonTokens(request));
+ Assertions.assertEquals(TeeSessionGenerationError.NO_TASK_DESCRIPTION, exception.getError());
+ Assertions.assertEquals("Task description must no be null", exception.getMessage());
+ }
+
+ @Test
+ void shouldFailToGetAppPalaemonTokensSinceNoEnclaveConfig() {
+ PalaemonSessionRequest request = PalaemonSessionRequest.builder()
+ .sessionId(SESSION_ID)
+ .workerAddress(WORKER_ADDRESS)
+ .enclaveChallenge(ENCLAVE_CHALLENGE)
+ .taskDescription(TaskDescription.builder().build())
+ .build();
+ TeeSessionGenerationException exception = assertThrows(TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getAppPalaemonTokens(request));
+ Assertions.assertEquals(TeeSessionGenerationError.APP_COMPUTE_NO_ENCLAVE_CONFIG, exception.getError());
+ Assertions.assertEquals("Enclave configuration must no be null", exception.getMessage());
+ }
+
+ @Test
+ void shouldFailToGetAppPalaemonTokensInvalidEnclaveConfig() {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
TeeEnclaveConfigurationValidator validator = mock(TeeEnclaveConfigurationValidator.class);
when(enclaveConfig.getValidator()).thenReturn(validator);
String validationError = "validation error";
when(validator.validate()).thenReturn(Collections.singletonList(validationError));
- IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ TeeSessionGenerationException exception = assertThrows(TeeSessionGenerationException.class,
() -> palaemonSessionService.getAppPalaemonTokens(request));
- Assertions.assertTrue(exception.getMessage().contains(validationError));
+ Assertions.assertEquals(TeeSessionGenerationError.APP_COMPUTE_INVALID_ENCLAVE_CONFIG, exception.getError());
}
- // post-compute
+ @Test
+ void shouldAddMultipleRequesterSecrets() {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
+ TeeEnclaveConfigurationValidator validator = mock(TeeEnclaveConfigurationValidator.class);
+ when(enclaveConfig.getValidator()).thenReturn(validator);
+ when(validator.isValid()).thenReturn(true);
+ addRequesterSecret(REQUESTER_SECRET_KEY_1, REQUESTER_SECRET_VALUE_1);
+ addRequesterSecret(REQUESTER_SECRET_KEY_2, REQUESTER_SECRET_VALUE_2);
+ Map tokens = assertDoesNotThrow(() -> palaemonSessionService.getAppPalaemonTokens(request));
+ verify(teeTaskComputeSecretService, times(2))
+ .getSecret(eq(OnChainObjectType.APPLICATION), eq(""), eq(SecretOwnerRole.REQUESTER), any(), any());
+ verify(teeTaskComputeSecretService).getSecret(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, REQUESTER_SECRET_KEY_1);
+ verify(teeTaskComputeSecretService).getSecret(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, REQUESTER_SECRET_KEY_2);
+ assertThat(tokens).containsEntry(REQUESTER_SECRETS,
+ Map.of(
+ IexecEnvUtils.IEXEC_REQUESTER_SECRET_PREFIX + "1", REQUESTER_SECRET_VALUE_1,
+ IexecEnvUtils.IEXEC_REQUESTER_SECRET_PREFIX + "2", REQUESTER_SECRET_VALUE_2
+ ));
+ }
@Test
- public void shouldGetPostComputePalaemonTokens() throws Exception {
- PalaemonSessionRequest request = createSessionRequest();
+ void shouldFilterRequesterSecretIndexLowerThanZero() {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
+ request.getTaskDescription().setSecrets(Map.of("1", REQUESTER_SECRET_KEY_1, "-1", "out-of-bound-requester-secret"));
+ TeeEnclaveConfigurationValidator validator = mock(TeeEnclaveConfigurationValidator.class);
+ when(enclaveConfig.getValidator()).thenReturn(validator);
+ when(validator.isValid()).thenReturn(true);
+ addRequesterSecret(REQUESTER_SECRET_KEY_1, REQUESTER_SECRET_VALUE_1);
+ Map tokens = assertDoesNotThrow(() -> palaemonSessionService.getAppPalaemonTokens(request));
+ verify(teeTaskComputeSecretService).getSecret(eq(OnChainObjectType.APPLICATION), eq(""), eq(SecretOwnerRole.REQUESTER), any(), any());
+ assertThat(tokens).containsEntry(REQUESTER_SECRETS,
+ Map.of(IexecEnvUtils.IEXEC_REQUESTER_SECRET_PREFIX + "1", REQUESTER_SECRET_VALUE_1));
+ }
+ //endregion
+
+ //region getPostComputePalaemonTokens
+ @Test
+ void shouldGetPostComputePalaemonTokens() throws Exception {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
Secret publicKeySecret = new Secret("address", ENCRYPTION_PUBLIC_KEY);
when(teeWorkflowConfig.getPostComputeFingerprint())
.thenReturn(POST_COMPUTE_FINGERPRINT);
@@ -228,7 +438,7 @@ public void shouldGetPostComputePalaemonTokens() throws Exception {
.thenReturn(Optional.of(publicKeySecret));
Secret storageSecret = new Secret("address", STORAGE_TOKEN);
when(web2SecretsService.getSecret(
- REQUESTER,
+ requesterAddress,
ReservedSecretKeyName.IEXEC_RESULT_IEXEC_IPFS_TOKEN,
true))
.thenReturn(Optional.of(storageSecret));
@@ -242,55 +452,341 @@ public void shouldGetPostComputePalaemonTokens() throws Exception {
Map tokens =
palaemonSessionService.getPostComputePalaemonTokens(request);
- assertThat(tokens).isNotEmpty();
- assertThat(tokens.get(PalaemonSessionService.POST_COMPUTE_MRENCLAVE))
- .isEqualTo(POST_COMPUTE_FINGERPRINT);
- assertThat(tokens.get(PalaemonSessionService.POST_COMPUTE_ENTRYPOINT))
- .isEqualTo(POST_COMPUTE_ENTRYPOINT);
+
+ final Map expectedTokens = new HashMap<>();
+ expectedTokens.put(PalaemonSessionService.POST_COMPUTE_MRENCLAVE, POST_COMPUTE_FINGERPRINT);
+ expectedTokens.put(PalaemonSessionService.POST_COMPUTE_ENTRYPOINT, POST_COMPUTE_ENTRYPOINT);
// encryption tokens
- assertThat(tokens.get(ResultUtils.RESULT_ENCRYPTION)).isEqualTo("yes") ;
- assertThat(tokens.get(ResultUtils.RESULT_ENCRYPTION_PUBLIC_KEY))
- .isEqualTo(ENCRYPTION_PUBLIC_KEY);
+ expectedTokens.put(ResultUtils.RESULT_ENCRYPTION, "yes");
+ expectedTokens.put(ResultUtils.RESULT_ENCRYPTION_PUBLIC_KEY, ENCRYPTION_PUBLIC_KEY);
// storage tokens
- assertThat(tokens.get(ResultUtils.RESULT_STORAGE_CALLBACK)).isEqualTo("no");
- assertThat(tokens.get(ResultUtils.RESULT_STORAGE_PROVIDER))
- .isEqualTo(STORAGE_PROVIDER);
- assertThat(tokens.get(ResultUtils.RESULT_STORAGE_PROXY))
- .isEqualTo(STORAGE_PROXY);
- assertThat(tokens.get(ResultUtils.RESULT_STORAGE_TOKEN))
- .isEqualTo(STORAGE_TOKEN);
+ expectedTokens.put(ResultUtils.RESULT_STORAGE_CALLBACK, "no");
+ expectedTokens.put(ResultUtils.RESULT_STORAGE_PROVIDER, STORAGE_PROVIDER);
+ expectedTokens.put(ResultUtils.RESULT_STORAGE_PROXY, STORAGE_PROXY);
+ expectedTokens.put(ResultUtils.RESULT_STORAGE_TOKEN, STORAGE_TOKEN);
// sign tokens
- assertThat(tokens.get(ResultUtils.RESULT_TASK_ID)).isEqualTo(TASK_ID);
- assertThat(tokens.get(ResultUtils.RESULT_SIGN_WORKER_ADDRESS))
- .isEqualTo(WORKER_ADDRESS);
- assertThat(tokens.get(ResultUtils.RESULT_SIGN_TEE_CHALLENGE_PRIVATE_KEY))
- .isEqualTo(challenge.getCredentials().getPrivateKey());
+ expectedTokens.put(ResultUtils.RESULT_TASK_ID, TASK_ID);
+ expectedTokens.put(ResultUtils.RESULT_SIGN_WORKER_ADDRESS, WORKER_ADDRESS);
+ expectedTokens.put(ResultUtils.RESULT_SIGN_TEE_CHALLENGE_PRIVATE_KEY, challenge.getCredentials().getPrivateKey());
+
+ assertThat(tokens).containsExactlyEntriesOf(expectedTokens);
+ }
+
+ @Test
+ void shouldNotGetPostComputePalaemonTokensSinceTaskDescriptionMissing() {
+ PalaemonSessionRequest request = PalaemonSessionRequest.builder().build();
+
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getPostComputePalaemonTokens(request)
+ );
+ assertEquals(NO_TASK_DESCRIPTION, exception.getError());
+ assertEquals("Task description must not be null", exception.getMessage());
+ }
+ //endregion
+
+ //region getPostComputeEncryptionTokens
+ @Test
+ void shouldGetPostComputeStorageTokensWithCallback() {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ sessionRequest.getTaskDescription().setCallback("callback");
+
+ final Map tokens = assertDoesNotThrow(
+ () -> palaemonSessionService.getPostComputeStorageTokens(sessionRequest));
+
+ assertThat(tokens)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ RESULT_STORAGE_CALLBACK, "yes",
+ RESULT_STORAGE_PROVIDER, EMPTY_YML_VALUE,
+ RESULT_STORAGE_PROXY, EMPTY_YML_VALUE,
+ RESULT_STORAGE_TOKEN, EMPTY_YML_VALUE
+ )
+ );
+ }
+
+ @Test
+ void shouldGetPostComputeStorageTokensOnIpfs() {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ final TaskDescription taskDescription = sessionRequest.getTaskDescription();
+
+ final String secretValue = "Secret value";
+ when(web2SecretsService.getSecret(taskDescription.getRequester(), ReservedSecretKeyName.IEXEC_RESULT_IEXEC_IPFS_TOKEN, true))
+ .thenReturn(Optional.of(new Secret(null, secretValue)));
+
+ final Map tokens = assertDoesNotThrow(
+ () -> palaemonSessionService.getPostComputeStorageTokens(sessionRequest));
+
+ assertThat(tokens)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ RESULT_STORAGE_CALLBACK, "no",
+ RESULT_STORAGE_PROVIDER, STORAGE_PROVIDER,
+ RESULT_STORAGE_PROXY, STORAGE_PROXY,
+ RESULT_STORAGE_TOKEN, secretValue
+ )
+ );
+ }
+
+ @Test
+ void shouldGetPostComputeStorageTokensOnDropbox() {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ final TaskDescription taskDescription = sessionRequest.getTaskDescription();
+ taskDescription.setResultStorageProvider(DROPBOX_RESULT_STORAGE_PROVIDER);
+
+ final String secretValue = "Secret value";
+ when(web2SecretsService.getSecret(taskDescription.getRequester(), IEXEC_RESULT_DROPBOX_TOKEN, true))
+ .thenReturn(Optional.of(new Secret(null, secretValue)));
+
+ final Map tokens = assertDoesNotThrow(
+ () -> palaemonSessionService.getPostComputeStorageTokens(sessionRequest));
+
+ assertThat(tokens)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ RESULT_STORAGE_CALLBACK, "no",
+ RESULT_STORAGE_PROVIDER, DROPBOX_RESULT_STORAGE_PROVIDER,
+ RESULT_STORAGE_PROXY, STORAGE_PROXY,
+ RESULT_STORAGE_TOKEN, secretValue
+ )
+ );
+ }
+
+ @Test
+ void shouldNotGetPostComputeStorageTokensSinceNoSecret() {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ final TaskDescription taskDescription = sessionRequest.getTaskDescription();
+
+ when(web2SecretsService.getSecret(taskDescription.getRequester(), ReservedSecretKeyName.IEXEC_RESULT_IEXEC_IPFS_TOKEN, true))
+ .thenReturn(Optional.empty());
+
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getPostComputeStorageTokens(sessionRequest));
+
+ assertThat(exception.getError()).isEqualTo(POST_COMPUTE_GET_STORAGE_TOKENS_FAILED);
+ assertThat(exception.getMessage()).isEqualTo("Empty requester storage token - taskId: " + taskDescription.getChainTaskId());
+ }
+
+ @Test
+ void shouldGetPostComputeSignTokens() throws GeneralSecurityException {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ final TaskDescription taskDescription = sessionRequest.getTaskDescription();
+ final String taskId = taskDescription.getChainTaskId();
+ final EthereumCredentials credentials = EthereumCredentials.generate();
+
+ when(teeChallengeService.getOrCreate(taskId, true))
+ .thenReturn(Optional.of(TeeChallenge.builder().credentials(credentials).build()));
+
+ final Map tokens = assertDoesNotThrow(() -> palaemonSessionService.getPostComputeSignTokens(sessionRequest));
+
+ assertThat(tokens)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ RESULT_TASK_ID, taskId,
+ RESULT_SIGN_WORKER_ADDRESS, sessionRequest.getWorkerAddress(),
+ RESULT_SIGN_TEE_CHALLENGE_PRIVATE_KEY, credentials.getPrivateKey()
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {""})
+ void shouldNotGetPostComputeSignTokensSinceNoWorkerAddress(String emptyWorkerAddress) {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ final String taskId = sessionRequest.getTaskDescription().getChainTaskId();
+ sessionRequest.setWorkerAddress(emptyWorkerAddress);
+
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getPostComputeSignTokens(sessionRequest)
+ );
+
+ assertThat(exception.getError()).isEqualTo(POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_WORKER_ADDRESS);
+ assertThat(exception.getMessage()).isEqualTo("Empty worker address - taskId: " + taskId);
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {""})
+ void shouldNotGetPostComputeSignTokensSinceNoEnclaveChallenge(String emptyEnclaveChallenge) {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ final String taskId = sessionRequest.getTaskDescription().getChainTaskId();
+ sessionRequest.setEnclaveChallenge(emptyEnclaveChallenge);
+
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getPostComputeSignTokens(sessionRequest)
+ );
+
+ assertThat(exception.getError()).isEqualTo(POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_PUBLIC_ENCLAVE_CHALLENGE);
+ assertThat(exception.getMessage()).isEqualTo("Empty public enclave challenge - taskId: " + taskId);
+ }
+
+ @Test
+ void shouldNotGetPostComputeSignTokensSinceNoTeeChallenge() {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ final String taskId = sessionRequest.getTaskDescription().getChainTaskId();
+
+ when(teeChallengeService.getOrCreate(taskId, true))
+ .thenReturn(Optional.empty());
+
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getPostComputeSignTokens(sessionRequest)
+ );
+
+ assertThat(exception.getError()).isEqualTo(POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_TEE_CHALLENGE);
+ assertThat(exception.getMessage()).isEqualTo("Empty TEE challenge - taskId: " + taskId);
+ }
+
+ @Test
+ void shouldNotGetPostComputeSignTokensSinceNoEnclaveCredentials() {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ final String taskId = sessionRequest.getTaskDescription().getChainTaskId();
+
+ when(teeChallengeService.getOrCreate(taskId, true))
+ .thenReturn(Optional.of(TeeChallenge.builder().credentials(null).build()));
+
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getPostComputeSignTokens(sessionRequest)
+ );
+
+ assertThat(exception.getError()).isEqualTo(POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_TEE_CREDENTIALS);
+ assertThat(exception.getMessage()).isEqualTo("Empty TEE challenge credentials - taskId: " + taskId);
+ }
+
+ @Test
+ void shouldNotGetPostComputeSignTokensSinceNoEnclaveCredentialsPrivateKey() {
+ final PalaemonSessionRequest sessionRequest = createSessionRequest(createTaskDescription());
+ final String taskId = sessionRequest.getTaskDescription().getChainTaskId();
+
+ when(teeChallengeService.getOrCreate(taskId, true))
+ .thenReturn(Optional.of(TeeChallenge.builder().credentials(new EthereumCredentials("", "", false, "")).build()));
+
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getPostComputeSignTokens(sessionRequest)
+ );
+
+ assertThat(exception.getError()).isEqualTo(POST_COMPUTE_GET_SIGNATURE_TOKENS_FAILED_EMPTY_TEE_CREDENTIALS);
+ assertThat(exception.getMessage()).isEqualTo("Empty TEE challenge credentials - taskId: " + taskId);
+ }
+
+ // endregion
+
+ @Test
+ void shouldGetPostComputeEncryptionTokensWithEncryption() {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
+
+ Secret publicKeySecret = new Secret("address", ENCRYPTION_PUBLIC_KEY);
+ when(web2SecretsService.getSecret(
+ request.getTaskDescription().getBeneficiary(),
+ IEXEC_RESULT_ENCRYPTION_PUBLIC_KEY,
+ true))
+ .thenReturn(Optional.of(publicKeySecret));
+
+ final Map encryptionTokens = assertDoesNotThrow(() -> palaemonSessionService.getPostComputeEncryptionTokens(request));
+ assertThat(encryptionTokens)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ RESULT_ENCRYPTION, "yes",
+ RESULT_ENCRYPTION_PUBLIC_KEY, ENCRYPTION_PUBLIC_KEY
+ )
+ );
+ }
+
+ @Test
+ void shouldGetPostComputeEncryptionTokensWithoutEncryption() {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
+ request.getTaskDescription().setResultEncryption(false);
+
+ final Map encryptionTokens = assertDoesNotThrow(() -> palaemonSessionService.getPostComputeEncryptionTokens(request));
+ assertThat(encryptionTokens)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of(
+ RESULT_ENCRYPTION, "no",
+ RESULT_ENCRYPTION_PUBLIC_KEY, ""
+ )
+ );
+ }
+
+ @Test
+ void shouldNotGetPostComputeEncryptionTokensSinceEmptyBeneficiaryKey() {
+ PalaemonSessionRequest request = createSessionRequest(createTaskDescription());
+
+ when(web2SecretsService.getSecret(
+ request.getTaskDescription().getBeneficiary(),
+ IEXEC_RESULT_ENCRYPTION_PUBLIC_KEY,
+ true))
+ .thenReturn(Optional.empty());
+
+ final TeeSessionGenerationException exception = assertThrows(
+ TeeSessionGenerationException.class,
+ () -> palaemonSessionService.getPostComputeEncryptionTokens(request)
+ );
+ assertEquals(POST_COMPUTE_GET_ENCRYPTION_TOKENS_FAILED_EMPTY_BENEFICIARY_KEY, exception.getError());
+ assertEquals("Empty beneficiary encryption key - taskId: taskId", exception.getMessage());
+ }
+
+ //endregion
+
+ //region utils
+ private void addApplicationDeveloperSecret() {
+ TeeTaskComputeSecret applicationDeveloperSecret = TeeTaskComputeSecret.builder()
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .onChainObjectAddress(appAddress)
+ .secretOwnerRole(SecretOwnerRole.APPLICATION_DEVELOPER)
+ .key(APP_DEVELOPER_SECRET_INDEX)
+ .value(APP_DEVELOPER_SECRET_VALUE)
+ .build();
+ when(teeTaskComputeSecretService.getSecret(OnChainObjectType.APPLICATION, appAddress, SecretOwnerRole.APPLICATION_DEVELOPER, "", APP_DEVELOPER_SECRET_INDEX))
+ .thenReturn(Optional.of(applicationDeveloperSecret));
+ }
+
+ private void addRequesterSecret(String secretKey, String secretValue) {
+ TeeTaskComputeSecret requesterSecret = TeeTaskComputeSecret.builder()
+ .onChainObjectType(OnChainObjectType.APPLICATION)
+ .onChainObjectAddress("")
+ .secretOwnerRole(SecretOwnerRole.REQUESTER)
+ .fixedSecretOwner(requesterAddress)
+ .key(secretKey)
+ .value(secretValue)
+ .build();
+ when(teeTaskComputeSecretService.getSecret(OnChainObjectType.APPLICATION, "", SecretOwnerRole.REQUESTER, requesterAddress, secretKey))
+ .thenReturn(Optional.of(requesterSecret));
}
- private PalaemonSessionRequest createSessionRequest() {
+ private PalaemonSessionRequest createSessionRequest(TaskDescription taskDescription) {
return PalaemonSessionRequest.builder()
.sessionId(SESSION_ID)
.workerAddress(WORKER_ADDRESS)
.enclaveChallenge(ENCLAVE_CHALLENGE)
- .taskDescription(createTaskDescription())
+ .taskDescription(taskDescription)
.build();
}
private TaskDescription createTaskDescription() {
+ appAddress = createEthereumAddress();
+ requesterAddress = createEthereumAddress();
return TaskDescription.builder()
.chainTaskId(TASK_ID)
.appUri(APP_URI)
+ .appAddress(appAddress)
.appEnclaveConfiguration(enclaveConfig)
.datasetAddress(DATASET_ADDRESS)
.datasetUri(DATASET_URL)
.datasetName(DATASET_NAME)
.datasetChecksum(DATASET_CHECKSUM)
- .requester(REQUESTER)
+ .requester(requesterAddress)
.cmd(ARGS)
.inputFiles(List.of(INPUT_FILE_URL_1, INPUT_FILE_URL_2))
.isResultEncryption(true)
.resultStorageProvider(STORAGE_PROVIDER)
.resultStorageProxy(STORAGE_PROXY)
+ .secrets(Map.of("1", REQUESTER_SECRET_KEY_1, "2", REQUESTER_SECRET_KEY_2))
.botSize(1)
.botFirstIndex(0)
.botIndex(0)
@@ -301,8 +797,8 @@ private Map getPreComputeTokens() {
return Map.of(
PRE_COMPUTE_MRENCLAVE, PRE_COMPUTE_FINGERPRINT,
PalaemonSessionService.PRE_COMPUTE_ENTRYPOINT, PRE_COMPUTE_ENTRYPOINT,
- IS_DATASET_REQUIRED, true,
- IEXEC_DATASET_KEY, DATASET_KEY.trim(),
+ PreComputeUtils.IS_DATASET_REQUIRED, true,
+ PreComputeUtils.IEXEC_DATASET_KEY, DATASET_KEY.trim(),
INPUT_FILE_URLS, Map.of(
IexecEnvUtils.IEXEC_INPUT_FILE_URL_PREFIX + "1", INPUT_FILE_URL_1,
IexecEnvUtils.IEXEC_INPUT_FILE_URL_PREFIX + "2", INPUT_FILE_URL_2));
@@ -358,4 +854,5 @@ private void assertRecursively(Object expected, Object actual) {
});
}
}
+ //endregion
}
diff --git a/src/test/java/com/iexec/sms/tee/workflow/TeeWorkflowConfigurationTests.java b/src/test/java/com/iexec/sms/tee/workflow/TeeWorkflowConfigurationTests.java
index 5842f3f9..fd930878 100644
--- a/src/test/java/com/iexec/sms/tee/workflow/TeeWorkflowConfigurationTests.java
+++ b/src/test/java/com/iexec/sms/tee/workflow/TeeWorkflowConfigurationTests.java
@@ -8,7 +8,7 @@
import static org.assertj.core.api.Assertions.assertThat;
-public class TeeWorkflowConfigurationTests {
+class TeeWorkflowConfigurationTests {
private static final String LAS_IMAGE = "lasImage";
private static final String PRE_COMPUTE_IMAGE = "preComputeImage";
@@ -36,7 +36,7 @@ void beforeEach() {
}
@Test
- public void shouldGetPublicConfiguration() {
+ void shouldGetPublicConfiguration() {
assertThat(teeWorkflowConfiguration.getSharedConfiguration())
.isEqualTo(TeeWorkflowSharedConfiguration.builder()
.lasImage(LAS_IMAGE)
diff --git a/src/test/java/com/iexec/sms/utils/version/VersionServiceTest.java b/src/test/java/com/iexec/sms/utils/version/VersionServiceTest.java
new file mode 100644
index 00000000..2276f95e
--- /dev/null
+++ b/src/test/java/com/iexec/sms/utils/version/VersionServiceTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 IEXEC BLOCKCHAIN TECH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.iexec.sms.utils.version;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.boot.info.BuildProperties;
+
+public class VersionServiceTest {
+
+ @Mock
+ private BuildProperties buildProperties;
+
+ @InjectMocks
+ private VersionService versionService;
+
+ @BeforeEach
+ public void preflight() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings={"x.y.z", "x.y.z-rc"})
+ void testNonSnapshotVersion(String version) {
+ Mockito.when(buildProperties.getVersion()).thenReturn(version);
+ Assertions.assertEquals(version, versionService.getVersion());
+ Assertions.assertFalse(versionService.isSnapshot());
+ }
+
+ @Test
+ void testSnapshotVersion() {
+ Mockito.when(buildProperties.getVersion()).thenReturn("x.y.z-NEXT-SNAPSHOT");
+ Assertions.assertEquals("x.y.z-NEXT-SNAPSHOT", versionService.getVersion());
+ Assertions.assertTrue(versionService.isSnapshot());
+ }
+
+}