Skip to content

Commit 8842d3f

Browse files
committed
Add regex validation for resource identifiers and update related integration tests
- Introduced regex validation for identifiers such as `organizationId`, `workspaceId`, `runnerId`, `runId`, `solutionId`, and `datasetId` across services and APIs. - Updated OpenAPI specifications to include regex patterns for identifier validation. - Added integration tests for invalid identifier formats in `organization`, `workspace`, `runner`, `run`, `dataset`, and `solution` APIs.
1 parent 1b0528f commit 8842d3f

File tree

26 files changed

+455
-1974
lines changed

26 files changed

+455
-1974
lines changed

api/src/integrationTest/kotlin/com/cosmotech/api/home/dataset/DatasetControllerTests.kt

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import kotlin.test.assertEquals
4949
import org.apache.commons.io.IOUtils
5050
import org.hamcrest.Matchers.empty
5151
import org.hamcrest.Matchers.greaterThan
52+
import org.hamcrest.core.StringContains.containsString
5253
import org.json.JSONArray
5354
import org.json.JSONObject
5455
import org.junit.jupiter.api.BeforeEach
@@ -109,6 +110,61 @@ class DatasetControllerTests : ControllerTestBase() {
109110
mvc, organizationId, constructWorkspaceCreateRequest(solutionId = solutionId))
110111
}
111112

