Skip to content

Commit

Permalink
#157 Fix double adam token issuing
Browse files Browse the repository at this point in the history
  • Loading branch information
vityaman committed Jun 17, 2024
1 parent c977fd1 commit 22fe90e
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 75 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ru.vityaman.lms.botalka.app.spring.security

import kotlinx.coroutines.runBlocking
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import ru.vityaman.lms.botalka.app.spring.storage.MainR2dbcConfig
import ru.vityaman.lms.botalka.app.spring.storage.SpringMigration
import ru.vityaman.lms.botalka.core.logging.Slf4jLog
import ru.vityaman.lms.botalka.core.security.Adam
import ru.vityaman.lms.botalka.core.security.AdamSource
import ru.vityaman.lms.botalka.core.security.auth.TokenService
import ru.vityaman.lms.botalka.core.storage.ConfigStorage
import ru.vityaman.lms.botalka.core.storage.UserStorage
import ru.vityaman.lms.botalka.core.tx.TxEnv

@Configuration
class SpringAdamSource(
migration: SpringMigration,

users: UserStorage,
tokens: TokenService,
config: ConfigStorage,

@Qualifier(MainR2dbcConfig.BeanName.TX_ENV)
txEnv: TxEnv,
) {
private val source = AdamSource(
users = users,
tokens = tokens,
config = config,
txEnv = txEnv,
log = Slf4jLog("SpringAdamCreation"),
)

init {
migration.let { }
}

@Bean
fun adam(): Adam = runBlocking { source.adam() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ru.vityaman.lms.botalka.core.event.domain
import kotlinx.coroutines.flow.Flow
import ru.vityaman.lms.botalka.core.event.Events
import ru.vityaman.lms.botalka.core.model.Homework
import ru.vityaman.lms.botalka.core.storage.FetchPolicy
import ru.vityaman.lms.botalka.core.storage.HomeworkStorage
import ru.vityaman.lms.botalka.core.tx.TxEnv
import java.time.Clock
Expand All @@ -23,5 +24,5 @@ class HomeworkEvents(
event.isPublished

override suspend fun acquireById(id: Homework.Id): Homework =
storage.acquireById(id)
storage.getById(id, FetchPolicy.WRITE_LOCKED)!!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ru.vityaman.lms.botalka.core.security

import ru.vityaman.lms.botalka.core.logging.Log
import ru.vityaman.lms.botalka.core.model.User
import ru.vityaman.lms.botalka.core.security.auth.AccessToken
import ru.vityaman.lms.botalka.core.security.auth.TokenService
import ru.vityaman.lms.botalka.core.storage.ConfigStorage
import ru.vityaman.lms.botalka.core.storage.FetchPolicy
import ru.vityaman.lms.botalka.core.storage.UserStorage
import ru.vityaman.lms.botalka.core.tx.TxEnv

data class Adam(val user: User, val token: AccessToken?)

class AdamSource(
private val users: UserStorage,
private val tokens: TokenService,
private val config: ConfigStorage,
private val txEnv: TxEnv,
private val log: Log,
) {
suspend fun adam(): Adam = txEnv.transactional {
val user = users.getByAlias(alias, FetchPolicy.WRITE_LOCKED)!!
log.info("Got $alias as $user")

val token = if (config.isInitialized()) {
buildString {
append("Service is already initialized, ")
append("don't issue access token")
}.let { log.info(it) }

null
} else {
log.info("Creating the world of LMS...")
tokens.issue(AccessToken.Payload(user.id))
.also { config.markInitialized() }
.also { log.warn("Adam token: '${it.text}'") }
}

Adam(user, token)
}

companion object {
private val alias = User.Alias("adam")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ru.vityaman.lms.botalka.core.storage

enum class FetchPolicy {
SNAPSHOT,
WRITE_LOCKED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import java.time.OffsetDateTime

interface HomeworkStorage {
suspend fun create(homework: Homework.Draft): Homework
suspend fun getById(id: Homework.Id): Homework?

suspend fun acquireById(id: Homework.Id): Homework
suspend fun getById(
id: Homework.Id,
policy: FetchPolicy = FetchPolicy.SNAPSHOT,
): Homework?

fun publishableAt(moment: OffsetDateTime): Flow<Homework.Id>
suspend fun markPublished(id: Homework.Id)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import ru.vityaman.lms.botalka.core.model.User

interface UserStorage {
suspend fun getById(id: User.Id): User?
suspend fun getByAlias(alias: User.Alias): User?

suspend fun getByAlias(
alias: User.Alias,
policy: FetchPolicy = FetchPolicy.SNAPSHOT,
): User?

suspend fun create(user: User.Draft): User
suspend fun create(teacher: Teacher)
suspend fun create(student: Student)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ru.vityaman.lms.botalka.storage.jooq

import org.jooq.Record
import org.jooq.SelectConditionStep
import ru.vityaman.lms.botalka.core.storage.FetchPolicy

internal fun <R : Record> SelectConditionStep<R>.withPolicy(
policy: FetchPolicy,
) = when (policy) {
FetchPolicy.SNAPSHOT -> this
FetchPolicy.WRITE_LOCKED -> this.forUpdate()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ru.vityaman.lms.botalka.storage.jooq
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import ru.vityaman.lms.botalka.core.model.Homework
import ru.vityaman.lms.botalka.core.storage.FetchPolicy
import ru.vityaman.lms.botalka.core.storage.HomeworkStorage
import ru.vityaman.lms.botalka.storage.jooq.entity.toModel
import ru.vityaman.lms.botalka.storage.jooq.tables.references.HOMEWORK
Expand Down Expand Up @@ -32,19 +33,16 @@ class JooqHomeworkStorage(
.coerce(HOMEWORK)
}.toModel()

override suspend fun getById(id: Homework.Id): Homework? =
override suspend fun getById(
id: Homework.Id,
policy: FetchPolicy,
): Homework? =
database.maybe {
selectFrom(HOMEWORK)
.where(HOMEWORK.ID.eq(id.number))
.withPolicy(policy)
}?.toModel()

override suspend fun acquireById(id: Homework.Id): Homework =
database.only {
selectFrom(HOMEWORK)
.where(HOMEWORK.ID.eq(id.number))
.forUpdate()
}.toModel()

override fun publishableAt(
moment: OffsetDateTime,
): Flow<Homework.Id> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ru.vityaman.lms.botalka.core.model.Admin
import ru.vityaman.lms.botalka.core.model.Student
import ru.vityaman.lms.botalka.core.model.Teacher
import ru.vityaman.lms.botalka.core.model.User
import ru.vityaman.lms.botalka.core.storage.FetchPolicy
import ru.vityaman.lms.botalka.core.storage.UserStorage
import ru.vityaman.lms.botalka.storage.jooq.entity.toModel
import ru.vityaman.lms.botalka.storage.jooq.exception.UniqueViolationException
Expand All @@ -23,10 +24,14 @@ class JooqUserStorage(
.where(USER.ID.equal(id.number))
}?.toModel()?.equipped()

override suspend fun getByAlias(alias: User.Alias): User? =
override suspend fun getByAlias(
alias: User.Alias,
policy: FetchPolicy,
): User? =
database.maybe {
selectFrom(USER)
.where(USER.ALIAS.equal(alias.text))
.withPolicy(policy)
}?.toModel()?.equipped()

override suspend fun create(user: User.Draft): User = try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import ru.vityaman.lms.botalka.app.spring.api.http.client.Api
import ru.vityaman.lms.botalka.app.spring.security.SpringAdam
import ru.vityaman.lms.botalka.app.spring.storage.BrokerContainerInitializer
import ru.vityaman.lms.botalka.app.spring.storage.DatabaseContainerInitializer
import ru.vityaman.lms.botalka.app.spring.storage.MainR2dbcConfig
import ru.vityaman.lms.botalka.app.spring.storage.SpringMigration
import ru.vityaman.lms.botalka.core.security.Adam
import ru.vityaman.lms.botalka.storage.jooq.JooqDatabase
import ru.vityaman.lms.botalka.storage.jooq.Lms.Companion.LMS as MainLMS

Expand All @@ -40,7 +40,7 @@ abstract class BotalkaTestSuite {
private lateinit var migration: SpringMigration

@Autowired
private lateinit var adam: SpringAdam
private lateinit var adam: Adam

protected lateinit var admin: Api

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import ru.vityaman.lms.botalka.app.spring.api.http.client.apis.HomeworkApi
import ru.vityaman.lms.botalka.app.spring.api.http.client.apis.MonitoringApi
import ru.vityaman.lms.botalka.app.spring.api.http.client.apis.RatingApi
import ru.vityaman.lms.botalka.app.spring.api.http.client.apis.UserApi
import ru.vityaman.lms.botalka.app.spring.security.SpringAdam
import ru.vityaman.lms.botalka.core.security.Adam

class Api private constructor(
private val data: Data? = null,
Expand Down Expand Up @@ -43,7 +43,7 @@ class Api private constructor(
fun ofNewbie() =
Api()

fun ofAdam(adam: SpringAdam) =
fun ofAdam(adam: Adam) =
Api(Data(adam.user.id.number, adam.token!!.text))

private suspend fun ofRegisteredAs(message: PostUserRequestMessage) =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ru.vityaman.lms.botalka.app.spring.security

import io.kotest.common.runBlocking
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import ru.vityaman.lms.botalka.app.spring.BotalkaTestSuite
import java.util.concurrent.atomic.AtomicInteger

class AdamTest(
@Autowired private val adam: SpringAdamSource,
) : BotalkaTestSuite() {
@Test
fun issueTokenExactlyOnce(): Unit = runBlocking {
val tokenIssuedCount = AtomicInteger(0)

coroutineScope {
repeat(4) {
launch {
val adam = adam.adam()
if (adam.token != null) {
tokenIssuedCount.addAndGet(1)
}
}
}
}

// As Adam was already created at startup
tokenIssuedCount shouldBe 0
}
}

0 comments on commit 22fe90e

Please sign in to comment.