Skip to content

Commit

Permalink
Hide all backend endpoints under /api and /internal (#2381)
Browse files Browse the repository at this point in the history
### What's done:
 * Changed `/demo/api` -> `/api/demo`
 * Changed `/cpg/api` -> `/api/cpg`
 * Changed `/sandbox/api` -> `/api/sandbox`
 * Changed `/demo/internal` -> `/internal/demo`
 * Changed `/sandbox/internal` -> `/internal/sandbox`
 * Added BackendRoutes in order to forbid some high-level url path segments e.g. `api`, `sec`, `grafana` etc.
 * Reworked `App` to be `FC`
  • Loading branch information
sanyavertolet authored Jul 28, 2023
1 parent af56acc commit dd1d895
Show file tree
Hide file tree
Showing 22 changed files with 267 additions and 279 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class WebSecurityConfig(
// all requests to backend are permitted on gateway, if user agent is authenticated in gateway or doesn't have
// any authentication data at all.
// backend returns 401 for those endpoints that require authentication
.pathMatchers("/api/**", "/sandbox/api/**", "/demo/api/**")
.pathMatchers("/api/**")
.access { authentication, authorizationContext ->
AuthenticatedReactiveAuthorizationManager.authenticated<AuthorizationContext>().check(
authentication, authorizationContext
Expand Down Expand Up @@ -98,15 +98,15 @@ class WebSecurityConfig(
}
.exceptionHandling {
it.authenticationEntryPoint(
// return 401 for unauthorized requests instead of redirect to login
// return 401 for unauthorized requests instead of redirect to log-in
HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)
)
}
.oauth2Login {
it.authenticationSuccessHandler(
DelegatingServerAuthenticationSuccessHandler(
StoringServerAuthenticationSuccessHandler(backendService),
RedirectServerAuthenticationSuccessHandler("/#"),
RedirectServerAuthenticationSuccessHandler("/"),
)
)
it.authenticationFailureHandler(
Expand Down
4 changes: 2 additions & 2 deletions api-gateway/src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ spring:
scope:
- openid
github:
# IMPORTANT! When you create a github application on the GITHUB you need to provide a callback API.
# IMPORTANT! When you create a GitHub application on the GITHUB you need to provide a callback API.
# If you will only provide a domain name: 'saveourtool.com' in CALLBACK URL field, everything will go well.
# Github will automatically add all the remaining endpoint information (after the domain name): /login/oauth2/code/github
# GitHub will automatically add all the remaining endpoint information (after the domain name): /login/oauth2/code/github
#
# We don't set any redirect-uri here, because it's managed by GitHub
# (the field is called "Authorization callback URL" and, for a local
Expand Down
46 changes: 23 additions & 23 deletions api-gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,52 +27,52 @@ spring:
cloud:
gateway:
routes:
- id: api_route
uri: ${gateway.backend.url}
- id: sandbox-api_route
uri: ${gateway.sandbox.url}
predicates:
- Path=/api/**
- Path=/api/sandbox/**
filters:
# If SESSION cookie is passed to downstream, it is then removed, because downstream discards it
- RemoveRequestHeader=Cookie
- ConvertAuthorizationHeader=
- id: index_route
uri: ${gateway.frontend.url}/index.html
- id: demo-api_route
uri: ${gateway.demo.url}
predicates:
- Path=/
- Path=/api/demo/**
filters:
# If SESSION cookie is passed to downstream, it is then removed, because downstream discards it
- RemoveRequestHeader=Cookie
- id: error_route
uri: ${gateway.backend.url}/error
predicates:
- Path=/error
- id: resource_route
uri: ${gateway.frontend.url}
- ConvertAuthorizationHeader=
- id: demo-cpg-api_route
uri: ${gateway.demo-cpg.url}
predicates:
- Path=/*.html,/*.js*,/*.css,/img/**,/*.ico,/*.png
- Path=/api/cpg/**
filters:
# If SESSION cookie is passed to downstream, it is then removed, because downstream discards it
- RemoveRequestHeader=Cookie
- id: sandbox-api_route
uri: ${gateway.sandbox.url}
- id: api_route
uri: ${gateway.backend.url}
predicates:
- Path=/sandbox/api/**
- Path=/api/**
filters:
# If SESSION cookie is passed to downstream, it is then removed, because downstream discards it
- RemoveRequestHeader=Cookie
- ConvertAuthorizationHeader=
- id: demo-api_route
uri: ${gateway.demo.url}
- id: index_route
uri: ${gateway.frontend.url}/index.html
predicates:
- Path=/demo/api/**
- Path=/
filters:
# If SESSION cookie is passed to downstream, it is then removed, because downstream discards it
- RemoveRequestHeader=Cookie
- ConvertAuthorizationHeader=
- id: demo-cpg-api_route
uri: ${gateway.demo-cpg.url}
- id: error_route
uri: ${gateway.backend.url}/error
predicates:
- Path=/cpg/api/**
- Path=/error
- id: resource_route
uri: ${gateway.frontend.url}
predicates:
- Path=/*.html,/*.js*,/*.css,/img/**,/*.ico,/*.png
filters:
# If SESSION cookie is passed to downstream, it is then removed, because downstream discards it
- RemoveRequestHeader=Cookie
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ class WebSecurityConfig(
// they are not proxied from gateway.
"/actuator/**",
"/internal/**",
"/sandbox/internal/**",
// Agents should communicate with sandbox without authorization
"/heartbeat",
// `CollectionView` is a public page
Expand All @@ -103,7 +102,7 @@ class WebSecurityConfig(
"/api/$v1/contests/*/public-test",
"/api/$v1/contests/*/scores",
"/api/$v1/contests/*/*/best",
"/demo/api/*/run",
"/api/demo/*/run",
"/api/$v1/vulnerabilities/get-all-public",
// `fossGraphView` is public page
"/api/$v1/vulnerabilities/by-name-with-description",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class DemoManagerController(
}
.flatMap {
webClientDemo.post()
.uri("/demo/internal/manager/save-or-update")
.uri("/internal/demo/manager/save-or-update")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(demoCreationRequest.demoDto)
.retrieve()
Expand All @@ -113,7 +113,7 @@ class DemoManagerController(
}
.doOnSuccess {
webClientDemo.post()
.uri("/demo/internal/files/${demoCreationRequest.demoDto.projectCoordinates}/upload?version=manual")
.uri("/internal/demo/files/${demoCreationRequest.demoDto.projectCoordinates}/upload?version=manual")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(demoCreationRequest.manuallyUploadedFileDtos)
.retrieve()
Expand Down Expand Up @@ -146,7 +146,7 @@ class DemoManagerController(
authentication: Authentication,
): Mono<FileDto> = forwardRequestCheckingPermission(Permission.DELETE, organizationName, projectName, authentication) {
webClientDemo.post()
.uri("/demo/internal/files/$organizationName/$projectName/upload?version=$version")
.uri("/internal/demo/files/$organizationName/$projectName/upload?version=$version")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData("file", file))
.retrieve()
Expand Down Expand Up @@ -182,7 +182,7 @@ class DemoManagerController(
}
.flatMap {
webClientDemo.get()
.uri("/demo/internal/files/$organizationName/$projectName/list-file?version=$version")
.uri("/internal/demo/files/$organizationName/$projectName/list-file?version=$version")
.retrieve()
.defaultNotFoundProcessing(organizationName, projectName)
.bodyToMono<List<FileDto>>()
Expand Down Expand Up @@ -213,7 +213,7 @@ class DemoManagerController(
authentication: Authentication,
): Mono<Boolean> = forwardRequestCheckingPermission(Permission.DELETE, organizationName, projectName, authentication) {
webClientDemo.delete()
.uri("/demo/internal/files/$organizationName/$projectName/delete?version=$version&fileName=$fileName")
.uri("/internal/demo/files/$organizationName/$projectName/delete?version=$version&fileName=$fileName")
.retrieve()
.defaultNotFoundProcessing(organizationName, projectName)
.bodyToMono()
Expand All @@ -239,7 +239,7 @@ class DemoManagerController(
authentication: Authentication,
): Mono<StringResponse> = forwardRequestCheckingPermission(Permission.DELETE, organizationName, projectName, authentication) { project ->
webClientDemo.post()
.uri("/demo/internal/manager/${project.toProjectCoordinates()}/delete")
.uri("/internal/demo/manager/${project.toProjectCoordinates()}/delete")
.retrieve()
.defaultNotFoundProcessing(organizationName, projectName)
.toEntity()
Expand All @@ -265,7 +265,7 @@ class DemoManagerController(
authentication: Authentication,
): Mono<StringResponse> = forwardRequestCheckingPermission(Permission.WRITE, organizationName, projectName, authentication) { project ->
webClientDemo.post()
.uri("/demo/internal/manager/${project.toProjectCoordinates()}/start")
.uri("/internal/demo/manager/${project.toProjectCoordinates()}/start")
.retrieve()
.defaultNotFoundProcessing(organizationName, projectName)
.toEntity()
Expand All @@ -291,7 +291,7 @@ class DemoManagerController(
authentication: Authentication,
): Mono<StringResponse> = forwardRequestCheckingPermission(Permission.WRITE, organizationName, projectName, authentication) { project ->
webClientDemo.post()
.uri("/demo/internal/manager/${project.toProjectCoordinates()}/stop")
.uri("/internal/demo/manager/${project.toProjectCoordinates()}/stop")
.retrieve()
.defaultNotFoundProcessing(organizationName, projectName)
.toEntity()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Names that are used as endpoints in the frontend.
* If you create a new view with new URL - add it here.
*/

package com.saveourtool.save.validation

import com.saveourtool.save.utils.URL_PATH_DELIMITER
import kotlin.js.JsExport

/**
* It is required to forbid creating organizations and users with such names
*
* @property path substring of url that defines given route
*/
@JsExport
@Suppress("unused", "WRONG_DECLARATIONS_ORDER")
enum class BackendRoutes(val path: String) {
API("api"),
INTERNAL("internal"),
ACTUATOR("actuator"),
GRAFANA("grafana"),
ERROR("error"),
LOGIN("login"),
LOGOUT("logout"),
SEC("sec"),
NEO4J("neo4j"),
;

override fun toString(): String = path

companion object {
/**
* Get forbidden words from [BackendRoutes].
*
* @return list of forbidden words
*/
fun getForbiddenWords() = BackendRoutes.values()
.map { it.path.split(URL_PATH_DELIMITER) }
.flatten()
.toTypedArray()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ fun String.isValidEmail() = ValidationRegularExpressions.EMAIL_VALIDATOR.value.m

private fun String.hasOnlyAlphaNumOrAllowedSpecialSymbols() = all { it.isLetterOrDigit() || namingAllowedSpecialSymbols.contains(it) }

private fun String.containsForbiddenWords() = FrontendRoutes.getForbiddenWords().any { this == it }
private fun String.containsForbiddenWords() = (FrontendRoutes.getForbiddenWords() + BackendRoutes.getForbiddenWords())
.any { this == it }

private fun String.isLengthOk(allowedLength: Int) = length < allowedLength
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ private fun executeSetupSh(setupShTimeoutMillis: Long, setupShName: String = "se
}

private suspend fun downloadDemoFiles(demoUrl: String, demoConfiguration: DemoConfiguration): Path {
val url = with(demoConfiguration) { "$demoUrl/demo/internal/files/$organizationName/$projectName/download-as-zip?version=$version" }
val url = with(demoConfiguration) { "$demoUrl/internal/demo/files/$organizationName/$projectName/download-as-zip?version=$version" }
return downloadDemoFiles(url)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const val FILE_NAME_SEPARATOR = "==="
Tag(name = "cpg-demo"),
)
@RestController
@RequestMapping("/cpg/api")
@RequestMapping("/api/cpg")
@ExperimentalSerializationApi
class CpgController(
val configProperties: ConfigProperties,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import reactor.kotlin.core.util.function.component2
Tag(name = "demo"),
)
@RestController
@RequestMapping("/demo/api")
@RequestMapping("/api/demo")
class DemoController(
private val demoService: DemoService,
private val demoRunnerFactory: RunnerFactory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import kotlinx.datetime.toKotlinLocalDateTime
* Internal controller that allows to upload files to save-demo
*/
@RestController
@RequestMapping("/demo/internal/files")
@RequestMapping("/internal/demo/files")
class DependencyController(
private val demoService: DemoService,
private val downloadToolService: DownloadToolService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import reactor.kotlin.core.util.function.component2
Tag(name = "management"),
)
@RestController
@RequestMapping("/demo/api/manager")
@RequestMapping("/api/demo/manager")
class ManagementApiController(
private val toolService: ToolService,
private val demoService: DemoService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import reactor.kotlin.core.util.function.component2
* Internal controller that allows to create demos
*/
@RestController
@RequestMapping("/demo/internal/manager")
@RequestMapping("/internal/demo/manager")
class ManagementController(
private val downloadToolService: DownloadToolService,
private val demoService: DemoService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ fun serviceNameForDemo(demo: Demo) = with(demo) {

@Suppress("SameParameterValue")
private fun getConfigureMeUrl(baseUrl: String, demo: Demo, version: String) = with(demo) {
"$baseUrl/demo/internal/manager/$organizationName/$projectName/configure-me?version=$version"
"$baseUrl/internal/demo/manager/$organizationName/$projectName/configure-me?version=$version"
}

@Suppress("TOO_LONG_FUNCTION")
Expand Down
Loading

0 comments on commit dd1d895

Please sign in to comment.