diff --git a/ok-marketplace-acceptance/src/test/kotlin/docker/DebugDockerCompose.kt b/ok-marketplace-acceptance/src/test/kotlin/docker/DebugDockerCompose.kt new file mode 100644 index 0000000..1fbd0dc --- /dev/null +++ b/ok-marketplace-acceptance/src/test/kotlin/docker/DebugDockerCompose.kt @@ -0,0 +1,20 @@ +package ru.otus.otuskotlin.marketplace.blackbox.docker + +import io.ktor.http.* +import ru.otus.otuskotlin.marketplace.blackbox.fixture.docker.DockerCompose + +// для отладки тестов, предполагается, что докер-компоуз запущен вручную +object DebugDockerCompose : DockerCompose { + override fun start() { + } + + override fun stop() { + } + + override val inputUrl: URLBuilder + get() = URLBuilder( + protocol = URLProtocol.HTTP, + host = "localhost", + port = 8080, + ) +} \ No newline at end of file diff --git a/ok-marketplace-acceptance/src/test/kotlin/fixture/client/RestClient.kt b/ok-marketplace-acceptance/src/test/kotlin/fixture/client/RestClient.kt index 86c5e17..b4246d4 100644 --- a/ok-marketplace-acceptance/src/test/kotlin/fixture/client/RestClient.kt +++ b/ok-marketplace-acceptance/src/test/kotlin/fixture/client/RestClient.kt @@ -8,6 +8,8 @@ import io.ktor.http.* import ru.otus.otuskotlin.marketplace.blackbox.fixture.client.Client import ru.otus.otuskotlin.marketplace.blackbox.fixture.docker.DockerCompose +private const val TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhZC11c2VycyIsImlzcyI6Ik90dXNLb3RsaW4iLCJncm91cHMiOlsiVVNFUiJdfQ.Ef_RcXDSuVU4P9bEDH5FwUrPioToz3H_Plylpuc2C1M" + /** * Отправка запросов по http/rest */ @@ -23,6 +25,7 @@ class RestClient(dockerCompose: DockerCompose) : Client { url(url) headers { append(HttpHeaders.ContentType, ContentType.Application.Json) + append(HttpHeaders.Authorization, "Bearer $TOKEN") } accept(ContentType.Application.Json) setBody(request) diff --git a/ok-marketplace-app-common/src/commonMain/kotlin/AuthConfig.kt b/ok-marketplace-app-common/src/commonMain/kotlin/AuthConfig.kt new file mode 100644 index 0000000..81e9cd2 --- /dev/null +++ b/ok-marketplace-app-common/src/commonMain/kotlin/AuthConfig.kt @@ -0,0 +1,34 @@ +package ru.otus.otuskotlin.marketplace.app.common + +data class AuthConfig( + val secret: String, + val issuer: String, + val audience: String, + val realm: String, + val clientId: String, + val certUrl: String? = null, +) { + companion object { + const val ID_CLAIM = "sub" + const val GROUPS_CLAIM = "groups" + const val F_NAME_CLAIM = "fname" + const val M_NAME_CLAIM = "mname" + const val L_NAME_CLAIM = "lname" + + val TEST = AuthConfig( + secret = "secret", + issuer = "OtusKotlin", + audience = "ad-users", + realm = "otus-marketplace", + clientId = "otus-marketplace-service", + ) + + val NONE = AuthConfig( + secret = "", + issuer = "", + audience = "", + realm = "", + clientId = "", + ) + } +} diff --git a/ok-marketplace-app-common/src/commonMain/kotlin/MkplAppSettings.kt b/ok-marketplace-app-common/src/commonMain/kotlin/MkplAppSettings.kt index 00e58b1..3206b36 100644 --- a/ok-marketplace-app-common/src/commonMain/kotlin/MkplAppSettings.kt +++ b/ok-marketplace-app-common/src/commonMain/kotlin/MkplAppSettings.kt @@ -8,5 +8,6 @@ data class MkplAppSettings( val appUrls: List = emptyList(), val corSettings: MkplCorSettings = MkplCorSettings(), val processor: MkplAdProcessor = MkplAdProcessor(corSettings), - val logger: MpLoggerProvider = MpLoggerProvider() + val logger: MpLoggerProvider = MpLoggerProvider(), + val auth: AuthConfig = AuthConfig.NONE, ) \ No newline at end of file diff --git a/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt b/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt index 629bf3b..b62223b 100644 --- a/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt +++ b/ok-marketplace-app-ktor/src/commonMain/kotlin/plugins/InitAppSettings.kt @@ -1,12 +1,12 @@ package ru.otus.otuskotlin.marketplace.app.plugins import io.ktor.server.application.* +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings import ru.otus.otuskotlin.marketplace.backend.repository.inmemory.AdRepoStub import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor import ru.otus.otuskotlin.marketplace.common.MkplCorSettings import ru.otus.otuskotlin.marketplace.logging.common.MpLoggerProvider -import ru.otus.otuskotlin.marketplace.repo.inmemory.AdRepoInMemory fun Application.initAppSettings(): MkplAppSettings { val corSettings = MkplCorSettings( @@ -19,6 +19,16 @@ fun Application.initAppSettings(): MkplAppSettings { appUrls = environment.config.propertyOrNull("ktor.urls")?.getList() ?: emptyList(), processor = MkplAdProcessor(corSettings), logger = getLoggerProviderConf(), + auth = initAppAuth(), ) } expect fun Application.getLoggerProviderConf(): MpLoggerProvider + +private fun Application.initAppAuth(): AuthConfig = AuthConfig( + secret = environment.config.propertyOrNull("jwt.secret")?.getString() ?: "", + issuer = environment.config.property("jwt.issuer").getString(), + audience = environment.config.property("jwt.audience").getString(), + realm = environment.config.property("jwt.realm").getString(), + clientId = environment.config.property("jwt.clientId").getString(), + certUrl = environment.config.propertyOrNull("jwt.certUrl")?.getString(), +) diff --git a/ok-marketplace-app-ktor/src/commonMain/kotlin/v2/ControllerHelperV2.kt b/ok-marketplace-app-ktor/src/commonMain/kotlin/v2/ControllerHelperV2.kt index a5e8745..ec5ea5f 100644 --- a/ok-marketplace-app-ktor/src/commonMain/kotlin/v2/ControllerHelperV2.kt +++ b/ok-marketplace-app-ktor/src/commonMain/kotlin/v2/ControllerHelperV2.kt @@ -8,6 +8,9 @@ import ru.otus.otuskotlin.marketplace.api.v2.models.IRequest import ru.otus.otuskotlin.marketplace.api.v2.models.IResponse import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings import ru.otus.otuskotlin.marketplace.app.common.process +import ru.otus.otuskotlin.marketplace.common.models.MkplUserId +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalModel +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserGroups import ru.otus.otuskotlin.marketplace.mappers.v2.fromTransport import ru.otus.otuskotlin.marketplace.mappers.v2.toTransportAd import kotlin.reflect.KClass @@ -20,8 +23,19 @@ suspend inline fun () + principal = mkplPrincipal(appSettings) fromTransport(request) }, sendResponse = { respond(toTransportAd()) } ) } + +// TODO: костыль для решения проблемы отсутствия jwt в native +@Suppress("UnusedReceiverParameter", "UNUSED_PARAMETER") +fun ApplicationCall.mkplPrincipal(appSettings: MkplAppSettings): MkplPrincipalModel = MkplPrincipalModel( + id = MkplUserId("user-1"), + fname = "Ivan", + mname = "Ivanovich", + lname = "Ivanov", + groups = setOf(MkplUserGroups.TEST, MkplUserGroups.USER), +) diff --git a/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V2AdStubApiTest.kt b/ok-marketplace-app-ktor/src/commonTest/kotlin/V2AdStubApiTest.kt similarity index 87% rename from ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V2AdStubApiTest.kt rename to ok-marketplace-app-ktor/src/commonTest/kotlin/V2AdStubApiTest.kt index 9e0064e..a869456 100644 --- a/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V2AdStubApiTest.kt +++ b/ok-marketplace-app-ktor/src/commonTest/kotlin/V2AdStubApiTest.kt @@ -1,4 +1,4 @@ -package ru.otus.otuskotlin.marketplace.app.stubs +package ru.otus.otuskotlin.marketplace.app import io.ktor.client.request.* import io.ktor.client.statement.* @@ -6,15 +6,19 @@ import io.ktor.http.* import io.ktor.server.testing.* import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString -import org.junit.Test import ru.otus.otuskotlin.marketplace.api.v2.apiV2Mapper import ru.otus.otuskotlin.marketplace.api.v2.models.* +import ru.otus.otuskotlin.marketplace.app.auth.addAuth +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig +import ru.otus.otuskotlin.marketplace.app.helpers.testSettings +import kotlin.test.Test import kotlin.test.assertEquals class V2AdStubApiTest { @Test fun create() = testApplication { + application { module(testSettings()) } val response = client.post("/v2/ad/create") { val requestObj = AdCreateRequest( requestId = "12345", @@ -30,6 +34,7 @@ class V2AdStubApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } @@ -41,6 +46,7 @@ class V2AdStubApiTest { @Test fun read() = testApplication { + application { module(testSettings()) } val response = client.post("/v2/ad/read") { val requestObj = AdReadRequest( requestId = "12345", @@ -51,6 +57,7 @@ class V2AdStubApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } @@ -62,6 +69,7 @@ class V2AdStubApiTest { @Test fun update() = testApplication { + application { module(testSettings()) } val response = client.post("/v2/ad/update") { val requestObj = AdUpdateRequest( requestId = "12345", @@ -78,6 +86,7 @@ class V2AdStubApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } @@ -89,6 +98,7 @@ class V2AdStubApiTest { @Test fun delete() = testApplication { + application { module(testSettings()) } val response = client.post("/v2/ad/delete") { val requestObj = AdDeleteRequest( requestId = "12345", @@ -102,6 +112,7 @@ class V2AdStubApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } @@ -113,6 +124,7 @@ class V2AdStubApiTest { @Test fun search() = testApplication { + application { module(testSettings()) } val response = client.post("/v2/ad/search") { val requestObj = AdSearchRequest( requestId = "12345", @@ -123,6 +135,7 @@ class V2AdStubApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } @@ -134,6 +147,7 @@ class V2AdStubApiTest { @Test fun offers() = testApplication { + application { module(testSettings()) } val response = client.post("/v2/ad/offers") { val requestObj = AdOffersRequest( requestId = "12345", @@ -145,6 +159,7 @@ class V2AdStubApiTest { stub = AdRequestDebugStubs.SUCCESS ) ) + addAuth(config = AuthConfig.TEST) contentType(ContentType.Application.Json) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) diff --git a/ok-marketplace-app-ktor/src/commonTest/kotlin/auth/authHelpers.kt b/ok-marketplace-app-ktor/src/commonTest/kotlin/auth/authHelpers.kt new file mode 100644 index 0000000..9939e8f --- /dev/null +++ b/ok-marketplace-app-ktor/src/commonTest/kotlin/auth/authHelpers.kt @@ -0,0 +1,10 @@ +package ru.otus.otuskotlin.marketplace.app.auth + +import io.ktor.client.request.* +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig + +expect fun HttpRequestBuilder.addAuth( + id: String = "user1", + groups: List = listOf("USER", "TEST"), + config: AuthConfig = AuthConfig.TEST, +) diff --git a/ok-marketplace-app-ktor/src/commonTest/kotlin/helpers/TestHelpers.kt b/ok-marketplace-app-ktor/src/commonTest/kotlin/helpers/TestHelpers.kt new file mode 100644 index 0000000..8a4e29e --- /dev/null +++ b/ok-marketplace-app-ktor/src/commonTest/kotlin/helpers/TestHelpers.kt @@ -0,0 +1,18 @@ +package ru.otus.otuskotlin.marketplace.app.helpers + + +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig +import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings +import ru.otus.otuskotlin.marketplace.backend.repository.inmemory.AdRepoStub +import ru.otus.otuskotlin.marketplace.common.MkplCorSettings +import ru.otus.otuskotlin.marketplace.common.repo.IAdRepository +import ru.otus.otuskotlin.marketplace.repo.inmemory.AdRepoInMemory + +fun testSettings(repo: IAdRepository? = null) = MkplAppSettings( + corSettings = MkplCorSettings( + repoStub = AdRepoStub(), + repoTest = repo ?: AdRepoInMemory(), + repoProd = repo ?: AdRepoInMemory(), + ), + auth = AuthConfig.TEST +) diff --git a/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V2AdInmemoryApiTest.kt b/ok-marketplace-app-ktor/src/commonTest/kotlin/inmemory/V2AdInmemoryApiTest.kt similarity index 57% rename from ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V2AdInmemoryApiTest.kt rename to ok-marketplace-app-ktor/src/commonTest/kotlin/inmemory/V2AdInmemoryApiTest.kt index 6bbeb2a..22c9e65 100644 --- a/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V2AdInmemoryApiTest.kt +++ b/ok-marketplace-app-ktor/src/commonTest/kotlin/inmemory/V2AdInmemoryApiTest.kt @@ -1,6 +1,5 @@ -package ru.otus.otuskotlin.marketplace.app.repo +package ru.otus.otuskotlin.marketplace.app.inmemory -import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* @@ -9,27 +8,70 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import ru.otus.otuskotlin.marketplace.api.v2.apiV2Mapper import ru.otus.otuskotlin.marketplace.api.v2.models.* +import ru.otus.otuskotlin.marketplace.app.auth.addAuth +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig +import ru.otus.otuskotlin.marketplace.app.helpers.testSettings +import ru.otus.otuskotlin.marketplace.app.module +import ru.otus.otuskotlin.marketplace.common.models.MkplAdId +import ru.otus.otuskotlin.marketplace.common.models.MkplAdLock +import ru.otus.otuskotlin.marketplace.common.models.MkplDealSide +import ru.otus.otuskotlin.marketplace.common.models.MkplVisibility +import ru.otus.otuskotlin.marketplace.repo.inmemory.AdRepoInMemory +import ru.otus.otuskotlin.marketplace.stubs.MkplAdStub import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals class V2AdInmemoryApiTest { - private val createAd = AdCreateObject( - title = "Болт", - description = "КРУТЕЙШИЙ", - adType = DealSide.DEMAND, - visibility = AdVisibility.PUBLIC, - ) - private val requestObj = AdCreateRequest( - requestId = "12345", - ad = createAd, - debug = AdDebug( - mode = AdRequestDebugMode.TEST, - ) - ) + private val uuidOld = "10000000-0000-0000-0000-000000000001" + private val uuidNew = "10000000-0000-0000-0000-000000000002" + private val uuidSup = "10000000-0000-0000-0000-000000000003" + private val initAd = MkplAdStub.prepareResult { + id = MkplAdId(uuidOld) + title = "abc" + description = "abc" + adType = MkplDealSide.DEMAND + visibility = MkplVisibility.VISIBLE_PUBLIC + lock = MkplAdLock(uuidOld) + } + private val initAdSupply = MkplAdStub.prepareResult { + id = MkplAdId(uuidSup) + title = "abc" + description = "abc" + adType = MkplDealSide.SUPPLY + visibility = MkplVisibility.VISIBLE_PUBLIC + } + + private val userId = initAd.ownerId + @Test fun create() = testApplication { - val responseObj = initObject(client) + application { module(testSettings(AdRepoInMemory(randomUuid = { uuidNew }))) } + + val createAd = AdCreateObject( + title = "Болт", + description = "КРУТЕЙШИЙ", + adType = DealSide.DEMAND, + visibility = AdVisibility.PUBLIC, + ) + + val response = client.post("/v2/ad/create") { + val requestObj = AdCreateRequest( + requestId = "12345", + ad = createAd, + debug = AdDebug( + mode = AdRequestDebugMode.TEST, + ) + ) + contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) + val requestJson = apiV2Mapper.encodeToString(requestObj) + setBody(requestJson) + } + val responseJson = response.bodyAsText() + val responseObj = apiV2Mapper.decodeFromString(responseJson) + assertEquals(200, response.status.value) + assertEquals(uuidNew, responseObj.ad?.id) assertEquals(createAd.title, responseObj.ad?.title) assertEquals(createAd.description, responseObj.ad?.description) assertEquals(createAd.adType, responseObj.ad?.adType) @@ -38,37 +80,44 @@ class V2AdInmemoryApiTest { @Test fun read() = testApplication { - val adCreateResponse = initObject(client) - val oldId = adCreateResponse.ad?.id - val response = client.post("/v2/ad/read") { + val repo = AdRepoInMemory(initObjects = listOf(initAd), randomUuid = { uuidNew }) + application { + module(testSettings(repo)) + } + val response = client.post("/v2/ad/read") { val requestObj = AdReadRequest( requestId = "12345", - ad = AdReadObject(oldId), + ad = AdReadObject(uuidOld), debug = AdDebug( mode = AdRequestDebugMode.TEST, ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } val responseJson = response.bodyAsText() val responseObj = apiV2Mapper.decodeFromString(responseJson) assertEquals(200, response.status.value) - assertEquals(oldId, responseObj.ad?.id) + assertEquals(uuidOld, responseObj.ad?.id) } @Test fun update() = testApplication { - val initObject = initObject(client) + val repo = AdRepoInMemory(initObjects = listOf(initAd), randomUuid = { uuidNew }) + application { + module(testSettings(repo)) + } + val adUpdate = AdUpdateObject( - id = initObject.ad?.id, + id = uuidOld, title = "Болт", description = "КРУТЕЙШИЙ", adType = DealSide.DEMAND, visibility = AdVisibility.PUBLIC, - lock = initObject.ad?.lock, + lock = initAd.lock.asString(), ) val response = client.post("/v2/ad/update") { @@ -80,6 +129,7 @@ class V2AdInmemoryApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } @@ -95,32 +145,40 @@ class V2AdInmemoryApiTest { @Test fun delete() = testApplication { - val initObject = initObject(client) - val id = initObject.ad?.id + val repo = AdRepoInMemory(initObjects = listOf(initAd), randomUuid = { uuidNew }) + application { + module(testSettings(repo)) + } + val response = client.post("/v2/ad/delete") { val requestObj = AdDeleteRequest( requestId = "12345", ad = AdDeleteObject( - id = id, - lock = initObject.ad?.lock, + id = uuidOld, + lock = initAd.lock.asString(), ), debug = AdDebug( mode = AdRequestDebugMode.TEST, ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } val responseJson = response.bodyAsText() val responseObj = apiV2Mapper.decodeFromString(responseJson) assertEquals(200, response.status.value) - assertEquals(id, responseObj.ad?.id) + assertEquals(uuidOld, responseObj.ad?.id) } @Test fun search() = testApplication { - val initObject = initObject(client) + val repo = AdRepoInMemory(initObjects = listOf(initAd), randomUuid = { uuidNew }) + application { + module(testSettings(repo)) + } + val response = client.post("/v2/ad/search") { val requestObj = AdSearchRequest( requestId = "12345", @@ -130,6 +188,7 @@ class V2AdInmemoryApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } @@ -137,33 +196,28 @@ class V2AdInmemoryApiTest { val responseObj = apiV2Mapper.decodeFromString(responseJson) assertEquals(200, response.status.value) assertNotEquals(0, responseObj.ads?.size) - assertEquals(initObject.ad?.id, responseObj.ads?.first()?.id) - } - private suspend fun initObject(client: HttpClient): AdCreateResponse { - val response = client.post("/v2/ad/create") { - contentType(ContentType.Application.Json) - val requestJson = apiV2Mapper.encodeToString(requestObj) - setBody(requestJson) - } - val responseJson = response.bodyAsText() - assertEquals(200, response.status.value) - return apiV2Mapper.decodeFromString(responseJson) + assertEquals(uuidOld, responseObj.ads?.first()?.id) } @Test fun offers() = testApplication { - val oldId = initObject(client).ad?.id + val repo = AdRepoInMemory(initObjects = listOf(initAd, initAdSupply), randomUuid = { uuidNew }) + application { + module(testSettings(repo)) + } + val response = client.post("/v2/ad/offers") { val requestObj = AdOffersRequest( - requestId = COMMON_REQUEST_ID, + requestId = "12345", ad = AdReadObject( - id = oldId, + id = uuidOld, ), debug = AdDebug( mode = AdRequestDebugMode.TEST, ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) val requestJson = apiV2Mapper.encodeToString(requestObj) setBody(requestJson) } @@ -171,6 +225,6 @@ class V2AdInmemoryApiTest { val responseObj = apiV2Mapper.decodeFromString(responseJson) assertEquals(200, response.status.value) assertNotEquals(0, responseObj.ads?.size) - assertEquals(COMMON_REQUEST_ID, responseObj.requestId) + assertEquals(uuidSup, responseObj.ads?.first()?.id) } } diff --git a/ok-marketplace-app-ktor/src/commonTest/kotlin/package.kt b/ok-marketplace-app-ktor/src/commonTest/kotlin/package.kt new file mode 100644 index 0000000..8728f19 --- /dev/null +++ b/ok-marketplace-app-ktor/src/commonTest/kotlin/package.kt @@ -0,0 +1 @@ +package ru.otus.otuskotlin.marketplace.app diff --git a/ok-marketplace-app-ktor/src/commonTest/resources/application.yaml b/ok-marketplace-app-ktor/src/commonTest/resources/application.yaml new file mode 100644 index 0000000..9706993 --- /dev/null +++ b/ok-marketplace-app-ktor/src/commonTest/resources/application.yaml @@ -0,0 +1,14 @@ +# application.yaml работает в jvm и native, но не работает в режиме сервлета с Tomcat +# в этом случае необходимо сформировать application.conf + +ktor: + development: true + deployment: + port: 8080 + watch: + - classes + - resources +# urls: +# - "http://127.0.0.1:8080/v1" +# - "http://0.0.0.0:8080/v1" +# - "http://192.168.0.182:8080/v1" diff --git a/ok-marketplace-app-ktor/src/jvmMain/kotlin/ApplicationJvm.kt b/ok-marketplace-app-ktor/src/jvmMain/kotlin/ApplicationJvm.kt index b14beb1..17e821d 100644 --- a/ok-marketplace-app-ktor/src/jvmMain/kotlin/ApplicationJvm.kt +++ b/ok-marketplace-app-ktor/src/jvmMain/kotlin/ApplicationJvm.kt @@ -1,21 +1,21 @@ package ru.otus.otuskotlin.marketplace.app -import io.ktor.http.* +import com.auth0.jwt.JWT import io.ktor.serialization.jackson.* import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* import io.ktor.server.http.content.* import io.ktor.server.locations.* -import io.ktor.server.plugins.autohead.* -import io.ktor.server.plugins.cachingheaders.* import io.ktor.server.plugins.callloging.* import io.ktor.server.plugins.contentnegotiation.* -import io.ktor.server.plugins.cors.routing.* -import io.ktor.server.plugins.defaultheaders.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.websocket.* import org.slf4j.event.Level import ru.otus.otuskotlin.marketplace.api.v1.apiV1Mapper +import ru.otus.otuskotlin.marketplace.app.base.resolveAlgorithm +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig.Companion.GROUPS_CLAIM import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings import ru.otus.otuskotlin.marketplace.app.plugins.initAppSettings import ru.otus.otuskotlin.marketplace.app.v1.WsHandlerV1 @@ -49,6 +49,31 @@ fun Application.moduleJvm(appSettings: MkplAppSettings = initAppSettings()) { val handlerV1 = WsHandlerV1() val handlerV2 = WsHandlerV2() + install(Authentication) { + jwt("auth-jwt") { + val authConfig = appSettings.auth + realm = authConfig.realm + + verifier { + val algorithm = it.resolveAlgorithm(authConfig) + JWT + .require(algorithm) + .withAudience(authConfig.audience) + .withIssuer(authConfig.issuer) + .build() + } + validate { jwtCredential: JWTCredential -> + when { + jwtCredential.payload.getClaim(GROUPS_CLAIM).asList(String::class.java).isNullOrEmpty() -> { + this@moduleJvm.log.error("Groups claim must not be empty in JWT token") + null + } + + else -> JWTPrincipal(jwtCredential.payload) + } + } + } + } routing { get("/") { call.respondText("Hello, world!") @@ -62,8 +87,10 @@ fun Application.moduleJvm(appSettings: MkplAppSettings = initAppSettings()) { } } - v1Ad(appSettings) - v1Offer(appSettings) + authenticate("auth-jwt") { + v1Ad(appSettings) + v1Offer(appSettings) + } } webSocket("/ws/v1") { diff --git a/ok-marketplace-app-ktor/src/jvmMain/kotlin/base/PrincipalMapper.kt b/ok-marketplace-app-ktor/src/jvmMain/kotlin/base/PrincipalMapper.kt new file mode 100644 index 0000000..be8ae41 --- /dev/null +++ b/ok-marketplace-app-ktor/src/jvmMain/kotlin/base/PrincipalMapper.kt @@ -0,0 +1,29 @@ +package ru.otus.otuskotlin.marketplace.app.base + +import io.ktor.server.auth.jwt.* +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig.Companion.F_NAME_CLAIM +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig.Companion.GROUPS_CLAIM +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig.Companion.ID_CLAIM +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig.Companion.L_NAME_CLAIM +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig.Companion.M_NAME_CLAIM +import ru.otus.otuskotlin.marketplace.common.models.MkplUserId +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalModel +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserGroups + +fun JWTPrincipal?.toModel() = this?.run { + MkplPrincipalModel( + id = payload.getClaim(ID_CLAIM).asString()?.let { MkplUserId(it) } ?: MkplUserId.NONE, + fname = payload.getClaim(F_NAME_CLAIM).asString() ?: "", + mname = payload.getClaim(M_NAME_CLAIM).asString() ?: "", + lname = payload.getClaim(L_NAME_CLAIM).asString() ?: "", + groups = payload + .getClaim(GROUPS_CLAIM) + ?.asList(String::class.java) + ?.mapNotNull { + when(it) { + "USER" -> MkplUserGroups.USER + else -> null + } + }?.toSet() ?: emptySet() + ) +} ?: MkplPrincipalModel.NONE diff --git a/ok-marketplace-app-ktor/src/jvmMain/kotlin/base/ResolveAlgorithm.kt b/ok-marketplace-app-ktor/src/jvmMain/kotlin/base/ResolveAlgorithm.kt new file mode 100644 index 0000000..03d0aa3 --- /dev/null +++ b/ok-marketplace-app-ktor/src/jvmMain/kotlin/base/ResolveAlgorithm.kt @@ -0,0 +1,45 @@ +package ru.otus.otuskotlin.marketplace.app.base + +import com.auth0.jwk.Jwk +import com.auth0.jwk.UrlJwkProvider +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import io.ktor.http.auth.* +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig +import java.net.URL +import java.security.interfaces.RSAPublicKey + +fun HttpAuthHeader.resolveAlgorithm(authConfig: AuthConfig): Algorithm = when { + authConfig.certUrl != null -> resolveAlgorithmKeycloak(authConfig) + else -> Algorithm.HMAC256(authConfig.secret) +} + +private val jwks = mutableMapOf() + +fun HttpAuthHeader.resolveAlgorithmKeycloak(authConfig: AuthConfig): Algorithm { + val tokenString = this.render().replace(this.authScheme, "").trim() + if (tokenString.isBlank()) { + throw IllegalArgumentException("Request contains no proper Authorization header") + } + val token = try { + JWT.decode(tokenString) + } catch (e: Exception) { + throw IllegalArgumentException("Cannot parse JWT token from request", e) + } + val algo = token.algorithm + if (algo != "RS256") { + throw IllegalArgumentException("Wrong algorithm in JWT ($algo). Must be ...") + } + val keyId = token.keyId + val jwk = jwks[keyId] ?: run { + val provider = UrlJwkProvider(URL(authConfig.certUrl)) + val jwk = provider.get(keyId) + jwks[keyId] = jwk + jwk + } + val publicKey = jwk.publicKey + if (publicKey !is RSAPublicKey) { + throw IllegalArgumentException("Key with ID was found in JWKS but is not a RSA-key.") + } + return Algorithm.RSA256(publicKey, null) +} diff --git a/ok-marketplace-app-ktor/src/jvmMain/kotlin/v1/ControllerHelperV1.kt b/ok-marketplace-app-ktor/src/jvmMain/kotlin/v1/ControllerHelperV1.kt index cc54d2f..ff7bb1c 100644 --- a/ok-marketplace-app-ktor/src/jvmMain/kotlin/v1/ControllerHelperV1.kt +++ b/ok-marketplace-app-ktor/src/jvmMain/kotlin/v1/ControllerHelperV1.kt @@ -1,12 +1,16 @@ package ru.otus.otuskotlin.marketplace.app.v1 import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* import io.ktor.server.request.* import io.ktor.server.response.* import ru.otus.otuskotlin.marketplace.api.v1.models.IRequest import ru.otus.otuskotlin.marketplace.api.v1.models.IResponse +import ru.otus.otuskotlin.marketplace.app.base.toModel import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings import ru.otus.otuskotlin.marketplace.app.common.process +import ru.otus.otuskotlin.marketplace.app.v2.mkplPrincipal import ru.otus.otuskotlin.marketplace.mappers.v1.fromTransport import ru.otus.otuskotlin.marketplace.mappers.v1.toTransportAd import kotlin.reflect.KClass @@ -19,6 +23,7 @@ suspend inline fun () + principal = this@processV1.request.call.principal().toModel() fromTransport(request) }, sendResponse = { respond(toTransportAd()) } diff --git a/ok-marketplace-app-ktor/src/jvmMain/resources/application.yaml b/ok-marketplace-app-ktor/src/jvmMain/resources/application.yaml index 5f7bffc..9cff895 100644 --- a/ok-marketplace-app-ktor/src/jvmMain/resources/application.yaml +++ b/ok-marketplace-app-ktor/src/jvmMain/resources/application.yaml @@ -34,3 +34,10 @@ marketplace: password: "$DB_GREMLIN_HOST:root_root" port: "$DB_GREMLIN_PORT:8182" enableSsl: false + +jwt: + secret: "secret" + issuer: "OtusKotlin" + audience: "ad-users" + realm: "mp-ads" + clientId: "otus-marketplace-service" diff --git a/ok-marketplace-app-ktor/src/jvmTest/kotlin/auth/AuthTest.kt b/ok-marketplace-app-ktor/src/jvmTest/kotlin/auth/AuthTest.kt new file mode 100644 index 0000000..c1c147d --- /dev/null +++ b/ok-marketplace-app-ktor/src/jvmTest/kotlin/auth/AuthTest.kt @@ -0,0 +1,23 @@ +package ru.otus.otuskotlin.marketplace.app.auth + +import io.ktor.client.request.* +import io.ktor.server.testing.* +import org.junit.Test +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig +import ru.otus.otuskotlin.marketplace.app.helpers.testSettings +import ru.otus.otuskotlin.marketplace.app.moduleJvm +import kotlin.test.assertEquals + +class AuthTest { + @Test + fun invalidAudience() = testApplication { + application { + moduleJvm(testSettings()) + } + + val response = client.post("/v1/ad/create") { + addAuth(config = AuthConfig.TEST.copy(audience = "invalid")) + } + assertEquals(401, response.status.value) + } +} diff --git a/ok-marketplace-app-ktor/src/jvmTest/kotlin/auth/addAuth.kt b/ok-marketplace-app-ktor/src/jvmTest/kotlin/auth/addAuth.kt new file mode 100644 index 0000000..13913d1 --- /dev/null +++ b/ok-marketplace-app-ktor/src/jvmTest/kotlin/auth/addAuth.kt @@ -0,0 +1,22 @@ +package ru.otus.otuskotlin.marketplace.app.auth + +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import io.ktor.client.request.* +import io.ktor.http.* +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig + +actual fun HttpRequestBuilder.addAuth( + id: String, + groups: List, + config: AuthConfig +) { + val token = JWT.create() + .withAudience(config.audience) + .withIssuer(config.issuer) + .withClaim(AuthConfig.GROUPS_CLAIM, groups) + .withClaim(AuthConfig.ID_CLAIM, id) + .sign(Algorithm.HMAC256(config.secret)) + + header(HttpHeaders.Authorization, "Bearer $token") +} \ No newline at end of file diff --git a/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V1AdInmemoryApiTest.kt b/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V1AdInmemoryApiTest.kt index 8a138b5..547c357 100644 --- a/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V1AdInmemoryApiTest.kt +++ b/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V1AdInmemoryApiTest.kt @@ -1,8 +1,7 @@ -package ru.otus.otuskotlin.marketplace.app.repo +package ru.otus.otuskotlin.marketplace.repo import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.SerializationFeature -import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* @@ -11,29 +10,71 @@ import io.ktor.serialization.jackson.* import io.ktor.server.testing.* import org.junit.Test import ru.otus.otuskotlin.marketplace.api.v1.models.* +import ru.otus.otuskotlin.marketplace.app.auth.addAuth +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig +import ru.otus.otuskotlin.marketplace.app.helpers.testSettings +import ru.otus.otuskotlin.marketplace.app.moduleJvm +import ru.otus.otuskotlin.marketplace.common.models.MkplAdId +import ru.otus.otuskotlin.marketplace.common.models.MkplAdLock +import ru.otus.otuskotlin.marketplace.common.models.MkplDealSide +import ru.otus.otuskotlin.marketplace.common.models.MkplVisibility +import ru.otus.otuskotlin.marketplace.repo.inmemory.AdRepoInMemory +import ru.otus.otuskotlin.marketplace.stubs.MkplAdStub import kotlin.test.assertEquals import kotlin.test.assertNotEquals -const val COMMON_REQUEST_ID = "12345" class V1AdInmemoryApiTest { - private val createAd = AdCreateObject( - title = "Болт", - description = "КРУТЕЙШИЙ", - adType = DealSide.DEMAND, - visibility = AdVisibility.PUBLIC, - ) - private val requestCreateObj = AdCreateRequest( - requestId = "12345", - ad = createAd, - debug = AdDebug( - mode = AdRequestDebugMode.TEST, - ) - ) + private val uuidOld = "10000000-0000-0000-0000-000000000001" + private val uuidNew = "10000000-0000-0000-0000-000000000002" + private val uuidSup = "10000000-0000-0000-0000-000000000003" + private val initAd = MkplAdStub.prepareResult { + id = MkplAdId(uuidOld) + title = "abc" + description = "abc" + adType = MkplDealSide.DEMAND + visibility = MkplVisibility.VISIBLE_PUBLIC + lock = MkplAdLock(uuidOld) + } + private val initAdSupply = MkplAdStub.prepareResult { + id = MkplAdId(uuidSup) + title = "abc" + description = "abc" + adType = MkplDealSide.SUPPLY + visibility = MkplVisibility.VISIBLE_PUBLIC + } + + private val userId = initAd.ownerId @Test fun create() = testApplication { + val repo = AdRepoInMemory(initObjects = listOf(initAd), randomUuid = { uuidNew }) + application { + moduleJvm(testSettings(repo)) + } val client = myClient() - val responseObj = initObject(client) + + val createAd = AdCreateObject( + title = "Болт", + description = "КРУТЕЙШИЙ", + adType = DealSide.DEMAND, + visibility = AdVisibility.PUBLIC, + ) + + val response = client.post("/v1/ad/create") { + val requestObj = AdCreateRequest( + requestId = "12345", + ad = createAd, + debug = AdDebug( + mode = AdRequestDebugMode.TEST, + ) + ) + contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) + setBody(requestObj) + } + val responseObj = response.body() + assertEquals(200, response.status.value) + assertEquals(uuidNew, responseObj.ad?.id) assertEquals(createAd.title, responseObj.ad?.title) assertEquals(createAd.description, responseObj.ad?.description) assertEquals(createAd.adType, responseObj.ad?.adType) @@ -42,37 +83,44 @@ class V1AdInmemoryApiTest { @Test fun read() = testApplication { + val repo = AdRepoInMemory(initObjects = listOf(initAd), randomUuid = { uuidNew }) + application { + moduleJvm(testSettings(repo)) + } val client = myClient() - val id = initObject(client).ad?.id + val response = client.post("/v1/ad/read") { val requestObj = AdReadRequest( requestId = "12345", - ad = AdReadObject(id), + ad = AdReadObject(uuidOld), debug = AdDebug( mode = AdRequestDebugMode.TEST, ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() assertEquals(200, response.status.value) - assertEquals(id, responseObj.ad?.id) + assertEquals(uuidOld, responseObj.ad?.id) } @Test fun update() = testApplication { + val repo = AdRepoInMemory(initObjects = listOf(initAd), randomUuid = { uuidNew }) + application { + moduleJvm(testSettings(repo)) + } val client = myClient() - val created = initObject(client) - val adUpdate = AdUpdateObject( - id = created.ad?.id, + id = uuidOld, title = "Болт", description = "КРУТЕЙШИЙ", adType = DealSide.DEMAND, visibility = AdVisibility.PUBLIC, - lock = created.ad?.lock, + lock = initAd.lock.asString(), ) val response = client.post("/v1/ad/update") { @@ -84,6 +132,7 @@ class V1AdInmemoryApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() @@ -97,32 +146,40 @@ class V1AdInmemoryApiTest { @Test fun delete() = testApplication { + val repo = AdRepoInMemory(initObjects = listOf(initAd), randomUuid = { uuidNew }) + application { + moduleJvm(testSettings(repo)) + } val client = myClient() - val created = initObject(client) val response = client.post("/v1/ad/delete") { val requestObj = AdDeleteRequest( requestId = "12345", ad = AdDeleteObject( - id = created.ad?.id, - lock = created.ad?.lock + id = uuidOld, + lock = initAd.lock.asString() ), debug = AdDebug( mode = AdRequestDebugMode.TEST, ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() assertEquals(200, response.status.value) - assertEquals(created.ad?.id, responseObj.ad?.id) + assertEquals(uuidOld, responseObj.ad?.id) } @Test fun search() = testApplication { + val repo = AdRepoInMemory(initObjects = listOf(initAd), randomUuid = { uuidNew }) + application { + moduleJvm(testSettings(repo)) + } val client = myClient() - val initObject = initObject(client) + val response = client.post("/v1/ad/search") { val requestObj = AdSearchRequest( requestId = "12345", @@ -132,42 +189,41 @@ class V1AdInmemoryApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() assertEquals(200, response.status.value) assertNotEquals(0, responseObj.ads?.size) - assertEquals(initObject.ad?.id, responseObj.ads?.first()?.id) + assertEquals(uuidOld, responseObj.ads?.first()?.id) } @Test fun offers() = testApplication { + val repo = AdRepoInMemory(initObjects = listOf(initAd, initAdSupply), randomUuid = { uuidNew }) + application { + moduleJvm(testSettings(repo)) + } val client = myClient() - val oldId = initObject(client).ad?.id + val response = client.post("/v1/ad/offers") { val requestObj = AdOffersRequest( - requestId = COMMON_REQUEST_ID, + requestId = "12345", ad = AdReadObject( - id = oldId, + id = uuidOld, ), debug = AdDebug( mode = AdRequestDebugMode.TEST, ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() assertEquals(200, response.status.value) - assertEquals(COMMON_REQUEST_ID, responseObj.requestId) - } - private suspend fun initObject(client: HttpClient): AdCreateResponse { - val responseCreate = client.post("/v1/ad/create") { - contentType(ContentType.Application.Json) - setBody(requestCreateObj) - } - assertEquals(200, responseCreate.status.value) - return responseCreate.body() + assertNotEquals(0, responseObj.ads?.size) + assertEquals(uuidSup, responseObj.ads?.first()?.id) } private fun ApplicationTestBuilder.myClient() = createClient { @@ -180,4 +236,5 @@ class V1AdInmemoryApiTest { } } } + } diff --git a/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V1AdMockApiTest.kt b/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V1AdMockApiTest.kt index 51831cb..4205a0b 100644 --- a/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V1AdMockApiTest.kt +++ b/ok-marketplace-app-ktor/src/jvmTest/kotlin/repo/V1AdMockApiTest.kt @@ -1,4 +1,4 @@ -package ru.otus.otuskotlin.marketplace.app.repo +package ru.otus.otuskotlin.marketplace.repo import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.SerializationFeature @@ -7,20 +7,19 @@ import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* import io.ktor.http.* import io.ktor.serialization.jackson.* -import io.ktor.server.config.* import io.ktor.server.testing.* import org.junit.Test import ru.otus.otuskotlin.marketplace.api.v1.models.* -import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig +import ru.otus.otuskotlin.marketplace.app.helpers.testSettings import ru.otus.otuskotlin.marketplace.app.moduleJvm +import ru.otus.otuskotlin.marketplace.app.auth.addAuth import ru.otus.otuskotlin.marketplace.backend.repo.tests.AdRepositoryMock -import ru.otus.otuskotlin.marketplace.common.MkplCorSettings import ru.otus.otuskotlin.marketplace.common.models.MkplAd import ru.otus.otuskotlin.marketplace.common.models.MkplAdLock import ru.otus.otuskotlin.marketplace.common.models.MkplDealSide import ru.otus.otuskotlin.marketplace.common.repo.DbAdResponse import ru.otus.otuskotlin.marketplace.common.repo.DbAdsResponse -import ru.otus.otuskotlin.marketplace.common.repo.IAdRepository import ru.otus.otuskotlin.marketplace.stubs.MkplAdStub import kotlin.test.assertEquals import kotlin.test.assertNotEquals @@ -32,15 +31,17 @@ class V1AdMockApiTest { @Test fun create() = testApplication { - initRepoTest(AdRepositoryMock( + val repo = AdRepositoryMock( invokeCreateAd = { DbAdResponse( isSuccess = true, data = it.ad.copy(id = adId), ) } - )) - + ) + application { + moduleJvm(testSettings(repo)) + } val client = myClient() val createAd = AdCreateObject( @@ -59,6 +60,7 @@ class V1AdMockApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() @@ -72,7 +74,7 @@ class V1AdMockApiTest { @Test fun read() = testApplication { - initRepoTest(AdRepositoryMock( + val repo = AdRepositoryMock( invokeReadAd = { DbAdResponse( isSuccess = true, @@ -82,8 +84,10 @@ class V1AdMockApiTest { ), ) } - )) - + ) + application { + moduleJvm(testSettings(repo)) + } val client = myClient() val response = client.post("/v1/ad/read") { @@ -95,6 +99,7 @@ class V1AdMockApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() @@ -104,7 +109,7 @@ class V1AdMockApiTest { @Test fun update() = testApplication { - initRepoTest(AdRepositoryMock( + val repo = AdRepositoryMock( invokeReadAd = { DbAdResponse( isSuccess = true, @@ -121,8 +126,10 @@ class V1AdMockApiTest { data = it.ad.copy(ownerId = userId), ) } - )) - + ) + application { + moduleJvm(testSettings(repo)) + } val client = myClient() val adUpdate = AdUpdateObject( @@ -150,6 +157,7 @@ class V1AdMockApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() @@ -163,26 +171,29 @@ class V1AdMockApiTest { @Test fun delete() = testApplication { - initRepoTest(AdRepositoryMock( - invokeReadAd = { - DbAdResponse( - isSuccess = true, - data = MkplAd( - id = it.id, - ownerId = userId, - ), - ) - }, - invokeDeleteAd = { - DbAdResponse( - isSuccess = true, - data = MkplAd( - id = it.id, - ownerId = userId, - ), - ) - } - )) + application { + val repo = AdRepositoryMock( + invokeReadAd = { + DbAdResponse( + isSuccess = true, + data = MkplAd( + id = it.id, + ownerId = userId, + ), + ) + }, + invokeDeleteAd = { + DbAdResponse( + isSuccess = true, + data = MkplAd( + id = it.id, + ownerId = userId, + ), + ) + } + ) + moduleJvm(testSettings(repo)) + } val client = myClient() @@ -200,6 +211,7 @@ class V1AdMockApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() @@ -209,20 +221,24 @@ class V1AdMockApiTest { @Test fun search() = testApplication { - initRepoTest(AdRepositoryMock( - invokeSearchAd = { - DbAdsResponse( - isSuccess = true, - data = listOf( - MkplAd( - title = it.titleFilter, - ownerId = it.ownerId, - adType = it.dealSide, + application { + val repo = + AdRepositoryMock( + invokeSearchAd = { + DbAdsResponse( + isSuccess = true, + data = listOf( + MkplAd( + title = it.titleFilter, + ownerId = it.ownerId, + adType = it.dealSide, + ) + ), ) - ), + } ) - } - )) + moduleJvm(testSettings(repo)) + } val client = myClient() val response = client.post("/v1/ad/search") { @@ -234,6 +250,7 @@ class V1AdMockApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() @@ -243,30 +260,34 @@ class V1AdMockApiTest { @Test fun offers() = testApplication { - initRepoTest(AdRepositoryMock( - invokeReadAd = { - DbAdResponse( - isSuccess = true, - data = MkplAd(id = it.id) - ) - }, - invokeSearchAd = { - DbAdsResponse( - isSuccess = true, - data = listOf( - MkplAd( - title = it.titleFilter, - ownerId = it.ownerId, - adType = when (it.dealSide) { - MkplDealSide.DEMAND -> MkplDealSide.SUPPLY - MkplDealSide.SUPPLY -> MkplDealSide.DEMAND - MkplDealSide.NONE -> MkplDealSide.NONE - }, + application { + val repo = + AdRepositoryMock( + invokeReadAd = { + DbAdResponse( + isSuccess = true, + data = MkplAd(id = it.id) ) - ), + }, + invokeSearchAd = { + DbAdsResponse( + isSuccess = true, + data = listOf( + MkplAd( + title = it.titleFilter, + ownerId = it.ownerId, + adType = when (it.dealSide) { + MkplDealSide.DEMAND -> MkplDealSide.SUPPLY + MkplDealSide.SUPPLY -> MkplDealSide.DEMAND + MkplDealSide.NONE -> MkplDealSide.NONE + }, + ) + ), + ) + } ) - } - )) + moduleJvm(testSettings(repo)) + } val client = myClient() val response = client.post("/v1/ad/offers") { @@ -281,6 +302,7 @@ class V1AdMockApiTest { ) ) contentType(ContentType.Application.Json) + addAuth(id = userId.asString(), config = AuthConfig.TEST) setBody(requestObj) } val responseObj = response.body() @@ -298,13 +320,4 @@ class V1AdMockApiTest { } } } - - private fun ApplicationTestBuilder.initRepoTest(repo: IAdRepository) { - environment { - config = MapApplicationConfig() - } - application { - moduleJvm(MkplAppSettings(corSettings = MkplCorSettings(repoTest = repo))) - } - } } diff --git a/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V1AdStubApiTest.kt b/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V1AdStubApiTest.kt index ce804f4..bdf354e 100644 --- a/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V1AdStubApiTest.kt +++ b/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V1AdStubApiTest.kt @@ -1,4 +1,4 @@ -package ru.otus.otuskotlin.marketplace.app.stubs +package ru.otus.otuskotlin.marketplace.stubs import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.SerializationFeature @@ -10,11 +10,18 @@ import io.ktor.serialization.jackson.* import io.ktor.server.testing.* import org.junit.Test import ru.otus.otuskotlin.marketplace.api.v1.models.* +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig +import ru.otus.otuskotlin.marketplace.app.helpers.testSettings +import ru.otus.otuskotlin.marketplace.app.moduleJvm +import ru.otus.otuskotlin.marketplace.app.auth.addAuth import kotlin.test.assertEquals class V1AdStubApiTest { @Test fun create() = testApplication { + application { + moduleJvm(appSettings = testSettings()) + } val client = myClient() val response = client.post("/v1/ad/create") { @@ -31,6 +38,7 @@ class V1AdStubApiTest { stub = AdRequestDebugStubs.SUCCESS ) ) + addAuth(config = AuthConfig.TEST) contentType(ContentType.Application.Json) setBody(requestObj) } @@ -43,6 +51,9 @@ class V1AdStubApiTest { @Test fun read() = testApplication { + application { + moduleJvm(appSettings = testSettings()) + } val client = myClient() val response = client.post("/v1/ad/read") { @@ -54,6 +65,7 @@ class V1AdStubApiTest { stub = AdRequestDebugStubs.SUCCESS ) ) + addAuth(config = AuthConfig.TEST) contentType(ContentType.Application.Json) setBody(requestObj) } @@ -64,6 +76,9 @@ class V1AdStubApiTest { @Test fun update() = testApplication { + application { + moduleJvm(appSettings = testSettings()) + } val client = myClient() val response = client.post("/v1/ad/update") { @@ -81,6 +96,7 @@ class V1AdStubApiTest { stub = AdRequestDebugStubs.SUCCESS ) ) + addAuth(config = AuthConfig.TEST) contentType(ContentType.Application.Json) setBody(requestObj) } @@ -91,6 +107,9 @@ class V1AdStubApiTest { @Test fun delete() = testApplication { + application { + moduleJvm(appSettings = testSettings()) + } val client = myClient() val response = client.post("/v1/ad/delete") { @@ -104,6 +123,7 @@ class V1AdStubApiTest { stub = AdRequestDebugStubs.SUCCESS ) ) + addAuth(config = AuthConfig.TEST) contentType(ContentType.Application.Json) setBody(requestObj) } @@ -114,6 +134,9 @@ class V1AdStubApiTest { @Test fun search() = testApplication { + application { + moduleJvm(appSettings = testSettings()) + } val client = myClient() val response = client.post("/v1/ad/search") { @@ -125,6 +148,7 @@ class V1AdStubApiTest { stub = AdRequestDebugStubs.SUCCESS ) ) + addAuth(config = AuthConfig.TEST) contentType(ContentType.Application.Json) setBody(requestObj) } @@ -135,6 +159,7 @@ class V1AdStubApiTest { @Test fun offers() = testApplication { + application { moduleJvm(testSettings()) } val client = myClient() val response = client.post("/v1/ad/offers") { @@ -148,6 +173,7 @@ class V1AdStubApiTest { stub = AdRequestDebugStubs.SUCCESS ) ) + addAuth(config = AuthConfig.TEST) contentType(ContentType.Application.Json) setBody(requestObj) } diff --git a/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V1WebsocketStubTest.kt b/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V1WebsocketStubTest.kt index 3b7c306..b0a1d04 100644 --- a/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V1WebsocketStubTest.kt +++ b/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V1WebsocketStubTest.kt @@ -1,4 +1,4 @@ -package ru.otus.otuskotlin.marketplace.app.stubs +package ru.otus.otuskotlin.marketplace.stubs import io.ktor.client.plugins.websocket.* import io.ktor.server.testing.* @@ -6,6 +6,8 @@ import io.ktor.websocket.* import kotlinx.coroutines.withTimeout import ru.otus.otuskotlin.marketplace.api.v1.apiV1Mapper import ru.otus.otuskotlin.marketplace.api.v1.models.* +import ru.otus.otuskotlin.marketplace.app.helpers.testSettings +import ru.otus.otuskotlin.marketplace.app.moduleJvm import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -127,6 +129,7 @@ class V1WebsocketStubTest { request: Any, crossinline assertBlock: (T) -> Unit ) = testApplication { + application { moduleJvm(testSettings()) } val client = createClient { install(WebSockets) } diff --git a/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V2WebsocketStubTest.kt b/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V2WebsocketStubTest.kt index 253d34f..e874fe8 100644 --- a/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V2WebsocketStubTest.kt +++ b/ok-marketplace-app-ktor/src/jvmTest/kotlin/stubs/V2WebsocketStubTest.kt @@ -8,6 +8,8 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import ru.otus.otuskotlin.marketplace.api.v2.apiV2Mapper import ru.otus.otuskotlin.marketplace.api.v2.models.* +import ru.otus.otuskotlin.marketplace.app.helpers.testSettings +import ru.otus.otuskotlin.marketplace.app.moduleJvm import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -129,6 +131,7 @@ class V2WebsocketStubTest { request: IRequest, crossinline assertBlock: (T) -> Unit ) = testApplication { + application { moduleJvm(testSettings()) } val client = createClient { install(WebSockets) } diff --git a/ok-marketplace-app-ktor/src/linuxX64Test/kotlin/authHelpers.linuxX64.kt b/ok-marketplace-app-ktor/src/linuxX64Test/kotlin/authHelpers.linuxX64.kt new file mode 100644 index 0000000..9f9b969 --- /dev/null +++ b/ok-marketplace-app-ktor/src/linuxX64Test/kotlin/authHelpers.linuxX64.kt @@ -0,0 +1,11 @@ +package ru.otus.otuskotlin.marketplace.app.auth + +import io.ktor.client.request.* +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig + +actual fun HttpRequestBuilder.addAuth( + id: String, + groups: List, + config: AuthConfig +) { +} diff --git a/ok-marketplace-app-ktor/src/linuxX64Test/kotlin/package.kt b/ok-marketplace-app-ktor/src/linuxX64Test/kotlin/package.kt new file mode 100644 index 0000000..81d2c47 --- /dev/null +++ b/ok-marketplace-app-ktor/src/linuxX64Test/kotlin/package.kt @@ -0,0 +1 @@ +package ru.otus.otuskotlin.marketplace.app.auth \ No newline at end of file diff --git a/ok-marketplace-app-ktor/src/macosArm64Test/kotlin/authHelpers.linuxX64.kt b/ok-marketplace-app-ktor/src/macosArm64Test/kotlin/authHelpers.linuxX64.kt new file mode 100644 index 0000000..9f9b969 --- /dev/null +++ b/ok-marketplace-app-ktor/src/macosArm64Test/kotlin/authHelpers.linuxX64.kt @@ -0,0 +1,11 @@ +package ru.otus.otuskotlin.marketplace.app.auth + +import io.ktor.client.request.* +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig + +actual fun HttpRequestBuilder.addAuth( + id: String, + groups: List, + config: AuthConfig +) { +} diff --git a/ok-marketplace-app-ktor/src/macosArm64Test/kotlin/package.kt b/ok-marketplace-app-ktor/src/macosArm64Test/kotlin/package.kt new file mode 100644 index 0000000..81d2c47 --- /dev/null +++ b/ok-marketplace-app-ktor/src/macosArm64Test/kotlin/package.kt @@ -0,0 +1 @@ +package ru.otus.otuskotlin.marketplace.app.auth \ No newline at end of file diff --git a/ok-marketplace-app-ktor/src/macosX64Test/kotlin/authHelpers.linuxX64.kt b/ok-marketplace-app-ktor/src/macosX64Test/kotlin/authHelpers.linuxX64.kt new file mode 100644 index 0000000..9f9b969 --- /dev/null +++ b/ok-marketplace-app-ktor/src/macosX64Test/kotlin/authHelpers.linuxX64.kt @@ -0,0 +1,11 @@ +package ru.otus.otuskotlin.marketplace.app.auth + +import io.ktor.client.request.* +import ru.otus.otuskotlin.marketplace.app.common.AuthConfig + +actual fun HttpRequestBuilder.addAuth( + id: String, + groups: List, + config: AuthConfig +) { +} diff --git a/ok-marketplace-app-ktor/src/macosX64Test/kotlin/package.kt b/ok-marketplace-app-ktor/src/macosX64Test/kotlin/package.kt new file mode 100644 index 0000000..81d2c47 --- /dev/null +++ b/ok-marketplace-app-ktor/src/macosX64Test/kotlin/package.kt @@ -0,0 +1 @@ +package ru.otus.otuskotlin.marketplace.app.auth \ No newline at end of file diff --git a/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v1/controller/ControllerHelperV1.kt b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v1/controller/ControllerHelperV1.kt index 74ed317..b3a88f0 100644 --- a/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v1/controller/ControllerHelperV1.kt +++ b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v1/controller/ControllerHelperV1.kt @@ -1,10 +1,10 @@ package ru.otus.otuskotlin.markeplace.springapp.api.v1.controller +import ru.otus.otuskotlin.markeplace.springapp.fakeMkplPrincipal import ru.otus.otuskotlin.marketplace.api.v1.models.IRequest import ru.otus.otuskotlin.marketplace.api.v1.models.IResponse import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings import ru.otus.otuskotlin.marketplace.app.common.process -import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor import ru.otus.otuskotlin.marketplace.logging.common.IMpLogWrapper import ru.otus.otuskotlin.marketplace.mappers.v1.fromTransport import ru.otus.otuskotlin.marketplace.mappers.v1.toTransportAd @@ -14,7 +14,10 @@ suspend inline fun processV1( request: Q, logger: IMpLogWrapper, logId: String, -): R = appSettings.processor.process(logger, logId, - fromTransport = { fromTransport(request) }, - sendResponse = { toTransportAd() as R } +): R = appSettings.processor.process(logger, logId, + fromTransport = { + fromTransport(request) + principal = fakeMkplPrincipal() + }, + sendResponse = { toTransportAd() as R } ) diff --git a/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v1/ws/WsAdHandlerV1.kt b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v1/ws/WsAdHandlerV1.kt index 865e587..6ad4a18 100644 --- a/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v1/ws/WsAdHandlerV1.kt +++ b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v1/ws/WsAdHandlerV1.kt @@ -7,6 +7,8 @@ import org.springframework.web.socket.CloseStatus import org.springframework.web.socket.TextMessage import org.springframework.web.socket.WebSocketSession import org.springframework.web.socket.handler.TextWebSocketHandler +import ru.otus.otuskotlin.markeplace.springapp.fakeMkplPrincipal +import ru.otus.otuskotlin.marketplace.api.logs.mapper.toLog import ru.otus.otuskotlin.marketplace.api.v1.apiV1Mapper import ru.otus.otuskotlin.marketplace.api.v1.models.IRequest import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings @@ -29,6 +31,7 @@ class WsAdHandlerV1(private val appSettings: MkplAppSettings) : TextWebSocketHan val context = MkplContext() // TODO убрать, когда заработает обычный режим context.workMode = MkplWorkMode.STUB + context.principal = fakeMkplPrincipal() runBlocking { appSettings.processor.exec(context) } val msg = apiV1Mapper.writeValueAsString(context.toTransportInit()) @@ -40,6 +43,7 @@ class WsAdHandlerV1(private val appSettings: MkplAppSettings) : TextWebSocketHan try { val request = apiV1Mapper.readValue(message.payload, IRequest::class.java) ctx.fromTransport(request) + ctx.principal = fakeMkplPrincipal() runBlocking { appSettings.processor.exec(ctx) } val result = apiV1Mapper.writeValueAsString(ctx.toTransportAd()) diff --git a/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v2/controller/ControllerHelperV2.kt b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v2/controller/ControllerHelperV2.kt index 9c16b5d..e863af7 100644 --- a/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v2/controller/ControllerHelperV2.kt +++ b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v2/controller/ControllerHelperV2.kt @@ -1,5 +1,7 @@ package ru.otus.otuskotlin.markeplace.springapp.api.v2.controller +import kotlinx.serialization.decodeFromString +import ru.otus.otuskotlin.markeplace.springapp.fakeMkplPrincipal import ru.otus.otuskotlin.marketplace.api.v2.models.IRequest import ru.otus.otuskotlin.marketplace.api.v2.models.IResponse import ru.otus.otuskotlin.marketplace.app.common.MkplAppSettings @@ -13,7 +15,10 @@ suspend inline fun processV2( request: Q, logger: IMpLogWrapper, logId: String, -): R = appSettings.processor.process(logger, logId, - fromTransport = { fromTransport(request) }, - sendResponse = { toTransportAd() as R } +): R = appSettings.processor.process(logger, logId, + fromTransport = { + fromTransport(request) + principal = fakeMkplPrincipal() + }, + sendResponse = { toTransportAd() as R } ) diff --git a/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v2/ws/WsAdHandlerV2.kt b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v2/ws/WsAdHandlerV2.kt index 7f0dfca..5932d77 100644 --- a/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v2/ws/WsAdHandlerV2.kt +++ b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/api/v2/ws/WsAdHandlerV2.kt @@ -9,6 +9,7 @@ import org.springframework.web.socket.CloseStatus import org.springframework.web.socket.TextMessage import org.springframework.web.socket.WebSocketSession import org.springframework.web.socket.handler.TextWebSocketHandler +import ru.otus.otuskotlin.markeplace.springapp.fakeMkplPrincipal import ru.otus.otuskotlin.marketplace.api.v2.apiV2Mapper import ru.otus.otuskotlin.marketplace.api.v2.apiV2ResponseSerialize import ru.otus.otuskotlin.marketplace.api.v2.models.IRequest @@ -33,6 +34,7 @@ WsAdHandlerV2(private val appSettings: MkplAppSettings) : TextWebSocketHandler() val context = MkplContext() // TODO убрать, когда заработает обычный режим context.workMode = MkplWorkMode.STUB + context.principal = fakeMkplPrincipal() runBlocking { appSettings.processor.exec(context) } val msg = apiV2ResponseSerialize(context.toTransportInit()) @@ -45,6 +47,7 @@ WsAdHandlerV2(private val appSettings: MkplAppSettings) : TextWebSocketHandler() try { val request = apiV2Mapper.decodeFromString(message.payload) ctx.fromTransport(request) + ctx.principal = fakeMkplPrincipal() runBlocking { appSettings.processor.exec(ctx) } val result = apiV2Mapper.encodeToString(ctx.toTransportAd()) diff --git a/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/fakeMkplPrincipal.kt b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/fakeMkplPrincipal.kt new file mode 100644 index 0000000..24c55ac --- /dev/null +++ b/ok-marketplace-app-spring/src/main/kotlin/ru/otus/otuskotlin/markeplace/springapp/fakeMkplPrincipal.kt @@ -0,0 +1,14 @@ +package ru.otus.otuskotlin.markeplace.springapp + +import ru.otus.otuskotlin.marketplace.common.models.MkplUserId +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalModel +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserGroups + +// TODO в наше приложение на спринге сейчас не прикручена авторизация +fun fakeMkplPrincipal() = MkplPrincipalModel( + id = MkplUserId("user-1"), + fname = "Ivan", + mname = "Ivanovich", + lname = "Ivanov", + groups = setOf(MkplUserGroups.USER), +) \ No newline at end of file diff --git a/ok-marketplace-auth/build.gradle.kts b/ok-marketplace-auth/build.gradle.kts new file mode 100644 index 0000000..b90c4a4 --- /dev/null +++ b/ok-marketplace-auth/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + kotlin("multiplatform") +} + +group = rootProject.group +version = rootProject.version + +kotlin { + jvm {} + macosX64 {} + linuxX64 {} + + sourceSets { + val datetimeVersion: String by project + val coroutinesVersion: String by project + + val commonMain by getting { + dependencies { + implementation(kotlin("stdlib-common")) + + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") + api("org.jetbrains.kotlinx:kotlinx-datetime:$datetimeVersion") + + implementation(project(":ok-marketplace-common")) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + } +} diff --git a/ok-marketplace-auth/src/commonMain/kotlin/CheckPermitted.kt b/ok-marketplace-auth/src/commonMain/kotlin/CheckPermitted.kt new file mode 100644 index 0000000..f35739f --- /dev/null +++ b/ok-marketplace-auth/src/commonMain/kotlin/CheckPermitted.kt @@ -0,0 +1,70 @@ +package ru.otus.otuskotlin.marketplace.auth + +import ru.otus.otuskotlin.marketplace.common.models.MkplCommand +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalRelations +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserPermissions + +fun checkPermitted( + command: MkplCommand, + relations: Iterable, + permissions: Iterable, +) = + relations.asSequence().flatMap { relation -> + permissions.map { permission -> + AccessTableConditions( + command = command, + permission = permission, + relation = relation, + ) + } + }.any { + accessTable[it] != null + } + +private data class AccessTableConditions( + val command: MkplCommand, + val permission: MkplUserPermissions, + val relation: MkplPrincipalRelations +) + +private val accessTable = mapOf( + // Create + AccessTableConditions( + command = MkplCommand.CREATE, + permission = MkplUserPermissions.CREATE_OWN, + relation = MkplPrincipalRelations.NEW, + ) to true, + + // Read + AccessTableConditions( + command = MkplCommand.READ, + permission = MkplUserPermissions.READ_OWN, + relation = MkplPrincipalRelations.OWN, + ) to true, + AccessTableConditions( + command = MkplCommand.READ, + permission = MkplUserPermissions.READ_PUBLIC, + relation = MkplPrincipalRelations.PUBLIC, + ) to true, + + // Update + AccessTableConditions( + command = MkplCommand.UPDATE, + permission = MkplUserPermissions.UPDATE_OWN, + relation = MkplPrincipalRelations.OWN, + ) to true, + + // Delete + AccessTableConditions( + command = MkplCommand.DELETE, + permission = MkplUserPermissions.DELETE_OWN, + relation = MkplPrincipalRelations.OWN, + ) to true, + + // Offers + AccessTableConditions( + command = MkplCommand.OFFERS, + permission = MkplUserPermissions.OFFER_FOR_OWN, + relation = MkplPrincipalRelations.OWN, + ) to true, +) diff --git a/ok-marketplace-auth/src/commonMain/kotlin/ResolveChainPermissionsTo.kt b/ok-marketplace-auth/src/commonMain/kotlin/ResolveChainPermissionsTo.kt new file mode 100644 index 0000000..ace3034 --- /dev/null +++ b/ok-marketplace-auth/src/commonMain/kotlin/ResolveChainPermissionsTo.kt @@ -0,0 +1,39 @@ +package ru.otus.otuskotlin.marketplace.auth + +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserGroups +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserPermissions + +fun resolveChainPermissions( + groups: Iterable, +) = mutableSetOf() + .apply { + addAll(groups.flatMap { groupPermissionsAdmits[it] ?: emptySet() }) + removeAll(groups.flatMap { groupPermissionsDenys[it] ?: emptySet() }.toSet()) + } + .toSet() + +private val groupPermissionsAdmits = mapOf( + MkplUserGroups.USER to setOf( + MkplUserPermissions.READ_OWN, + MkplUserPermissions.READ_PUBLIC, + MkplUserPermissions.CREATE_OWN, + MkplUserPermissions.UPDATE_OWN, + MkplUserPermissions.DELETE_OWN, + MkplUserPermissions.OFFER_FOR_OWN, + ), + MkplUserGroups.MODERATOR_MP to setOf(), + MkplUserGroups.ADMIN_AD to setOf(), + MkplUserGroups.TEST to setOf(), + MkplUserGroups.BAN_AD to setOf(), +) + +private val groupPermissionsDenys = mapOf( + MkplUserGroups.USER to setOf(), + MkplUserGroups.MODERATOR_MP to setOf(), + MkplUserGroups.ADMIN_AD to setOf(), + MkplUserGroups.TEST to setOf(), + MkplUserGroups.BAN_AD to setOf( + MkplUserPermissions.UPDATE_OWN, + MkplUserPermissions.CREATE_OWN, + ), +) diff --git a/ok-marketplace-auth/src/commonMain/kotlin/ResolveFrontPermissionsTo.kt b/ok-marketplace-auth/src/commonMain/kotlin/ResolveFrontPermissionsTo.kt new file mode 100644 index 0000000..ce672b7 --- /dev/null +++ b/ok-marketplace-auth/src/commonMain/kotlin/ResolveFrontPermissionsTo.kt @@ -0,0 +1,56 @@ +package ru.otus.otuskotlin.marketplace.auth + +import ru.otus.otuskotlin.marketplace.common.models.MkplAdPermissionClient +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalRelations +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserPermissions + +fun resolveFrontPermissions( + permissions: Iterable, + relations: Iterable, +) = mutableSetOf() + .apply { + for (permission in permissions) { + for (relation in relations) { + accessTable[permission]?.get(relation)?.let { this@apply.add(it) } + } + } + } + .toSet() + +private val accessTable = mapOf( + // READ + MkplUserPermissions.READ_OWN to mapOf( + MkplPrincipalRelations.OWN to MkplAdPermissionClient.READ + ), + MkplUserPermissions.READ_GROUP to mapOf( + MkplPrincipalRelations.GROUP to MkplAdPermissionClient.READ + ), + MkplUserPermissions.READ_PUBLIC to mapOf( + MkplPrincipalRelations.PUBLIC to MkplAdPermissionClient.READ + ), + MkplUserPermissions.READ_CANDIDATE to mapOf( + MkplPrincipalRelations.MODERATABLE to MkplAdPermissionClient.READ + ), + + // UPDATE + MkplUserPermissions.UPDATE_OWN to mapOf( + MkplPrincipalRelations.OWN to MkplAdPermissionClient.UPDATE + ), + MkplUserPermissions.UPDATE_PUBLIC to mapOf( + MkplPrincipalRelations.MODERATABLE to MkplAdPermissionClient.UPDATE + ), + MkplUserPermissions.UPDATE_CANDIDATE to mapOf( + MkplPrincipalRelations.MODERATABLE to MkplAdPermissionClient.UPDATE + ), + + // DELETE + MkplUserPermissions.DELETE_OWN to mapOf( + MkplPrincipalRelations.OWN to MkplAdPermissionClient.DELETE + ), + MkplUserPermissions.DELETE_PUBLIC to mapOf( + MkplPrincipalRelations.MODERATABLE to MkplAdPermissionClient.DELETE + ), + MkplUserPermissions.DELETE_CANDIDATE to mapOf( + MkplPrincipalRelations.MODERATABLE to MkplAdPermissionClient.DELETE + ), +) diff --git a/ok-marketplace-auth/src/commonMain/kotlin/ResolveRelationsTo.kt b/ok-marketplace-auth/src/commonMain/kotlin/ResolveRelationsTo.kt new file mode 100644 index 0000000..909ccae --- /dev/null +++ b/ok-marketplace-auth/src/commonMain/kotlin/ResolveRelationsTo.kt @@ -0,0 +1,15 @@ +package ru.otus.otuskotlin.marketplace.auth + +import ru.otus.otuskotlin.marketplace.common.models.MkplAd +import ru.otus.otuskotlin.marketplace.common.models.MkplAdId +import ru.otus.otuskotlin.marketplace.common.models.MkplVisibility +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalModel +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalRelations + +fun MkplAd.resolveRelationsTo(principal: MkplPrincipalModel): Set = setOfNotNull( + MkplPrincipalRelations.NONE, + MkplPrincipalRelations.NEW.takeIf { id == MkplAdId.NONE }, + MkplPrincipalRelations.OWN.takeIf { principal.id == ownerId }, + MkplPrincipalRelations.MODERATABLE.takeIf { visibility != MkplVisibility.VISIBLE_TO_OWNER }, + MkplPrincipalRelations.PUBLIC.takeIf { visibility == MkplVisibility.VISIBLE_PUBLIC }, +) diff --git a/ok-marketplace-auth/src/commonMain/kotlin/package.kt b/ok-marketplace-auth/src/commonMain/kotlin/package.kt new file mode 100644 index 0000000..71d94a7 --- /dev/null +++ b/ok-marketplace-auth/src/commonMain/kotlin/package.kt @@ -0,0 +1 @@ +package ru.otus.otuskotlin.marketplace.auth diff --git a/ok-marketplace-biz/build.gradle.kts b/ok-marketplace-biz/build.gradle.kts index 341a008..5b3ea5c 100644 --- a/ok-marketplace-biz/build.gradle.kts +++ b/ok-marketplace-biz/build.gradle.kts @@ -19,6 +19,8 @@ kotlin { implementation(kotlin("stdlib-common")) implementation(project(":ok-marketplace-common")) + implementation(project(":ok-marketplace-auth")) + implementation(project(":ok-marketplace-repo-in-memory")) implementation(project(":ok-marketplace-stubs")) implementation(project(":ok-marketplace-lib-cor")) } diff --git a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/MkplAdProcessor.kt b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/MkplAdProcessor.kt index 7f5b51c..3f3a9f3 100644 --- a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/MkplAdProcessor.kt +++ b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/MkplAdProcessor.kt @@ -4,6 +4,10 @@ import ru.otus.otuskotlin.marketplace.biz.general.initRepo import ru.otus.otuskotlin.marketplace.biz.general.operation import ru.otus.otuskotlin.marketplace.biz.general.prepareResult import ru.otus.otuskotlin.marketplace.biz.general.stubs +import ru.otus.otuskotlin.marketplace.biz.permissions.accessValidation +import ru.otus.otuskotlin.marketplace.biz.permissions.chainPermissions +import ru.otus.otuskotlin.marketplace.biz.permissions.frontPermissions +import ru.otus.otuskotlin.marketplace.biz.permissions.searchTypes import ru.otus.otuskotlin.marketplace.biz.repo.* import ru.otus.otuskotlin.marketplace.biz.validation.* import ru.otus.otuskotlin.marketplace.biz.workers.* @@ -44,11 +48,14 @@ class MkplAdProcessor(val settings: MkplCorSettings = MkplCorSettings()) { finishAdValidation("Завершение проверок") } + chainPermissions("Вычисление разрешений для пользователя") chain { title = "Логика сохранения" repoPrepareCreate("Подготовка объекта для сохранения") + accessValidation("Вычисление прав доступа") repoCreate("Создание объявления в БД") } + frontPermissions("Вычисление пользовательских разрешений для фронтенда") prepareResult("Подготовка ответа") } operation("Получить объявление", MkplCommand.READ) { @@ -66,15 +73,18 @@ class MkplAdProcessor(val settings: MkplCorSettings = MkplCorSettings()) { finishAdValidation("Успешное завершение процедуры валидации") } + chainPermissions("Вычисление разрешений для пользователя") chain { title = "Логика чтения" repoRead("Чтение объявления из БД") + accessValidation("Вычисление прав доступа") worker { title = "Подготовка ответа для Read" on { state == MkplState.RUNNING } handle { adRepoDone = adRepoRead } } } + frontPermissions("Вычисление пользовательских разрешений для фронтенда") prepareResult("Подготовка ответа") } operation("Изменить объявление", MkplCommand.UPDATE) { @@ -102,14 +112,17 @@ class MkplAdProcessor(val settings: MkplCorSettings = MkplCorSettings()) { validateDescriptionHasContent("Проверка на наличие содержания в описании") finishAdValidation("Успешное завершение процедуры валидации") - chain { - title = "Логика сохранения" - repoRead("Чтение объявления из БД") - repoPrepareUpdate("Подготовка объекта для обновления") - repoUpdate("Обновление объявления в БД") - } - prepareResult("Подготовка ответа") } + chainPermissions("Вычисление разрешений для пользователя") + chain { + title = "Логика сохранения" + repoRead("Чтение объявления из БД") + accessValidation("Вычисление прав доступа") + repoPrepareUpdate("Подготовка объекта для обновления") + repoUpdate("Обновление объявления в БД") + } + frontPermissions("Вычисление пользовательских разрешений для фронтенда") + prepareResult("Подготовка ответа") } operation("Удалить объявление", MkplCommand.DELETE) { stubs("Обработка стабов") { @@ -129,12 +142,15 @@ class MkplAdProcessor(val settings: MkplCorSettings = MkplCorSettings()) { validateLockProperFormat("Проверка формата lock") finishAdValidation("Успешное завершение процедуры валидации") } + chainPermissions("Вычисление разрешений для пользователя") chain { title = "Логика удаления" repoRead("Чтение объявления из БД") + accessValidation("Вычисление прав доступа") repoPrepareDelete("Подготовка объекта для удаления") repoDelete("Удаление объявления из БД") } + frontPermissions("Вычисление пользовательских разрешений для фронтенда") prepareResult("Подготовка ответа") } operation("Поиск объявлений", MkplCommand.SEARCH) { @@ -149,7 +165,11 @@ class MkplAdProcessor(val settings: MkplCorSettings = MkplCorSettings()) { finishAdFilterValidation("Успешное завершение процедуры валидации") } + chainPermissions("Вычисление разрешений для пользователя") + searchTypes("Подготовка поискового запроса") + repoSearch("Поиск объявления в БД по фильтру") + frontPermissions("Вычисление пользовательских разрешений для фронтенда") prepareResult("Подготовка ответа") } operation("Поиск подходящих предложений для объявления", MkplCommand.OFFERS) { @@ -167,12 +187,15 @@ class MkplAdProcessor(val settings: MkplCorSettings = MkplCorSettings()) { finishAdValidation("Успешное завершение процедуры валидации") } + chainPermissions("Вычисление разрешений для пользователя") chain { title = "Логика поиска в БД" repoRead("Чтение объявления из БД") + accessValidation("Вычисление прав доступа") repoPrepareOffers("Подготовка данных для поиска предложений") repoOffers("Поиск предложений для объявления в БД") } + frontPermissions("Вычисление пользовательских разрешений для фронтенда") prepareResult("Подготовка ответа") } }.build() diff --git a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/general/InitRepo.kt b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/general/InitRepo.kt index 4c4c131..571a054 100644 --- a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/general/InitRepo.kt +++ b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/general/InitRepo.kt @@ -4,6 +4,7 @@ import ru.otus.otuskotlin.marketplace.common.MkplContext import ru.otus.otuskotlin.marketplace.common.helpers.errorAdministration import ru.otus.otuskotlin.marketplace.common.helpers.fail import ru.otus.otuskotlin.marketplace.common.models.MkplWorkMode +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserGroups import ru.otus.otuskotlin.marketplace.common.repo.IAdRepository import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl import ru.otus.otuskotlin.marketplace.cor.worker @@ -17,6 +18,7 @@ fun ICorChainDsl.initRepo(title: String) = worker { adRepo = when { workMode == MkplWorkMode.TEST -> settings.repoTest workMode == MkplWorkMode.STUB -> settings.repoStub + principal.groups.contains(MkplUserGroups.TEST) -> settings.repoTest else -> settings.repoProd } if (workMode != MkplWorkMode.STUB && adRepo == IAdRepository.NONE) fail( diff --git a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/AccessValidation.kt b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/AccessValidation.kt new file mode 100644 index 0000000..e82b817 --- /dev/null +++ b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/AccessValidation.kt @@ -0,0 +1,32 @@ +package ru.otus.otuskotlin.marketplace.biz.permissions + +import ru.otus.otuskotlin.marketplace.auth.checkPermitted +import ru.otus.otuskotlin.marketplace.auth.resolveRelationsTo +import ru.otus.otuskotlin.marketplace.common.MkplContext +import ru.otus.otuskotlin.marketplace.common.helpers.fail +import ru.otus.otuskotlin.marketplace.common.models.MkplError +import ru.otus.otuskotlin.marketplace.common.models.MkplState +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.chain +import ru.otus.otuskotlin.marketplace.cor.worker + +fun ICorChainDsl.accessValidation(title: String) = chain { + this.title = title + description = "Вычисление прав доступа по группе принципала и таблице прав доступа" + on { state == MkplState.RUNNING } + worker("Вычисление отношения объявления к принципалу") { + adRepoRead.principalRelations = adRepoRead.resolveRelationsTo(principal) + } + worker("Вычисление доступа к объявлению") { + permitted = checkPermitted(command, adRepoRead.principalRelations, permissionsChain) + } + worker { + this.title = "Валидация прав доступа" + description = "Проверка наличия прав для выполнения операции" + on { !permitted } + handle { + fail(MkplError(message = "User is not allowed to perform this operation")) + } + } +} + diff --git a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/ChainPermissions.kt b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/ChainPermissions.kt new file mode 100644 index 0000000..657738b --- /dev/null +++ b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/ChainPermissions.kt @@ -0,0 +1,21 @@ +package ru.otus.otuskotlin.marketplace.biz.permissions + +import ru.otus.otuskotlin.marketplace.auth.resolveChainPermissions +import ru.otus.otuskotlin.marketplace.common.MkplContext +import ru.otus.otuskotlin.marketplace.common.models.MkplState +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker + + +fun ICorChainDsl.chainPermissions(title: String) = worker { + this.title = title + description = "Вычисление прав доступа для групп пользователей" + + on { state == MkplState.RUNNING } + + handle { + permissionsChain.addAll(resolveChainPermissions(principal.groups)) + println("PRINCIPAL: $principal") + println("PERMISSIONS: $permissionsChain") + } +} diff --git a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/FrontPermissions.kt b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/FrontPermissions.kt new file mode 100644 index 0000000..502cf65 --- /dev/null +++ b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/FrontPermissions.kt @@ -0,0 +1,34 @@ +package ru.otus.otuskotlin.marketplace.biz.permissions + +import ru.otus.otuskotlin.marketplace.auth.resolveFrontPermissions +import ru.otus.otuskotlin.marketplace.auth.resolveRelationsTo +import ru.otus.otuskotlin.marketplace.common.MkplContext +import ru.otus.otuskotlin.marketplace.common.models.MkplState +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.worker + +fun ICorChainDsl.frontPermissions(title: String) = worker { + this.title = title + description = "Вычисление разрешений пользователей для фронтенда" + + on { state == MkplState.RUNNING } + + handle { + adRepoDone.permissionsClient.addAll( + resolveFrontPermissions( + permissionsChain, + // Повторно вычисляем отношения, поскольку они могли измениться при выполении операции + adRepoDone.resolveRelationsTo(principal) + ) + ) + + for (ad in adsRepoDone) { + ad.permissionsClient.addAll( + resolveFrontPermissions( + permissionsChain, + ad.resolveRelationsTo(principal) + ) + ) + } + } +} diff --git a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/SearchType.kt b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/SearchType.kt new file mode 100644 index 0000000..44fa73f --- /dev/null +++ b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/permissions/SearchType.kt @@ -0,0 +1,22 @@ +package ru.otus.otuskotlin.marketplace.biz.permissions + +import ru.otus.otuskotlin.marketplace.common.MkplContext +import ru.otus.otuskotlin.marketplace.common.models.MkplSearchPermissions +import ru.otus.otuskotlin.marketplace.common.models.MkplState +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserPermissions +import ru.otus.otuskotlin.marketplace.cor.ICorChainDsl +import ru.otus.otuskotlin.marketplace.cor.chain +import ru.otus.otuskotlin.marketplace.cor.worker + +fun ICorChainDsl.searchTypes(title: String) = chain { + this.title = title + description = "Добавление ограничений в поисковый запрос согласно правам доступа и др. политикам" + on { state == MkplState.RUNNING } + worker("Определение типа поиска") { + adFilterValidated.searchPermissions = setOfNotNull( + MkplSearchPermissions.OWN.takeIf { permissionsChain.contains(MkplUserPermissions.SEARCH_OWN) }, + MkplSearchPermissions.PUBLIC.takeIf { permissionsChain.contains(MkplUserPermissions.SEARCH_PUBLIC) }, + MkplSearchPermissions.REGISTERED.takeIf { permissionsChain.contains(MkplUserPermissions.SEARCH_REGISTERED) }, + ).toMutableSet() + } +} diff --git a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/AdRepoPrepareCreate.kt b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/AdRepoPrepareCreate.kt index bcc45d5..17da931 100644 --- a/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/AdRepoPrepareCreate.kt +++ b/ok-marketplace-biz/src/commonMain/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/AdRepoPrepareCreate.kt @@ -11,6 +11,7 @@ fun ICorChainDsl.repoPrepareCreate(title: String) = worker { on { state == MkplState.RUNNING } handle { adRepoRead = adValidated.deepCopy() + adRepoRead.ownerId = principal.id adRepoPrepare = adRepoRead } diff --git a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/AddTestPrincipal.kt b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/AddTestPrincipal.kt new file mode 100644 index 0000000..9a18c3e --- /dev/null +++ b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/AddTestPrincipal.kt @@ -0,0 +1,16 @@ +package ru.otus.otuskotlin.marketplace.biz + +import ru.otus.otuskotlin.marketplace.common.MkplContext +import ru.otus.otuskotlin.marketplace.common.models.MkplUserId +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalModel +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserGroups + +fun MkplContext.addTestPrincipal(userId: MkplUserId = MkplUserId("321")) { + principal = MkplPrincipalModel( + id = userId, + groups = setOf( + MkplUserGroups.USER, + MkplUserGroups.TEST, + ) + ) +} diff --git a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/auth/AdCrudAuthTest.kt b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/auth/AdCrudAuthTest.kt new file mode 100644 index 0000000..21aab64 --- /dev/null +++ b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/auth/AdCrudAuthTest.kt @@ -0,0 +1,94 @@ +package ru.otus.otuskotlin.marketplace.biz.auth + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.common.MkplContext +import ru.otus.otuskotlin.marketplace.common.MkplCorSettings +import ru.otus.otuskotlin.marketplace.common.models.* +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalModel +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserGroups +import ru.otus.otuskotlin.marketplace.repo.inmemory.AdRepoInMemory +import ru.otus.otuskotlin.marketplace.stubs.MkplAdStub +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * @crud - экземпляр класса-фасада бизнес-логики + * @context - контекст, смапленный из транспортной модели запроса + */ +@OptIn(ExperimentalCoroutinesApi::class) +class AdCrudAuthTest { + @Test + fun createSuccessTest() = runTest { + val userId = MkplUserId("123") + val repo = AdRepoInMemory() + val processor = MkplAdProcessor( + settings = MkplCorSettings( + repoTest = repo + ) + ) + val context = MkplContext( + workMode = MkplWorkMode.TEST, + adRequest = MkplAdStub.prepareResult { + permissionsClient.clear() + id = MkplAdId.NONE + }, + command = MkplCommand.CREATE, + principal = MkplPrincipalModel( + id = userId, + groups = setOf( + MkplUserGroups.USER, + MkplUserGroups.TEST, + ) + ) + ) + + processor.exec(context) + assertEquals(MkplState.FINISHING, context.state) + with(context.adResponse) { + assertTrue { id.asString().isNotBlank() } + assertContains(permissionsClient, MkplAdPermissionClient.READ) + assertContains(permissionsClient, MkplAdPermissionClient.UPDATE) + assertContains(permissionsClient, MkplAdPermissionClient.DELETE) +// assertFalse { permissionsClient.contains(PermissionModel.CONTACT) } + } + } + + @Test + fun readSuccessTest() = runTest { + val adObj = MkplAdStub.get() + val userId = adObj.ownerId + val adId = adObj.id + val repo = AdRepoInMemory(initObjects = listOf(adObj)) + val processor = MkplAdProcessor( + settings = MkplCorSettings( + repoTest = repo + ) + ) + val context = MkplContext( + command = MkplCommand.READ, + workMode = MkplWorkMode.TEST, + adRequest = MkplAd(id = adId), + principal = MkplPrincipalModel( + id = userId, + groups = setOf( + MkplUserGroups.USER, + MkplUserGroups.TEST, + ) + ) + ) + processor.exec(context) + assertEquals(MkplState.FINISHING, context.state) + with(context.adResponse) { + assertTrue { id.asString().isNotBlank() } + assertContains(permissionsClient, MkplAdPermissionClient.READ) + assertContains(permissionsClient, MkplAdPermissionClient.UPDATE) + assertContains(permissionsClient, MkplAdPermissionClient.DELETE) +// assertFalse { context.responseAd.permissions.contains(PermissionModel.CONTACT) } + } + } + +} diff --git a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/BizRepoDeleteTest.kt b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/BizRepoDeleteTest.kt index 8b2aef8..785b601 100644 --- a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/BizRepoDeleteTest.kt +++ b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/BizRepoDeleteTest.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import ru.otus.otuskotlin.marketplace.backend.repo.tests.AdRepositoryMock import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.biz.addTestPrincipal import ru.otus.otuskotlin.marketplace.common.MkplContext import ru.otus.otuskotlin.marketplace.common.MkplCorSettings import ru.otus.otuskotlin.marketplace.common.models.* @@ -63,6 +64,7 @@ class BizRepoDeleteTest { workMode = MkplWorkMode.TEST, adRequest = adToUpdate, ) + ctx.addTestPrincipal(userId) processor.exec(ctx) assertEquals(MkplState.FINISHING, ctx.state) assertTrue { ctx.errors.isEmpty() } diff --git a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/BizRepoUpdateTest.kt b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/BizRepoUpdateTest.kt index e5f0d13..71fa3e4 100644 --- a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/BizRepoUpdateTest.kt +++ b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/BizRepoUpdateTest.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import ru.otus.otuskotlin.marketplace.backend.repo.tests.AdRepositoryMock import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.biz.addTestPrincipal import ru.otus.otuskotlin.marketplace.common.MkplContext import ru.otus.otuskotlin.marketplace.common.MkplCorSettings import ru.otus.otuskotlin.marketplace.common.models.* @@ -67,6 +68,7 @@ class BizRepoUpdateTest { workMode = MkplWorkMode.TEST, adRequest = adToUpdate, ) + ctx.addTestPrincipal(userId) processor.exec(ctx) assertEquals(MkplState.FINISHING, ctx.state) assertEquals(adToUpdate.id, ctx.adResponse.id) diff --git a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/RepoByIdTests.kt b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/RepoByIdTests.kt index b1e4779..4c2503d 100644 --- a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/RepoByIdTests.kt +++ b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/repo/RepoByIdTests.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import ru.otus.otuskotlin.marketplace.backend.repo.tests.AdRepositoryMock import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.biz.addTestPrincipal import ru.otus.otuskotlin.marketplace.common.MkplContext import ru.otus.otuskotlin.marketplace.common.MkplCorSettings import ru.otus.otuskotlin.marketplace.common.models.* @@ -54,6 +55,7 @@ fun repoNotFoundTest(command: MkplCommand) = runTest { ), ) + ctx.addTestPrincipal() processor.exec(ctx) assertEquals(MkplState.FAILING, ctx.state) assertEquals(MkplAd(), ctx.adResponse) diff --git a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadDescription.kt b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadDescription.kt index 1e01f25..a92f569 100644 --- a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadDescription.kt +++ b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadDescription.kt @@ -3,6 +3,7 @@ package ru.otus.otuskotlin.marketplace.biz.validation import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.biz.addTestPrincipal import ru.otus.otuskotlin.marketplace.common.MkplContext import ru.otus.otuskotlin.marketplace.common.models.* import ru.otus.otuskotlin.marketplace.stubs.MkplAdStub @@ -27,6 +28,7 @@ fun validationDescriptionCorrect(command: MkplCommand, processor: MkplAdProcesso lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(0, ctx.errors.size) assertNotEquals(MkplState.FAILING, ctx.state) @@ -48,6 +50,7 @@ fun validationDescriptionTrim(command: MkplCommand, processor: MkplAdProcessor) lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(0, ctx.errors.size) assertNotEquals(MkplState.FAILING, ctx.state) @@ -69,6 +72,7 @@ fun validationDescriptionEmpty(command: MkplCommand, processor: MkplAdProcessor) lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(1, ctx.errors.size) assertEquals(MkplState.FAILING, ctx.state) @@ -92,6 +96,7 @@ fun validationDescriptionSymbols(command: MkplCommand, processor: MkplAdProcesso lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(1, ctx.errors.size) assertEquals(MkplState.FAILING, ctx.state) diff --git a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadId.kt b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadId.kt index 7bd3bfd..de49d6a 100644 --- a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadId.kt +++ b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadId.kt @@ -3,6 +3,7 @@ package ru.otus.otuskotlin.marketplace.biz.validation import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.biz.addTestPrincipal import ru.otus.otuskotlin.marketplace.common.MkplContext import ru.otus.otuskotlin.marketplace.common.models.* import ru.otus.otuskotlin.marketplace.stubs.MkplAdStub @@ -10,6 +11,8 @@ import kotlin.test.assertContains import kotlin.test.assertEquals import kotlin.test.assertNotEquals +private val stub = MkplAdStub.prepareResult { id = MkplAdId("123-234-abc-ABC") } + @OptIn(ExperimentalCoroutinesApi::class) fun validationIdCorrect(command: MkplCommand, processor: MkplAdProcessor) = runTest { val ctx = MkplContext( @@ -20,6 +23,7 @@ fun validationIdCorrect(command: MkplCommand, processor: MkplAdProcessor) = runT lock = MkplAdLock("123-234-abc-ABC") } ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(0, ctx.errors.size) assertNotEquals(MkplState.FAILING, ctx.state) @@ -42,6 +46,7 @@ fun validationIdTrim(command: MkplCommand, processor: MkplAdProcessor) = runTest lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(0, ctx.errors.size) assertNotEquals(MkplState.FAILING, ctx.state) diff --git a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadLock.kt b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadLock.kt index aad7073..0e0859d 100644 --- a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadLock.kt +++ b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadLock.kt @@ -3,12 +3,16 @@ package validation import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.biz.addTestPrincipal import ru.otus.otuskotlin.marketplace.common.MkplContext import ru.otus.otuskotlin.marketplace.common.models.* +import ru.otus.otuskotlin.marketplace.stubs.MkplAdStub import kotlin.test.assertContains import kotlin.test.assertEquals import kotlin.test.assertNotEquals +private val stub = MkplAdStub.prepareResult { id = MkplAdId("123-234-abc-ABC") } + @OptIn(ExperimentalCoroutinesApi::class) fun validationLockCorrect(command: MkplCommand, processor: MkplAdProcessor) = runTest { val ctx = MkplContext( @@ -24,6 +28,7 @@ fun validationLockCorrect(command: MkplCommand, processor: MkplAdProcessor) = ru lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(0, ctx.errors.size) assertNotEquals(MkplState.FAILING, ctx.state) @@ -44,6 +49,7 @@ fun validationLockTrim(command: MkplCommand, processor: MkplAdProcessor) = runTe lock = MkplAdLock(" \n\t 123-234-abc-ABC \n\t "), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(0, ctx.errors.size) assertNotEquals(MkplState.FAILING, ctx.state) diff --git a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadTitle.kt b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadTitle.kt index 6ca7dc5..69a469e 100644 --- a/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadTitle.kt +++ b/ok-marketplace-biz/src/commonTest/kotlin/ru/otus/otuskotlin/marketplace/biz/validation/ValidationBadTitle.kt @@ -3,6 +3,7 @@ package ru.otus.otuskotlin.marketplace.biz.validation import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import ru.otus.otuskotlin.marketplace.biz.MkplAdProcessor +import ru.otus.otuskotlin.marketplace.biz.addTestPrincipal import ru.otus.otuskotlin.marketplace.common.MkplContext import ru.otus.otuskotlin.marketplace.common.models.* import ru.otus.otuskotlin.marketplace.stubs.MkplAdStub @@ -27,6 +28,7 @@ fun validationTitleCorrect(command: MkplCommand, processor: MkplAdProcessor) = r lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(0, ctx.errors.size) assertNotEquals(MkplState.FAILING, ctx.state) @@ -48,6 +50,7 @@ fun validationTitleTrim(command: MkplCommand, processor: MkplAdProcessor) = runT lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(0, ctx.errors.size) assertNotEquals(MkplState.FAILING, ctx.state) @@ -69,6 +72,7 @@ fun validationTitleEmpty(command: MkplCommand, processor: MkplAdProcessor) = run lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(1, ctx.errors.size) assertEquals(MkplState.FAILING, ctx.state) @@ -92,6 +96,7 @@ fun validationTitleSymbols(command: MkplCommand, processor: MkplAdProcessor) = r lock = MkplAdLock("123-234-abc-ABC"), ), ) + ctx.addTestPrincipal(stub.ownerId) processor.exec(ctx) assertEquals(1, ctx.errors.size) assertEquals(MkplState.FAILING, ctx.state) diff --git a/ok-marketplace-common/src/commonMain/kotlin/MkplContext.kt b/ok-marketplace-common/src/commonMain/kotlin/MkplContext.kt index 92649e5..e2db730 100644 --- a/ok-marketplace-common/src/commonMain/kotlin/MkplContext.kt +++ b/ok-marketplace-common/src/commonMain/kotlin/MkplContext.kt @@ -2,6 +2,8 @@ package ru.otus.otuskotlin.marketplace.common import kotlinx.datetime.Instant import ru.otus.otuskotlin.marketplace.common.models.* +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalModel +import ru.otus.otuskotlin.marketplace.common.permissions.MkplUserPermissions import ru.otus.otuskotlin.marketplace.common.repo.IAdRepository import ru.otus.otuskotlin.marketplace.common.stubs.MkplStubs @@ -20,6 +22,10 @@ data class MkplContext( var adRepoDone: MkplAd = MkplAd(), var adsRepoDone: MutableList = mutableListOf(), + var principal: MkplPrincipalModel = MkplPrincipalModel.NONE, + val permissionsChain: MutableSet = mutableSetOf(), + var permitted: Boolean = false, + var requestId: MkplRequestId = MkplRequestId.NONE, var timeStart: Instant = Instant.NONE, var adRequest: MkplAd = MkplAd(), diff --git a/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt b/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt index 7784274..a10b4eb 100644 --- a/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt +++ b/ok-marketplace-common/src/commonMain/kotlin/models/MkplAd.kt @@ -2,6 +2,7 @@ package ru.otus.otuskotlin.marketplace.common.models import kotlinx.datetime.Instant import ru.otus.otuskotlin.marketplace.common.NONE +import ru.otus.otuskotlin.marketplace.common.permissions.MkplPrincipalRelations import ru.otus.otuskotlin.marketplace.common.statemachine.SMAdStates data class MkplAd( @@ -17,6 +18,7 @@ data class MkplAd( var timePublished: Instant = Instant.NONE, var timeUpdated: Instant = Instant.NONE, var lock: MkplAdLock = MkplAdLock.NONE, + var principalRelations: Set = emptySet(), val permissionsClient: MutableSet = mutableSetOf() ) { fun deepCopy(): MkplAd = copy( diff --git a/ok-marketplace-common/src/commonMain/kotlin/models/MkplAdFilter.kt b/ok-marketplace-common/src/commonMain/kotlin/models/MkplAdFilter.kt index b7352b4..083ba62 100644 --- a/ok-marketplace-common/src/commonMain/kotlin/models/MkplAdFilter.kt +++ b/ok-marketplace-common/src/commonMain/kotlin/models/MkplAdFilter.kt @@ -4,4 +4,5 @@ data class MkplAdFilter( var searchString: String = "", var ownerId: MkplUserId = MkplUserId.NONE, var dealSide: MkplDealSide = MkplDealSide.NONE, + var searchPermissions: MutableSet = mutableSetOf(), ) diff --git a/ok-marketplace-common/src/commonMain/kotlin/models/MkplSearchPermissions.kt b/ok-marketplace-common/src/commonMain/kotlin/models/MkplSearchPermissions.kt new file mode 100644 index 0000000..675fbd9 --- /dev/null +++ b/ok-marketplace-common/src/commonMain/kotlin/models/MkplSearchPermissions.kt @@ -0,0 +1,7 @@ +package ru.otus.otuskotlin.marketplace.common.models + +enum class MkplSearchPermissions { + OWN, + PUBLIC, + REGISTERED, +} diff --git a/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplPrincipalModel.kt b/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplPrincipalModel.kt new file mode 100644 index 0000000..8b109a5 --- /dev/null +++ b/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplPrincipalModel.kt @@ -0,0 +1,15 @@ +package ru.otus.otuskotlin.marketplace.common.permissions + +import ru.otus.otuskotlin.marketplace.common.models.MkplUserId + +data class MkplPrincipalModel( + val id: MkplUserId = MkplUserId.NONE, + val fname: String = "", + val mname: String = "", + val lname: String = "", + val groups: Set = emptySet() +) { + companion object { + val NONE = MkplPrincipalModel() + } +} diff --git a/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplPrincipalRelations.kt b/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplPrincipalRelations.kt new file mode 100644 index 0000000..5e77fb5 --- /dev/null +++ b/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplPrincipalRelations.kt @@ -0,0 +1,10 @@ +package ru.otus.otuskotlin.marketplace.common.permissions + +enum class MkplPrincipalRelations { + NONE, + NEW, + OWN, + GROUP, + PUBLIC, + MODERATABLE, +} diff --git a/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplUserGroups.kt b/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplUserGroups.kt new file mode 100644 index 0000000..fab09ea --- /dev/null +++ b/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplUserGroups.kt @@ -0,0 +1,9 @@ +package ru.otus.otuskotlin.marketplace.common.permissions + +enum class MkplUserGroups { + USER, + ADMIN_AD, + MODERATOR_MP, + TEST, + BAN_AD +} diff --git a/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplUserPermissions.kt b/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplUserPermissions.kt new file mode 100644 index 0000000..aca34be --- /dev/null +++ b/ok-marketplace-common/src/commonMain/kotlin/permissions/MkplUserPermissions.kt @@ -0,0 +1,26 @@ +package ru.otus.otuskotlin.marketplace.common.permissions + +@Suppress("unused") +enum class MkplUserPermissions { + CREATE_OWN, + + READ_OWN, + READ_GROUP, + READ_PUBLIC, + READ_CANDIDATE, + + UPDATE_OWN, + UPDATE_CANDIDATE, + UPDATE_PUBLIC, + + DELETE_OWN, + DELETE_CANDIDATE, + DELETE_PUBLIC, + + SEARCH_OWN, + SEARCH_PUBLIC, + SEARCH_REGISTERED, + SEARCH_DRAFTS, + + OFFER_FOR_OWN, +} diff --git a/settings.gradle.kts b/settings.gradle.kts index ded96d1..ae12afa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -72,3 +72,5 @@ include("ok-marketplace-repo-stubs") include("ok-marketplace-repo-tests") include("ok-marketplace-repo-cassandra") include("ok-marketplace-repo-gremlin") + +include("ok-marketplace-auth")