Skip to content
Draft
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.deck.app.controller

import io.deck.iam.ManagementType
import io.deck.iam.api.PublicEmailDomainPolicy
import io.deck.iam.api.WorkspaceDirectory
import io.deck.iam.api.WorkspaceInvitationManager
import io.deck.iam.api.WorkspaceManagedType
import io.deck.iam.api.WorkspaceRoster
import io.deck.iam.api.WorkspaceUserLookup
import jakarta.validation.Valid
Expand Down Expand Up @@ -37,18 +37,18 @@ class MyWorkspaceController(
private val publicEmailDomainPolicy: PublicEmailDomainPolicy,
) {
private fun ensureUserManagedEnabled() {
workspaceDirectory.ensureManagedTypeEnabled(WorkspaceManagedType.USER_MANAGED)
workspaceDirectory.ensureManagedTypeEnabled(ManagementType.USER_MANAGED)
}

private fun ensureUserManagedAccessible(workspaceId: UUID) {
workspaceDirectory.ensureAccessible(workspaceId, WorkspaceManagedType.USER_MANAGED)
private fun ensureReadableWorkspace(workspaceId: UUID) {
workspaceDirectory.ensureAccessible(workspaceId)
}

private fun verifyUserManagedOwner(
workspaceId: UUID,
userId: UUID,
) {
workspaceDirectory.verifyOwner(workspaceId, userId, WorkspaceManagedType.USER_MANAGED)
workspaceDirectory.verifyOwner(workspaceId, userId, ManagementType.USER_MANAGED)
}

@GetMapping
Expand All @@ -74,7 +74,7 @@ class MyWorkspaceController(
principal: Principal,
): ResponseEntity<MyWorkspaceDto> {
val userId = UUID.fromString(principal.name)
val workspace = workspaceDirectory.createForUser(request.name, request.description, userId, request.allowedDomains)
val workspace = workspaceDirectory.createForUser(request.name, request.description, userId, request.autoJoinDomains)
val memberCount = workspaceRoster.countByWorkspaceId(workspace.id).toInt()
return ResponseEntity.status(HttpStatus.CREATED).body(workspace.toMyWorkspaceDto(memberCount, true, loadOwnersByWorkspaceIds(listOf(workspace.id))[workspace.id].orEmpty()))
}
Expand All @@ -93,8 +93,8 @@ class MyWorkspaceController(
name = request.name,
description = request.description,
requestedBy = userId,
managedType = WorkspaceManagedType.USER_MANAGED,
allowedDomains = request.allowedDomains,
managedType = ManagementType.USER_MANAGED,
autoJoinDomains = request.autoJoinDomains,
)
val memberCount = workspaceRoster.countByWorkspaceId(id).toInt()
return ResponseEntity.ok(updated.toMyWorkspaceDto(memberCount, true, loadOwnersByWorkspaceIds(listOf(id))[id].orEmpty()))
Expand All @@ -107,7 +107,7 @@ class MyWorkspaceController(
principal: Principal,
): ResponseEntity<Void> {
val userId = UUID.fromString(principal.name)
workspaceDirectory.deleteBatch(request.workspaceIds, userId, WorkspaceManagedType.USER_MANAGED)
workspaceDirectory.deleteBatch(request.workspaceIds, userId, ManagementType.USER_MANAGED)
return ResponseEntity.noContent().build()
}

Expand All @@ -120,7 +120,7 @@ class MyWorkspaceController(
principal: Principal,
): ResponseEntity<List<WorkspaceMemberDto>> {
val userId = UUID.fromString(principal.name)
ensureUserManagedAccessible(id)
ensureReadableWorkspace(id)
val members = workspaceRoster.listMembersIfMember(id, userId)
val userIds = members.map { it.userId }.distinct()
val users = workspaceUserLookup.findAllByIds(userIds).associateBy { it.id }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.deck.app.controller

import io.deck.iam.ManagementType
import io.deck.iam.api.WorkspaceDirectory
import io.deck.iam.api.WorkspaceInvitationManager
import io.deck.iam.api.WorkspaceManagedType
import io.deck.iam.api.WorkspaceRoster
import io.deck.iam.api.WorkspaceUserLookup
import jakarta.validation.Valid
Expand Down Expand Up @@ -32,15 +32,19 @@ class WorkspaceController(
private val workspaceInvitations: WorkspaceInvitationManager,
private val workspaceUserLookup: WorkspaceUserLookup,
) {
private fun ensureOwnerManagedEnabled() {
workspaceDirectory.ensureManagedTypeEnabled(WorkspaceManagedType.SYSTEM_MANAGED)
private fun ensurePlatformManagedEnabled() {
workspaceDirectory.ensureManagedTypeEnabled(ManagementType.PLATFORM_MANAGED)
}

private fun requirePlatformManagedWorkspace(workspaceId: UUID) {
workspaceDirectory.getPlatformManaged(workspaceId)
}

@GetMapping
@PreAuthorize("hasAuthority(@P.WORKSPACE_MANAGEMENT_READ)")
fun list(): ResponseEntity<List<WorkspaceDto>> {
ensureOwnerManagedEnabled()
val workspaces = workspaceDirectory.listAll()
ensurePlatformManagedEnabled()
val workspaces = workspaceDirectory.listPlatformManaged()
val memberCounts = workspaceRoster.countByWorkspaceIds(workspaces.map { it.id })
val ownersByWorkspaceId = loadOwnersByWorkspaceIds(workspaces.map { it.id })
val dtos =
Expand All @@ -57,14 +61,13 @@ class WorkspaceController(
@Valid @RequestBody request: CreateWorkspaceRequest,
principal: Principal,
): ResponseEntity<WorkspaceDto> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
val workspace =
workspaceDirectory.create(
workspaceDirectory.createPlatformManaged(
name = request.name,
description = request.description,
initialOwnerId = UUID.fromString(principal.name),
managedType = request.managedType ?: WorkspaceManagedType.SYSTEM_MANAGED,
allowedDomains = request.allowedDomains,
autoJoinDomains = request.autoJoinDomains,
)
val memberCount = workspaceRoster.countByWorkspaceId(workspace.id).toInt()
return ResponseEntity.status(HttpStatus.CREATED).body(workspace.toWorkspaceDto(memberCount, loadOwners(workspace.id)))
Expand All @@ -77,15 +80,14 @@ class WorkspaceController(
@Valid @RequestBody request: UpdateWorkspaceRequest,
principal: Principal,
): ResponseEntity<WorkspaceDto> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
val updated =
workspaceDirectory.updateByAdmin(
workspaceDirectory.updatePlatformManaged(
workspaceId = id,
name = request.name,
description = request.description,
updatedBy = UUID.fromString(principal.name),
managedType = request.managedType ?: workspaceDirectory.get(id).managedType,
allowedDomains = request.allowedDomains,
autoJoinDomains = request.autoJoinDomains,
)
val memberCount = workspaceRoster.countByWorkspaceId(id).toInt()
return ResponseEntity.ok(updated.toWorkspaceDto(memberCount, loadOwners(id)))
Expand All @@ -97,8 +99,8 @@ class WorkspaceController(
@Valid @RequestBody request: BatchDeleteWorkspaceRequest,
principal: Principal,
): ResponseEntity<Void> {
ensureOwnerManagedEnabled()
workspaceDirectory.deleteByAdminBatch(request.workspaceIds, UUID.fromString(principal.name))
ensurePlatformManagedEnabled()
workspaceDirectory.deletePlatformManagedBatch(request.workspaceIds, UUID.fromString(principal.name))
return ResponseEntity.noContent().build()
}

Expand All @@ -111,7 +113,7 @@ class WorkspaceController(
@RequestParam(required = false) excludeUserIds: List<UUID>?,
pageable: Pageable,
): ResponseEntity<Page<UserSearchResult>> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
val excludeIds = excludeUserIds?.toSet() ?: emptySet()
val page = workspaceUserLookup.search(keyword = keyword, excludeIds = excludeIds, pageable = pageable)
return ResponseEntity.ok(page.map { UserSearchResult(it.id, it.name, it.email) })
Expand All @@ -124,7 +126,8 @@ class WorkspaceController(
fun listMembers(
@PathVariable id: UUID,
): ResponseEntity<List<WorkspaceMemberDto>> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
requirePlatformManagedWorkspace(id)
val members = workspaceRoster.listMembers(id)
val userIds = members.map { it.userId }.distinct()
val users = workspaceUserLookup.findAllByIds(userIds).associateBy { it.id }
Expand All @@ -139,9 +142,9 @@ class WorkspaceController(
@Valid @RequestBody request: AddMemberRequest,
principal: Principal,
): ResponseEntity<Void> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
val currentUserId = UUID.fromString(principal.name)
workspaceDirectory.get(id)
requirePlatformManagedWorkspace(id)
workspaceRoster.addMember(id, request.userId, currentUserId)
return ResponseEntity.status(HttpStatus.CREATED).build()
}
Expand All @@ -153,9 +156,9 @@ class WorkspaceController(
@Valid @RequestBody request: BatchAddMemberRequest,
principal: Principal,
): ResponseEntity<Void> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
val currentUserId = UUID.fromString(principal.name)
workspaceDirectory.get(id)
requirePlatformManagedWorkspace(id)
request.userIds.forEach { userId ->
workspaceRoster.addMember(id, userId, currentUserId)
}
Expand All @@ -169,7 +172,8 @@ class WorkspaceController(
@Valid @RequestBody request: BatchRemoveMemberRequest,
principal: Principal,
): ResponseEntity<Void> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
requirePlatformManagedWorkspace(id)
workspaceRoster.removeMembers(id, request.userIds, UUID.fromString(principal.name))
return ResponseEntity.noContent().build()
}
Expand All @@ -180,8 +184,8 @@ class WorkspaceController(
@PathVariable id: UUID,
@Valid @RequestBody request: ReplaceWorkspaceOwnersRequest,
): ResponseEntity<Void> {
ensureOwnerManagedEnabled()
workspaceDirectory.get(id)
ensurePlatformManagedEnabled()
requirePlatformManagedWorkspace(id)
workspaceRoster.replaceOwners(id, request.userIds)
return ResponseEntity.noContent().build()
}
Expand All @@ -193,7 +197,8 @@ class WorkspaceController(
fun listInvites(
@PathVariable id: UUID,
): ResponseEntity<List<WorkspaceInviteDto>> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
requirePlatformManagedWorkspace(id)
val invites = workspaceInvitations.listByWorkspace(id)
return ResponseEntity.ok(invites.map { it.toWorkspaceInviteDto() })
}
Expand All @@ -205,7 +210,8 @@ class WorkspaceController(
@Valid @RequestBody request: CreateWorkspaceInviteRequest,
principal: Principal,
): ResponseEntity<WorkspaceInviteDto> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
requirePlatformManagedWorkspace(id)
val userId = UUID.fromString(principal.name)
val invite = workspaceInvitations.invite(id, request.email, request.message, userId)
return ResponseEntity.status(HttpStatus.CREATED).body(invite.toWorkspaceInviteDto())
Expand All @@ -218,7 +224,8 @@ class WorkspaceController(
@Valid @RequestBody request: BatchInviteRequest,
principal: Principal,
): ResponseEntity<Void> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
requirePlatformManagedWorkspace(id)
val userId = UUID.fromString(principal.name)
request.emails.forEach { email ->
workspaceInvitations.invite(id, email, request.message, userId)
Expand All @@ -232,7 +239,8 @@ class WorkspaceController(
@PathVariable id: UUID,
@Valid @RequestBody request: BatchWorkspaceInviteActionRequest,
): ResponseEntity<Void> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
requirePlatformManagedWorkspace(id)
workspaceInvitations.cancelBatch(id, request.inviteIds)
return ResponseEntity.noContent().build()
}
Expand All @@ -244,7 +252,8 @@ class WorkspaceController(
@Valid @RequestBody request: BatchWorkspaceInviteActionRequest,
principal: Principal,
): ResponseEntity<Void> {
ensureOwnerManagedEnabled()
ensurePlatformManagedEnabled()
requirePlatformManagedWorkspace(id)
workspaceInvitations.resendBatch(id, UUID.fromString(principal.name), request.inviteIds)
return ResponseEntity.noContent().build()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package io.deck.app.controller

import io.deck.iam.ManagementType
import io.deck.iam.api.ExternalReferenceRecord
import io.deck.iam.api.InviteStatus
import io.deck.iam.api.WorkspaceInviteRecord
import io.deck.iam.api.WorkspaceManagedType
import io.deck.iam.api.WorkspaceMemberRecord
import io.deck.iam.api.WorkspaceRecord
import io.deck.iam.api.WorkspaceUserRecord
import io.deck.iam.domain.ExternalSource
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.util.UUID

data class WorkspaceOwnerDto(
Expand All @@ -19,26 +23,30 @@ data class WorkspaceDto(
val id: UUID,
val name: String,
val description: String?,
val allowedDomains: List<String>,
val autoJoinDomains: List<String>,
val owners: List<WorkspaceOwnerDto>,
val memberCount: Int,
val managedType: WorkspaceManagedType,
val managedType: ManagementType,
val externalReference: ExternalReferenceDto?,
val createdAt: Instant?,
val updatedAt: Instant?,
)

data class ExternalReferenceDto(
val source: ExternalSource,
val externalId: String,
)

data class CreateWorkspaceRequest(
val name: String,
val description: String? = null,
val allowedDomains: List<String> = emptyList(),
val managedType: WorkspaceManagedType? = null,
val autoJoinDomains: List<String> = emptyList(),
)

data class UpdateWorkspaceRequest(
val name: String,
val description: String? = null,
val allowedDomains: List<String> = emptyList(),
val managedType: WorkspaceManagedType? = null,
val autoJoinDomains: List<String> = emptyList(),
)

data class WorkspaceMemberDto(
Expand Down Expand Up @@ -72,10 +80,11 @@ data class MyWorkspaceDto(
val id: UUID,
val name: String,
val description: String?,
val allowedDomains: List<String>,
val autoJoinDomains: List<String>,
val owners: List<WorkspaceOwnerDto>,
val memberCount: Int,
val managedType: WorkspaceManagedType,
val managedType: ManagementType,
val externalReference: ExternalReferenceDto?,
val role: String,
val createdAt: Instant?,
val updatedAt: Instant?,
Expand Down Expand Up @@ -120,12 +129,13 @@ internal fun WorkspaceRecord.toWorkspaceDto(
id = id,
name = name,
description = description,
allowedDomains = allowedDomains,
autoJoinDomains = autoJoinDomains,
owners = owners,
memberCount = memberCount,
managedType = managedType,
createdAt = createdAt,
updatedAt = updatedAt,
externalReference = externalReference?.toDto(),
createdAt = createdAt?.toUtcInstant(),
updatedAt = updatedAt?.toUtcInstant(),
)

internal fun WorkspaceRecord.toMyWorkspaceDto(
Expand All @@ -137,13 +147,14 @@ internal fun WorkspaceRecord.toMyWorkspaceDto(
id = id,
name = name,
description = description,
allowedDomains = allowedDomains,
autoJoinDomains = autoJoinDomains,
owners = owners,
memberCount = memberCount,
managedType = managedType,
externalReference = externalReference?.toDto(),
role = if (isOwnerMember) "OWNER" else "MEMBER",
createdAt = createdAt,
updatedAt = updatedAt,
createdAt = createdAt?.toUtcInstant(),
updatedAt = updatedAt?.toUtcInstant(),
)

internal fun WorkspaceMemberRecord.toWorkspaceMemberDto(users: Map<UUID, WorkspaceUserRecord>): WorkspaceMemberDto =
Expand All @@ -165,3 +176,7 @@ internal fun WorkspaceInviteRecord.toWorkspaceInviteDto(): WorkspaceInviteDto =
message = message,
createdAt = createdAt,
)

private fun LocalDateTime.toUtcInstant(): Instant = toInstant(ZoneOffset.UTC)

private fun ExternalReferenceRecord.toDto(): ExternalReferenceDto = ExternalReferenceDto(source = source, externalId = externalId)
Loading
Loading