Skip to content

Commit

Permalink
chore: Some more perms ?
Browse files Browse the repository at this point in the history
  • Loading branch information
phinner committed Nov 21, 2023
1 parent 7658837 commit f04b22a
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@ import com.xpdustry.imperium.common.security.findMissingUsernameRequirements
import com.xpdustry.imperium.common.snowflake.Snowflake
import com.xpdustry.imperium.common.snowflake.SnowflakeGenerator
import com.xpdustry.imperium.common.snowflake.timestamp
import java.time.Duration
import java.time.Instant
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.toJavaDuration
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.less
import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.batchInsert
import org.jetbrains.exposed.sql.deleteWhere
Expand Down Expand Up @@ -104,6 +107,10 @@ interface AccountManager {
suspend fun getAchievements(
snowflake: Snowflake
): Map<Account.Achievement, Account.Achievement.Progression>

suspend fun incrementGames(snowflake: Snowflake): Boolean

suspend fun incrementPlaytime(snowflake: Snowflake, duration: Duration): Boolean
}

data class AchievementCompletedMessage(
Expand Down Expand Up @@ -357,7 +364,7 @@ class SimpleAccountManager(
AccountSessionTable.insert {
it[account] = result[AccountTable.id]
it[hash] = sessionHash
it[expiration] = Instant.now().plus(SESSION_TOKEN_DURATION)
it[expiration] = Instant.now().plus(SESSION_TOKEN_DURATION.toJavaDuration())
}

return@newSuspendTransaction AccountResult.Success
Expand All @@ -373,7 +380,7 @@ class SimpleAccountManager(

val updated =
AccountSessionTable.update({ AccountSessionTable.hash eq sessionHash }) {
it[expiration] = Instant.now().plus(SESSION_TOKEN_DURATION)
it[expiration] = Instant.now().plus(SESSION_TOKEN_DURATION.toJavaDuration())
}

if (updated == 0) {
Expand Down Expand Up @@ -453,6 +460,18 @@ class SimpleAccountManager(
}
}

override suspend fun incrementGames(snowflake: Snowflake): Boolean =
provider.newSuspendTransaction {
AccountTable.update({ AccountTable.id eq snowflake }) { it[games] = games.plus(1) } != 0
}

override suspend fun incrementPlaytime(snowflake: Snowflake, duration: Duration): Boolean =
provider.newSuspendTransaction {
AccountTable.update({ AccountTable.id eq snowflake }) {
it[playtime] = playtime.plus(duration.toJavaDuration())
} != 0
}

@VisibleForTesting
internal suspend fun createSessionHash(identity: Identity.Mindustry): ByteArray =
Argon2HashFunction.create(
Expand All @@ -475,7 +494,7 @@ class SimpleAccountManager(
completed = this[AccountAchievementTable.completed])

companion object {
private val SESSION_TOKEN_DURATION = Duration.ofDays(7L)
private val SESSION_TOKEN_DURATION = 7.days

private val SESSION_TOKEN_PARAMS =
Argon2Params(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.xpdustry.imperium.common.security.permission
import com.google.common.cache.CacheBuilder
import com.xpdustry.imperium.common.account.AccountManager
import com.xpdustry.imperium.common.application.ImperiumApplication
import com.xpdustry.imperium.common.config.ImperiumConfig
import com.xpdustry.imperium.common.database.SQLProvider
import com.xpdustry.imperium.common.message.Message
import com.xpdustry.imperium.common.message.Messenger
Expand All @@ -39,6 +40,8 @@ import org.jetbrains.exposed.sql.select

interface PermissionManager {

suspend fun hasPermission(identity: Identity.Mindustry, permission: Permission): Boolean

suspend fun getPermission(
identity: Identity.Mindustry,
permission: Permission
Expand Down Expand Up @@ -72,6 +75,7 @@ class SimplePermissionManager(
private val provider: SQLProvider,
private val messenger: Messenger,
private val accounts: AccountManager,
private val config: ImperiumConfig,
) : PermissionManager, ImperiumApplication.Listener {

private val cache =
Expand All @@ -84,6 +88,11 @@ class SimplePermissionManager(
messenger.consumer<PermissionChangeMessage> { refreshPermissions(it.account) }
}

override suspend fun hasPermission(
identity: Identity.Mindustry,
permission: Permission
): Boolean = getPermission(identity, permission).matches(config.server.name)

override suspend fun getPermission(
identity: Identity.Mindustry,
permission: Permission
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ class PermissionSyncListener(instances: InstanceManager) : ImperiumApplication.L
for ((permission, scope) in permissions.getPermissions(snowflake)) {
for (element in config.permissions2roles[permission] ?: emptyList()) {
val role = discord.getMainServer().getRoleById(element).getOrNull() ?: continue
when (scope) {
is Permission.Scope.True -> updater.addRoleToUser(user, role)
else -> updater.removeRoleFromUser(user, role)
if (scope.matches(config.name)) {
updater.addRoleToUser(user, role)
} else {
updater.removeRoleFromUser(user, role)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package com.xpdustry.imperium.mindustry.account

import com.google.common.cache.CacheBuilder
import com.xpdustry.imperium.common.account.AccountManager
import com.xpdustry.imperium.common.account.AccountOperationResult
import com.xpdustry.imperium.common.account.AccountResult
import com.xpdustry.imperium.common.application.ImperiumApplication
import com.xpdustry.imperium.common.async.ImperiumScope
import com.xpdustry.imperium.common.command.Command
Expand All @@ -31,8 +31,9 @@ import com.xpdustry.imperium.common.misc.logger
import com.xpdustry.imperium.common.security.PasswordRequirement
import com.xpdustry.imperium.common.security.UsernameRequirement
import com.xpdustry.imperium.common.security.VerificationMessage
import com.xpdustry.imperium.common.security.permission.Role
import com.xpdustry.imperium.common.security.permission.containsRole
import com.xpdustry.imperium.common.security.permission.Permission
import com.xpdustry.imperium.common.security.permission.PermissionManager
import com.xpdustry.imperium.common.snowflake.Snowflake
import com.xpdustry.imperium.mindustry.command.annotation.ClientSide
import com.xpdustry.imperium.mindustry.misc.Entities
import com.xpdustry.imperium.mindustry.misc.identity
Expand All @@ -55,7 +56,6 @@ import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mindustry.gen.Player
import org.bson.types.ObjectId

// TODO
// - Replace sequential interfaces with a proper form interface
Expand All @@ -68,16 +68,17 @@ private val OLD_PASSWORD = stateKey<String>("old_password")

class AccountCommand(instances: InstanceManager) : ImperiumApplication.Listener {

private val permissions = instances.get<PermissionManager>()
private val manager = instances.get<AccountManager>()
private val messenger = instances.get<Messenger>()
private val loginInterface = createLoginInterface(instances.get(), manager)
private val loginInterface = createLoginInterface(instances.get(), manager, permissions)
private val registerInterface = createRegisterInterface(instances.get(), manager)
private val migrateInterface = createMigrateInterface(instances.get(), manager)
private val changePasswordInterface = createPasswordChangeInterface(instances.get(), manager)
private val verifications =
CacheBuilder.newBuilder()
.expireAfterWrite(10.minutes.toJavaDuration())
.build<ObjectId, Int>()
.build<Snowflake, Int>()

@Command(["login"])
@ClientSide
Expand Down Expand Up @@ -148,8 +149,8 @@ class AccountCommand(instances: InstanceManager) : ImperiumApplication.Listener
}

code = Random.nextInt(1000..9999)
messenger.publish(VerificationMessage(account._id, sender.player.uuid(), code))
verifications.put(account._id, code)
messenger.publish(VerificationMessage(account.snowflake, sender.player.uuid(), code))
verifications.put(account.snowflake, code)

sender.sendMessage(
"""
Expand Down Expand Up @@ -190,7 +191,11 @@ private class PlayerCoroutineExceptionHandler(
}
}

private fun createLoginInterface(plugin: MindustryPlugin, manager: AccountManager): Interface {
private fun createLoginInterface(
plugin: MindustryPlugin,
manager: AccountManager,
permissions: PermissionManager
): Interface {
val usernameInterface = TextInputInterface.create(plugin)
val passwordInterface = TextInputInterface.create(plugin)

Expand All @@ -217,18 +222,15 @@ private fun createLoginInterface(plugin: MindustryPlugin, manager: AccountManage
when (val result =
manager.login(
view.state[USERNAME]!!, value.toCharArray(), view.viewer.identity)) {
is AccountOperationResult.Success -> {
is AccountResult.Success -> {
view.viewer.sendMessage("You have been logged in!")
// Grants admin to moderators
view.viewer.admin =
manager
.findByIdentity(view.viewer.identity)
?.roles
?.containsRole(Role.MODERATOR)
?: view.viewer.admin
permissions.hasPermission(
view.viewer.identity, Permission.SEE_USER_INFO) || view.viewer.admin
}
is AccountOperationResult.WrongPassword,
AccountOperationResult.NotRegistered -> {
is AccountResult.WrongPassword,
AccountResult.NotFound -> {
view.open()
view.viewer.showInfoMessage("The username or password is incorrect!")
}
Expand Down Expand Up @@ -281,10 +283,8 @@ private fun createRegisterInterface(plugin: MindustryPlugin, manager: AccountMan
return@BiAction
}
ImperiumScope.MAIN.launch(PlayerCoroutineExceptionHandler(view)) {
when (val result =
manager.register(
view.state[USERNAME]!!, value.toCharArray(), view.viewer.identity)) {
is AccountOperationResult.Success -> {
when (val result = manager.register(view.state[USERNAME]!!, value.toCharArray())) {
is AccountResult.Success -> {
view.viewer.sendMessage(
"Your account have been created! You can do /login now.")
}
Expand Down Expand Up @@ -336,11 +336,8 @@ fun createMigrateInterface(plugin: MindustryPlugin, manager: AccountManager): In
ImperiumScope.MAIN.launch(PlayerCoroutineExceptionHandler(view)) {
when (val result =
manager.migrate(
view.state[OLD_USERNAME]!!,
value,
view.state[PASSWORD]!!.toCharArray(),
view.viewer.identity)) {
is AccountOperationResult.Success -> {
view.state[OLD_USERNAME]!!, value, view.state[PASSWORD]!!.toCharArray())) {
is AccountResult.Success -> {
view.viewer.sendMessage(
"Your account have been migrated! You can do /login now.")
}
Expand Down Expand Up @@ -401,7 +398,7 @@ private fun createPasswordChangeInterface(
view.state[OLD_PASSWORD]!!.toCharArray(),
value.toCharArray(),
view.viewer.identity)) {
is AccountOperationResult.Success -> {
is AccountResult.Success -> {
view.viewer.sendMessage("Your password have been changed!")
}
else -> {
Expand All @@ -415,25 +412,22 @@ private fun createPasswordChangeInterface(
return oldPasswordInterface
}

private suspend fun handleAccountResult(result: AccountOperationResult, view: View) =
runMindustryThread {
val message =
when (result) {
is AccountOperationResult.Success -> "Success!"
is AccountOperationResult.AlreadyRegistered -> "This account is already registered!"
is AccountOperationResult.NotRegistered -> "You are not registered!"
is AccountOperationResult.NotLogged -> "You are not logged in! Use /login to login."
is AccountOperationResult.WrongPassword -> "Wrong password!"
is AccountOperationResult.InvalidPassword ->
"The password does not meet the requirements:\n ${result.missing.joinToString("\n- ", transform = ::getErrorMessage)}"
is AccountOperationResult.InvalidUsername ->
"The username does not meet the requirements:\n ${result.missing.joinToString("\n- ", transform = ::getErrorMessage)}"
is AccountOperationResult.RateLimit ->
"You have made too many attempts, please try again later."
}
view.open()
view.viewer.showInfoMessage("[red]$message")
}
private suspend fun handleAccountResult(result: AccountResult, view: View) = runMindustryThread {
val message =
when (result) {
is AccountResult.Success -> "Success!"
is AccountResult.AlreadyRegistered -> "This account is already registered!"
is AccountResult.NotFound -> "You are not registered!"
is AccountResult.WrongPassword -> "Wrong password!"
is AccountResult.InvalidPassword ->
"The password does not meet the requirements:\n ${result.missing.joinToString("\n- ", transform = ::getErrorMessage)}"
is AccountResult.InvalidUsername ->
"The username does not meet the requirements:\n ${result.missing.joinToString("\n- ", transform = ::getErrorMessage)}"
is AccountResult.AlreadyLogged -> "You are already logged in."
}
view.open()
view.viewer.showInfoMessage("[red]$message")
}

private fun getErrorMessage(requirement: PasswordRequirement) =
when (requirement) {
Expand All @@ -452,4 +446,5 @@ private fun getErrorMessage(requirement: UsernameRequirement) =
is UsernameRequirement.Length ->
"It needs to be between ${requirement.min} and ${requirement.max} characters long."
is UsernameRequirement.Reserved -> "This username is reserved or already taken."
is UsernameRequirement.AllLowercase -> "Uppercase letters aren't allowed in the username."
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@
package com.xpdustry.imperium.mindustry.account

import com.xpdustry.imperium.common.account.AccountManager
import com.xpdustry.imperium.common.account.User
import com.xpdustry.imperium.common.account.UserManager
import com.xpdustry.imperium.common.application.ImperiumApplication
import com.xpdustry.imperium.common.async.ImperiumScope
import com.xpdustry.imperium.common.inject.InstanceManager
import com.xpdustry.imperium.common.inject.get
import com.xpdustry.imperium.common.misc.toInetAddress
import com.xpdustry.imperium.common.security.Identity
import com.xpdustry.imperium.common.user.UserManager
import com.xpdustry.imperium.mindustry.misc.Entities
import com.xpdustry.imperium.mindustry.misc.identity
import com.xpdustry.imperium.mindustry.misc.tryGrantAdmin
Expand All @@ -34,7 +32,6 @@ import com.xpdustry.imperium.mindustry.security.GatekeeperResult
import fr.xpdustry.distributor.api.event.EventHandler
import fr.xpdustry.distributor.api.util.Priority
import java.time.Duration
import java.time.Instant
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.launch
import mindustry.game.EventType
Expand All @@ -59,24 +56,15 @@ class AccountListener(instances: InstanceManager) : ImperiumApplication.Listener
internal fun onPlayerJoin(event: EventType.PlayerJoin) {
playtime[event.player] = System.currentTimeMillis()
ImperiumScope.MAIN.launch {
users.updateOrCreateByUuid(event.player.uuid()) { user ->
user.timesJoined += 1
user.lastName = event.player.plainName()
user.names += event.player.plainName()
user.lastAddress = event.player.ip().toInetAddress()
user.addresses += event.player.ip().toInetAddress()
user.lastJoin = Instant.now()
}
users.incrementJoins(event.player.identity)
event.player.tryGrantAdmin(accounts)
}
}

@EventHandler
internal fun onGameOver(event: EventType.GameOverEvent) {
Entities.getPlayers().forEach { player ->
ImperiumScope.MAIN.launch {
accounts.updateByIdentity(player.identity) { account -> account.games++ }
}
ImperiumScope.MAIN.launch { accounts.incrementGames(accounts.findByIdentity()) }
}
}

Expand Down

0 comments on commit f04b22a

Please sign in to comment.