Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,42 @@ The plugin requires the gRPC target to be provided via environment variable:
export PLAYER_PRESENCE_GRPC_TARGET="dns:///service-player.api.svc.cluster.local:9000"
```

Permissions use the same target by default but can be overridden:

```bash
export PERMISSIONS_GRPC_TARGET="dns:///service-player.api.svc.cluster.local:9000"
export PERMISSIONS_CACHE_REFRESH_SECONDS="30"
# Optional: gRPC target for permissions events (defaults to PERMISSIONS_GRPC_TARGET if unset)
export PERMISSIONS_EVENTS_GRPC_TARGET="dns:///service-player.api.svc.cluster.local:9000"
# Optional: unique identifier for this server instance when handling permissions events
export PERMISSIONS_EVENTS_SERVER_ID="velocity-1"
```

Messages are configured in `velocity/src/main/resources/messages.yml` (copied to the plugin data
directory on first run).

## Commands

```text
/permissions help
/permissions refresh [player|uuid]
/permissions player <player|uuid> info
/permissions player <player|uuid> check <permission>
/permissions player <player|uuid> refresh
/permissions player <player|uuid> permission add <permission> [duration]
/permissions player <player|uuid> permission remove <permission>
/permissions player <player|uuid> group add <group> [duration]
/permissions player <player|uuid> group remove <group>
/permissions group list
/permissions group <group> create
/permissions group <group> info
/permissions group <group> delete
/permissions group <group> permission add <permission> [duration]
/permissions group <group> permission remove <permission>
```

Durations use a single suffix: `30m`, `1h`, `7d`, `2w` (seconds `s`, minutes `m`, hours `h`, days `d`, weeks `w`).

## Development

Run in dev mode with live reload using DevSpace in a Kubernetes cluster:
Expand Down
5 changes: 4 additions & 1 deletion common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ repositories {
}
}

dependencies { protobuf("gg.grounds:library-grpc-contracts-player:0.1.0") }
dependencies {
protobuf("gg.grounds:library-grpc-contracts-player:0.1.0")
protobuf("gg.grounds:library-grpc-contracts-permission:feat-perm-protos-SNAPSHOT")
}
32 changes: 32 additions & 0 deletions common/src/main/kotlin/gg/grounds/grpc/BaseGrpcClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gg.grounds.grpc

import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import java.util.concurrent.TimeUnit

abstract class BaseGrpcClient(protected val channel: ManagedChannel) : AutoCloseable {
override fun close() {
closeChannel(channel)
}

companion object {
fun createChannel(target: String): ManagedChannel {
val channelBuilder = ManagedChannelBuilder.forTarget(target)
channelBuilder.usePlaintext()
return channelBuilder.build()
}

fun closeChannel(channel: ManagedChannel) {
channel.shutdown()
try {
if (!channel.awaitTermination(3, TimeUnit.SECONDS)) {
channel.shutdownNow()
channel.awaitTermination(3, TimeUnit.SECONDS)
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
channel.shutdownNow()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package gg.grounds.permissions

import gg.grounds.grpc.BaseGrpcClient
import gg.grounds.grpc.permissions.AddGroupPermissionsReply
import gg.grounds.grpc.permissions.AddGroupPermissionsRequest
import gg.grounds.grpc.permissions.AddPlayerGroupsReply
import gg.grounds.grpc.permissions.AddPlayerGroupsRequest
import gg.grounds.grpc.permissions.AddPlayerPermissionsReply
import gg.grounds.grpc.permissions.AddPlayerPermissionsRequest
import gg.grounds.grpc.permissions.CreateGroupReply
import gg.grounds.grpc.permissions.CreateGroupRequest
import gg.grounds.grpc.permissions.DeleteGroupReply
import gg.grounds.grpc.permissions.DeleteGroupRequest
import gg.grounds.grpc.permissions.GetGroupReply
import gg.grounds.grpc.permissions.GetGroupRequest
import gg.grounds.grpc.permissions.ListGroupsReply
import gg.grounds.grpc.permissions.ListGroupsRequest
import gg.grounds.grpc.permissions.PermissionsAdminServiceGrpc
import gg.grounds.grpc.permissions.RemoveGroupPermissionsReply
import gg.grounds.grpc.permissions.RemoveGroupPermissionsRequest
import gg.grounds.grpc.permissions.RemovePlayerGroupsReply
import gg.grounds.grpc.permissions.RemovePlayerGroupsRequest
import gg.grounds.grpc.permissions.RemovePlayerPermissionsReply
import gg.grounds.grpc.permissions.RemovePlayerPermissionsRequest
import io.grpc.ManagedChannel
import java.util.concurrent.TimeUnit

class GrpcPermissionsAdminClient
private constructor(
channel: ManagedChannel,
private val stub: PermissionsAdminServiceGrpc.PermissionsAdminServiceBlockingStub,
) : BaseGrpcClient(channel) {
fun createGroup(request: CreateGroupRequest): CreateGroupReply =
stub.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS).createGroup(request)

fun deleteGroup(request: DeleteGroupRequest): DeleteGroupReply =
stub.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS).deleteGroup(request)

fun getGroup(request: GetGroupRequest): GetGroupReply =
stub.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS).getGroup(request)

fun listGroups(request: ListGroupsRequest): ListGroupsReply =
stub.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS).listGroups(request)

fun addGroupPermissions(request: AddGroupPermissionsRequest): AddGroupPermissionsReply =
stub
.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.addGroupPermissions(request)

fun removeGroupPermissions(
request: RemoveGroupPermissionsRequest
): RemoveGroupPermissionsReply =
stub
.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.removeGroupPermissions(request)

fun addPlayerPermissions(request: AddPlayerPermissionsRequest): AddPlayerPermissionsReply =
stub
.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.addPlayerPermissions(request)

fun removePlayerPermissions(
request: RemovePlayerPermissionsRequest
): RemovePlayerPermissionsReply =
stub
.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.removePlayerPermissions(request)

fun addPlayerGroups(request: AddPlayerGroupsRequest): AddPlayerGroupsReply =
stub.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS).addPlayerGroups(request)

fun removePlayerGroups(request: RemovePlayerGroupsRequest): RemovePlayerGroupsReply =
stub
.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.removePlayerGroups(request)

companion object {
fun create(target: String): GrpcPermissionsAdminClient {
val channel = createChannel(target)
val stub = PermissionsAdminServiceGrpc.newBlockingStub(channel)
return GrpcPermissionsAdminClient(channel, stub)
}

private const val DEFAULT_TIMEOUT_MS = 2000L
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package gg.grounds.permissions

import gg.grounds.grpc.BaseGrpcClient
import gg.grounds.grpc.permissions.CheckPlayerPermissionReply
import gg.grounds.grpc.permissions.CheckPlayerPermissionRequest
import gg.grounds.grpc.permissions.GetPlayerPermissionsReply
import gg.grounds.grpc.permissions.GetPlayerPermissionsRequest
import gg.grounds.grpc.permissions.PermissionsServiceGrpc
import io.grpc.ManagedChannel
import io.grpc.Status
import io.grpc.StatusRuntimeException
import java.util.UUID
import java.util.concurrent.TimeUnit

class GrpcPermissionsClient
private constructor(
channel: ManagedChannel,
private val stub: PermissionsServiceGrpc.PermissionsServiceBlockingStub,
) : BaseGrpcClient(channel) {
fun getPlayerPermissions(
playerId: UUID,
includeEffectivePermissions: Boolean = true,
includeDirectPermissions: Boolean = true,
includeGroups: Boolean = true,
): GetPlayerPermissionsReply {
return stub
.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.getPlayerPermissions(
GetPlayerPermissionsRequest.newBuilder()
.setPlayerId(playerId.toString())
.setIncludeEffectivePermissions(includeEffectivePermissions)
.setIncludeDirectPermissions(includeDirectPermissions)
.setIncludeGroups(includeGroups)
.build()
)
}

fun checkPlayerPermission(playerId: UUID, permission: String): CheckPlayerPermissionReply {
return stub
.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.checkPlayerPermission(
CheckPlayerPermissionRequest.newBuilder()
.setPlayerId(playerId.toString())
.setPermission(permission)
.build()
)
}

companion object {
fun create(target: String): GrpcPermissionsClient {
val channel = createChannel(target)
val stub = PermissionsServiceGrpc.newBlockingStub(channel)
return GrpcPermissionsClient(channel, stub)
}

fun isServiceUnavailable(status: Status.Code): Boolean =
status == Status.Code.UNAVAILABLE || status == Status.Code.DEADLINE_EXCEEDED

fun isServiceUnavailable(error: StatusRuntimeException): Boolean =
isServiceUnavailable(error.status.code)

private const val DEFAULT_TIMEOUT_MS = 2000L
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package gg.grounds.permissions

import gg.grounds.grpc.BaseGrpcClient
import gg.grounds.grpc.permissions.PermissionsChangeEvent
import gg.grounds.grpc.permissions.PermissionsEventsServiceGrpc
import gg.grounds.grpc.permissions.SubscribePermissionsChangesRequest
import io.grpc.ManagedChannel
import io.grpc.stub.StreamObserver

class GrpcPermissionsEventsClient
private constructor(
channel: ManagedChannel,
private val stub: PermissionsEventsServiceGrpc.PermissionsEventsServiceStub,
) : BaseGrpcClient(channel) {
fun subscribe(
request: SubscribePermissionsChangesRequest,
observer: StreamObserver<PermissionsChangeEvent>,
) {
stub.subscribePermissionsChanges(request, observer)
}

companion object {
fun create(target: String): GrpcPermissionsEventsClient {
val channel = createChannel(target)
val stub = PermissionsEventsServiceGrpc.newStub(channel)
return GrpcPermissionsEventsClient(channel, stub)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package gg.grounds.player.presence
package gg.grounds.presence

import gg.grounds.grpc.BaseGrpcClient
import gg.grounds.grpc.player.PlayerLoginRequest
import gg.grounds.grpc.player.PlayerLogoutReply
import gg.grounds.grpc.player.PlayerLogoutRequest
import gg.grounds.grpc.player.PlayerPresenceServiceGrpc
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import io.grpc.Status
import io.grpc.StatusRuntimeException
import java.util.UUID
import java.util.concurrent.TimeUnit

class GrpcPlayerPresenceClient
private constructor(
private val channel: ManagedChannel,
channel: ManagedChannel,
private val stub: PlayerPresenceServiceGrpc.PlayerPresenceServiceBlockingStub,
) : AutoCloseable {
) : BaseGrpcClient(channel) {
fun tryLogin(playerId: UUID): PlayerLoginResult {
return try {
val reply =
Expand Down Expand Up @@ -49,24 +49,9 @@ private constructor(
}
}

override fun close() {
channel.shutdown()
try {
if (!channel.awaitTermination(3, TimeUnit.SECONDS)) {
channel.shutdownNow()
channel.awaitTermination(3, TimeUnit.SECONDS)
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
channel.shutdownNow()
}
}

companion object {
fun create(target: String): GrpcPlayerPresenceClient {
val channelBuilder = ManagedChannelBuilder.forTarget(target)
channelBuilder.usePlaintext()
val channel = channelBuilder.build()
val channel = createChannel(target)
val stub = PlayerPresenceServiceGrpc.newBlockingStub(channel)
return GrpcPlayerPresenceClient(channel, stub)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gg.grounds.player.presence
package gg.grounds.presence

import gg.grounds.grpc.player.PlayerLoginReply

Expand Down
1 change: 1 addition & 0 deletions velocity/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ dependencies {
implementation("tools.jackson.dataformat:jackson-dataformat-yaml:3.0.3")
implementation("tools.jackson.module:jackson-module-kotlin:3.0.3")
implementation("io.grpc:grpc-netty-shaded:1.78.0")
implementation("io.grpc:grpc-stub:1.78.0")
}
Loading