feat(academic): защита активов материалов
This commit is contained in:
@@ -131,12 +131,14 @@ fun ApiClient.deleteContentArticle(
|
||||
}
|
||||
}
|
||||
|
||||
fun ApiClient.contentAsset(name: String): DownloadDto {
|
||||
fun ApiClient.contentAsset(
|
||||
name: String,
|
||||
token: String? = null,
|
||||
): DownloadDto {
|
||||
val entity =
|
||||
call {
|
||||
client
|
||||
.get()
|
||||
.uri("/api/v1/content/assets/{name}", name)
|
||||
val spec = client.get().uri("/api/v1/content/assets/{name}", name)
|
||||
(if (token != null) spec.header(HttpHeaders.AUTHORIZATION, bearer(token)) else spec)
|
||||
.retrieve()
|
||||
.toEntity(ByteArray::class.java)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ru.ysu.lms.web.learn
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
@@ -7,10 +8,13 @@ import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import ru.ysu.lms.web.api.ApiClient
|
||||
import ru.ysu.lms.web.api.contentAsset
|
||||
import ru.ysu.lms.web.session.SessionKeys
|
||||
|
||||
/**
|
||||
* Проксирует картинки статей (public path) через BFF: страницы /learn/...
|
||||
* отдаются web, а src картинок указывает на этот же хост.
|
||||
* Проксирует картинки статей через BFF: страницы /learn/... отдаются web,
|
||||
* а src картинок указывает на этот же хост. Токен из web-сессии нужен,
|
||||
* чтобы academic-service проверил доступ к активам курсовых статей;
|
||||
* для публичных страниц токена нет — прокси остаётся анонимным.
|
||||
*/
|
||||
@RestController
|
||||
class ContentAssetProxy(
|
||||
@@ -19,8 +23,10 @@ class ContentAssetProxy(
|
||||
@GetMapping("/api/v1/content/assets/{name}")
|
||||
fun asset(
|
||||
@PathVariable name: String,
|
||||
request: HttpServletRequest,
|
||||
): ResponseEntity<ByteArray> {
|
||||
val asset = api.contentAsset(name)
|
||||
val token = request.getSession(false)?.getAttribute(SessionKeys.TOKEN) as? String
|
||||
val asset = api.contentAsset(name, token)
|
||||
val media =
|
||||
runCatching { MediaType.parseMediaType(asset.contentType) }
|
||||
.getOrElse { MediaType.APPLICATION_OCTET_STREAM }
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package ru.ysu.lms.web.api
|
||||
|
||||
import com.sun.net.httpserver.HttpServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class ApiClientContentAssetTest {
|
||||
@Test
|
||||
fun `contentAsset sends bearer token when session token is present`() {
|
||||
val authorization = AtomicReference<String?>()
|
||||
withAssetServer(authorization) { baseUrl ->
|
||||
val asset = ApiClient(baseUrl, ObjectMapper()).contentAsset("picture.png", "session-token")
|
||||
|
||||
assertThat(authorization.get()).isEqualTo("Bearer session-token")
|
||||
assertThat(asset.bytes).containsExactly(1, 2, 3)
|
||||
assertThat(asset.contentType).isEqualTo("image/png")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contentAsset stays anonymous for public pages without a session token`() {
|
||||
val authorization = AtomicReference<String?>()
|
||||
withAssetServer(authorization) { baseUrl ->
|
||||
val asset = ApiClient(baseUrl, ObjectMapper()).contentAsset("picture.png")
|
||||
|
||||
assertThat(authorization.get()).isNull()
|
||||
assertThat(asset.bytes).containsExactly(1, 2, 3)
|
||||
}
|
||||
}
|
||||
|
||||
private fun withAssetServer(
|
||||
authorization: AtomicReference<String?>,
|
||||
block: (String) -> Unit,
|
||||
) {
|
||||
val server = HttpServer.create(InetSocketAddress("127.0.0.1", 0), 0)
|
||||
server.createContext("/api/v1/content/assets/") { exchange ->
|
||||
authorization.set(exchange.requestHeaders.getFirst("Authorization"))
|
||||
val body = byteArrayOf(1, 2, 3)
|
||||
exchange.responseHeaders.add("Content-Type", "image/png")
|
||||
exchange.sendResponseHeaders(200, body.size.toLong())
|
||||
exchange.responseBody.use { it.write(body) }
|
||||
}
|
||||
server.start()
|
||||
try {
|
||||
block("http://127.0.0.1:${server.address.port}")
|
||||
} finally {
|
||||
server.stop(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user