113+
@Test
114+
@WithMockOauth2User
115+
fun get_dataset_part_with_wrong_ids_format() {
116+
mvc.perform(
117+
get(
118+
"/organizations/wrong-orgId/workspaces/wrong-workspaceId/datasets/wrong-datasetId/parts/wrong-datasetPartId")
119+
.contentType(MediaType.APPLICATION_JSON)
120+
.accept(MediaType.APPLICATION_JSON))
121+
.andExpect(status().isBadRequest)
122+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
123+
.andExpect(
124+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
125+
.andExpect(
126+
jsonPath("$.detail", containsString("wrong-datasetId:must match \"^d-\\w{10,20}\"")))
127+
.andExpect(
128+
jsonPath(
129+
"$.detail", containsString("wrong-datasetPartId:must match \"^dp-\\w{10,20}\"")))
130+
131+
mvc.perform(
132+
get(
133+
"/organizations/wrong-orgId/workspaces/w-123456abcdef/datasets/d-123456azerty/parts/dp-123456azert")
134+
.contentType(MediaType.APPLICATION_JSON)
135+
.accept(MediaType.APPLICATION_JSON))
136+
.andExpect(status().isBadRequest)
137+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
138+
139+
mvc.perform(
140+
get(
141+
"/organizations/o-1233456azer/workspaces/wrong-workspaceId/datasets/d-123456azerty/parts/dp-123456azert")
142+
.contentType(MediaType.APPLICATION_JSON)
143+
.accept(MediaType.APPLICATION_JSON))
144+
.andExpect(status().isBadRequest)
145+
.andExpect(
146+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
147+
148+
mvc.perform(
149+
get(
150+
"/organizations/o-1233456azer/workspaces/w-123456abcdef/datasets/wrong-datasetId/parts/dp-123456azert")
151+
.contentType(MediaType.APPLICATION_JSON)
152+
.accept(MediaType.APPLICATION_JSON))
153+
.andExpect(status().isBadRequest)
154+
.andExpect(
155+
jsonPath("$.detail", containsString("wrong-datasetId:must match \"^d-\\w{10,20}\"")))
156+
157+
mvc.perform(
158+
get(
159+
"/organizations/o-1233456azer/workspaces/w-123456abcdef/datasets/d-123456azerty/parts/wrong-datasetPartId")
160+
.contentType(MediaType.APPLICATION_JSON)
161+
.accept(MediaType.APPLICATION_JSON))
162+
.andExpect(status().isBadRequest)
163+
.andExpect(
164+
jsonPath(
165+
"$.detail", containsString("wrong-datasetPartId:must match \"^dp-\\w{10,20}\"")))
166+
}
167+
112168
@Test
113169
@WithMockOauth2User
114170
fun create_dataset() {

api/src/integrationTest/kotlin/com/cosmotech/api/home/organization/OrganizationControllerTests.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ class OrganizationControllerTests : ControllerTestBase() {
5959
.andDo(MockMvcResultHandlers.print())
6060
}
6161

62+
@Test
63+
@WithMockOauth2User
64+
fun get_organization_with_wrong_orgId_format() {
65+
mvc.perform(
66+
get("/organizations/wrongid-format")
67+
.contentType(MediaType.APPLICATION_JSON)
68+
.accept(MediaType.APPLICATION_JSON))
69+
.andExpect(status().isBadRequest)
70+
.andExpect(jsonPath("$.detail").value("wrongid-format:must match \"^o-\\w{10,20}\""))
71+
}
72+
6273
@Test
6374
@WithMockOauth2User
6475
fun create_organization() {

api/src/integrationTest/kotlin/com/cosmotech/api/home/run/RunControllerTests.kt

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import io.mockk.impl.annotations.MockK
6363
import io.mockk.mockk
6464
import java.time.Instant
6565
import java.util.*
66+
import org.hamcrest.core.StringContains.containsString
6667
import org.json.JSONObject
6768
import org.junit.jupiter.api.BeforeEach
6869
import org.junit.jupiter.api.Test
@@ -171,6 +172,85 @@ class RunControllerTests : ControllerTestBase() {
171172
.getString("id")
172173
}
173174

175+
@Test
176+
@WithMockOauth2User
177+
fun get_run_with_wrong_ids_format() {
178+
mvc.perform(
179+
get(
180+
"/organizations/wrong-orgId/workspaces/wrong-workspaceId/runners/wrong-runnerId/runs/wrong-runId")
181+
.contentType(MediaType.APPLICATION_JSON)
182+
.accept(MediaType.APPLICATION_JSON))
183+
.andExpect(status().isBadRequest)
184+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
185+
.andExpect(
186+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
187+
.andExpect(
188+
jsonPath("$.detail", containsString("wrong-runnerId:must match \"^(r|s)-\\w{10,20}\"")))
189+
.andExpect(
190+
jsonPath("$.detail", containsString("wrong-runId:must match \"^(run|sr)-\\w{10,20}\"")))
191+
192+
mvc.perform(
193+
get(
194+
"/organizations/wrong-orgId/workspaces/w-123456abcdef/runners/r-123456azerty/runs/run-123456azerty")
195+
.contentType(MediaType.APPLICATION_JSON)
196+
.accept(MediaType.APPLICATION_JSON))
197+
.andExpect(status().isBadRequest)
198+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
199+
200+
mvc.perform(
201+
get(
202+
"/organizations/wrong-orgId/workspaces/w-123456abcdef/runners/s-123456azerty/runs/run-123456azerty")
203+
.contentType(MediaType.APPLICATION_JSON)
204+
.accept(MediaType.APPLICATION_JSON))
205+
.andExpect(status().isBadRequest)
206+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
207+
208+
mvc.perform(
209+
get(
210+
"/organizations/o-1233456azer/workspaces/wrong-workspaceId/runners/r-123456azerty/runs/run-123456azerty")
211+
.contentType(MediaType.APPLICATION_JSON)
212+
.accept(MediaType.APPLICATION_JSON))
213+
.andExpect(status().isBadRequest)
214+
.andExpect(
215+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
216+
217+
mvc.perform(
218+
get(
219+
"/organizations/o-1233456azer/workspaces/wrong-workspaceId/runners/s-123456azerty/runs/run-123456azerty")
220+
.contentType(MediaType.APPLICATION_JSON)
221+
.accept(MediaType.APPLICATION_JSON))
222+
.andExpect(status().isBadRequest)
223+
.andExpect(
224+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
225+
226+
mvc.perform(
227+
get(
228+
"/organizations/o-1233456azer/workspaces/w-123456abcdef/runners/wrong-runnerId/runs/run-123456azerty")
229+
.contentType(MediaType.APPLICATION_JSON)
230+
.accept(MediaType.APPLICATION_JSON))
231+
.andExpect(status().isBadRequest)
232+
.andExpect(
233+
jsonPath("$.detail", containsString("wrong-runnerId:must match \"^(r|s)-\\w{10,20}\"")))
234+
235+
mvc.perform(
236+
get(
237+
"/organizations/o-1233456azer/workspaces/w-123456abcdef/runners/r-123456azerty/runs/wrong-runId")
238+
.contentType(MediaType.APPLICATION_JSON)
239+
.accept(MediaType.APPLICATION_JSON))
240+
.andExpect(status().isBadRequest)
241+
.andExpect(
242+
jsonPath("$.detail", containsString("wrong-runId:must match \"^(run|sr)-\\w{10,20}\"")))
243+
244+
mvc.perform(
245+
get(
246+
"/organizations/o-1233456azer/workspaces/w-123456abcdef/runners/s-123456azerty/runs/wrong-runId")
247+
.contentType(MediaType.APPLICATION_JSON)
248+
.accept(MediaType.APPLICATION_JSON))
249+
.andExpect(status().isBadRequest)
250+
.andExpect(
251+
jsonPath("$.detail", containsString("wrong-runId:must match \"^(run|sr)-\\w{10,20}\"")))
252+
}
253+
174254
@Test
175255
@WithMockOauth2User
176256
fun list_runs() {

api/src/integrationTest/kotlin/com/cosmotech/api/home/runner/RunnerControllerTests.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import com.cosmotech.workspace.domain.WorkspaceSolution
4141
import com.cosmotech.workspace.domain.WorkspaceUpdateRequest
4242
import com.ninjasquad.springmockk.SpykBean
4343
import io.mockk.every
44+
import org.hamcrest.core.StringContains.containsString
4445
import org.json.JSONObject
4546
import org.junit.jupiter.api.BeforeEach
4647
import org.junit.jupiter.api.Test
@@ -159,6 +160,59 @@ class RunnerControllerTests : ControllerTestBase() {
159160
.andExpect(status().is2xxSuccessful)
160161
}
161162

163+
@Test
164+
@WithMockOauth2User
165+
fun get_runner_with_wrong_ids_format() {
166+
mvc.perform(
167+
get("/organizations/wrong-orgId/workspaces/wrong-workspaceId/runners/wrong-runnerId")
168+
.contentType(MediaType.APPLICATION_JSON)
169+
.accept(MediaType.APPLICATION_JSON))
170+
.andExpect(status().isBadRequest)
171+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
172+
.andExpect(
173+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
174+
.andExpect(
175+
jsonPath("$.detail", containsString("wrong-runnerId:must match \"^(r|s)-\\w{10,20}\"")))
176+
177+
mvc.perform(
178+
get("/organizations/wrong-orgId/workspaces/w-123456abcdef/runners/r-123456azerty")
179+
.contentType(MediaType.APPLICATION_JSON)
180+
.accept(MediaType.APPLICATION_JSON))
181+
.andExpect(status().isBadRequest)
182+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
183+
184+
mvc.perform(
185+
get("/organizations/wrong-orgId/workspaces/w-123456abcdef/runners/s-123456azerty")
186+
.contentType(MediaType.APPLICATION_JSON)
187+
.accept(MediaType.APPLICATION_JSON))
188+
.andExpect(status().isBadRequest)
189+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
190+
191+
mvc.perform(
192+
get("/organizations/o-1233456azer/workspaces/wrong-workspaceId/runners/r-123456azerty")
193+
.contentType(MediaType.APPLICATION_JSON)
194+
.accept(MediaType.APPLICATION_JSON))
195+
.andExpect(status().isBadRequest)
196+
.andExpect(
197+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
198+
199+
mvc.perform(
200+
get("/organizations/o-1233456azer/workspaces/wrong-workspaceId/runners/s-123456azerty")
201+
.contentType(MediaType.APPLICATION_JSON)
202+
.accept(MediaType.APPLICATION_JSON))
203+
.andExpect(status().isBadRequest)
204+
.andExpect(
205+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
206+
207+
mvc.perform(
208+
get("/organizations/o-1233456azer/workspaces/w-123456abcdef/runners/wrong-runnerId")
209+
.contentType(MediaType.APPLICATION_JSON)
210+
.accept(MediaType.APPLICATION_JSON))
211+
.andExpect(status().isBadRequest)
212+
.andExpect(
213+
jsonPath("$.detail", containsString("wrong-runnerId:must match \"^(r|s)-\\w{10,20}\"")))
214+
}
215+
162216
@Test
163217
@WithMockOauth2User
164218
fun create_runner() {

api/src/integrationTest/kotlin/com/cosmotech/api/home/solution/SolutionControllerTests.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.cosmotech.solution.api.SolutionApiService
2525
import com.cosmotech.solution.domain.*
2626
import io.mockk.every
2727
import io.mockk.mockk
28+
import org.hamcrest.core.StringContains.containsString
2829
import org.json.JSONObject
2930
import org.junit.jupiter.api.BeforeEach
3031
import org.junit.jupiter.api.Test
@@ -59,6 +60,34 @@ class SolutionControllerTests : ControllerTestBase() {
5960
organizationId = createOrganizationAndReturnId(mvc, constructOrganizationCreateRequest())
6061
}
6162

63+
@Test
64+
@WithMockOauth2User
65+
fun get_solution_with_wrong_ids_format() {
66+
mvc.perform(
67+
get("/organizations/wrong-orgId/solutions/wrong-solutionId")
68+
.contentType(MediaType.APPLICATION_JSON)
69+
.accept(MediaType.APPLICATION_JSON))
70+
.andExpect(status().isBadRequest)
71+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
72+
.andExpect(
73+
jsonPath("$.detail", containsString("wrong-solutionId:must match \"^sol-\\w{10,20}\"")))
74+
75+
mvc.perform(
76+
get("/organizations/wrong-orgId/solutions/sol-123456abcdef")
77+
.contentType(MediaType.APPLICATION_JSON)
78+
.accept(MediaType.APPLICATION_JSON))
79+
.andExpect(status().isBadRequest)
80+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
81+
82+
mvc.perform(
83+
get("/organizations/o-123456abcdef/solutions/wrong-solutionId")
84+
.contentType(MediaType.APPLICATION_JSON)
85+
.accept(MediaType.APPLICATION_JSON))
86+
.andExpect(status().isBadRequest)
87+
.andExpect(
88+
jsonPath("$.detail", containsString("wrong-solutionId:must match \"^sol-\\w{10,20}\"")))
89+
}
90+
6291
@Test
6392
@WithMockOauth2User
6493
fun list_solutions() {

api/src/integrationTest/kotlin/com/cosmotech/api/home/workspace/WorkspaceControllerTests.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.cosmotech.common.rbac.ROLE_VIEWER
2222
import com.cosmotech.workspace.domain.WorkspaceAccessControl
2323
import com.cosmotech.workspace.domain.WorkspaceSecurity
2424
import org.apache.commons.io.IOUtils
25+
import org.hamcrest.core.StringContains.containsString
2526
import org.json.JSONObject
2627
import org.junit.jupiter.api.BeforeEach
2728
import org.junit.jupiter.api.Test
@@ -53,6 +54,34 @@ class WorkspaceControllerTests : ControllerTestBase() {
5354
solutionId = createSolutionAndReturnId(mvc, organizationId, constructSolutionCreateRequest())
5455
}
5556

57+
@Test
58+
@WithMockOauth2User
59+
fun get_workspace_with_wrong_ids_format() {
60+
mvc.perform(
61+
get("/organizations/wrong-orgId/workspaces/wrong-workspaceId")
62+
.contentType(MediaType.APPLICATION_JSON)
63+
.accept(MediaType.APPLICATION_JSON))
64+
.andExpect(status().isBadRequest)
65+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
66+
.andExpect(
67+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
68+
69+
mvc.perform(
70+
get("/organizations/wrong-orgId/workspaces/w-123456abcdef")
71+
.contentType(MediaType.APPLICATION_JSON)
72+
.accept(MediaType.APPLICATION_JSON))
73+
.andExpect(status().isBadRequest)
74+
.andExpect(jsonPath("$.detail", containsString("wrong-orgId:must match \"^o-\\w{10,20}\"")))
75+
76+
mvc.perform(
77+
get("/organizations/o-123456abcdef/workspaces/wrong-workspaceId")
78+
.contentType(MediaType.APPLICATION_JSON)
79+
.accept(MediaType.APPLICATION_JSON))
80+
.andExpect(status().isBadRequest)
81+
.andExpect(
82+
jsonPath("$.detail", containsString("wrong-workspaceId:must match \"^w-\\w{10,20}\"")))
83+
}
84+
5685
@Test
5786
@WithMockOauth2User
5887
fun create_workspace() {

common/src/main/kotlin/com/cosmotech/common/exceptions/CsmExceptionHandling.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
package com.cosmotech.common.exceptions
44

55
import io.awspring.cloud.s3.S3Exception
6+
import jakarta.validation.ConstraintViolationException
67
import java.net.URI
78
import org.apache.commons.lang3.NotImplementedException
9+
import org.hibernate.validator.internal.engine.ConstraintViolationImpl
810
import org.springframework.core.Ordered
911
import org.springframework.core.annotation.Order
1012
import org.springframework.http.HttpHeaders
@@ -129,6 +131,20 @@ open class CsmExceptionHandling : ResponseEntityExceptionHandler() {
129131
return problemDetail
130132
}
131133

134+
@ExceptionHandler
135+
fun handleConstraintViolationException(exception: ConstraintViolationException): ProblemDetail {
136+
val badRequestStatus = HttpStatus.BAD_REQUEST
137+
val problemDetail = ProblemDetail.forStatus(badRequestStatus)
138+
problemDetail.type = URI.create(httpStatusCodeTypePrefix + badRequestStatus.value())
139+
val constraintViolations = exception.constraintViolations
140+
problemDetail.detail =
141+
constraintViolations.joinToString {
142+
val constraint = (it as ConstraintViolationImpl)
143+
"${constraint.invalidValue}:${constraint.message}"
144+
}
145+
return problemDetail
146+
}
147+
132148
@ExceptionHandler(AuthenticationServiceException::class)
133149
fun handleAuthenticationServiceException(
134150
exception: AuthenticationServiceException

0 commit comments

Comments
 (0)