#873: BaseUserGroupRightUtils.kt

projectforge-business/src/main/kotlin/org/projectforge/business/common/BaseUserGroupRightUtils.kt Type: Kotlin object (static utility) · Purpose: Core access rights logic · Source: projectforge-business/src/main/kotlin/org/projectforge/business/common/BaseUserGroupRightUtils.kt 136 lines · 93 code · 33 comments · 10 blank
Standalone utility object containing the core access rights resolution logic. Determines whether a user has OWNER, FULL, READONLY, MINIMAL, or NONE access to a BaseUserGroupRightsDO object by checking the CSV ID fields against group membership and direct user ID matching. Used by both BaseUserGroupRight (#864) and TeamCalCache.

API

isOwner(user, obj) / isOwner(userId, obj)

Returns true if userId == obj.ownerId (both non-null). Simple identity check.

getAccessType(obj, userId): DataobjectAccessType

The core resolution method. Evaluates access in priority order:

  1. Null guard: Returns NONE if obj or userId is null
  2. Owner check: Returns OWNER if userId == obj.ownerId
  3. Full access: Checks fullAccessGroupIds and fullAccessUserIds
  4. Read-only access: Checks readonlyAccessGroupIds and readonlyAccessUserIds
  5. Minimal access: Checks minimalAccessGroupIds and minimalAccessUserIds
  6. Fallback: Returns NONE

hasReadAccess(obj, userId, throwException?): Boolean

Returns true if access type is READONLY, FULL, or OWNER. Otherwise returns false, or throws AccessException("access.exception.noReadAccess") if throwException is true.

hasWriteAccess(obj, userId, throwException?): Boolean

Returns true if access type is FULL or OWNER. Otherwise returns false, or throws AccessException("access.exception.noWriteAccess").

hasAccess(obj, userId, operationType, throwException?)

Routes to hasReadAccess or hasWriteAccess based on operationType.isWriteType.

hasAccess(obj, oldObj, userId, operationType, throwException?)

For write operations, checks the old object first (allowing modification if user already had write access). Falls back to checking the new object. For read operations, checks oldObj ?: obj.

Internal Logic

isMemberOfAny(groupIds, userIds, userId): Boolean

Private helper that performs the actual group/user membership check:

Data Flow

BaseUserGroupRightsDO (CSV columns)
  │
  ├─ StringHelper.splitToLongObjects()
  │
  ├─ UserGroupCache.isUserMemberOfAtLeastOneGroup()  ← groups
  └─ Direct userId comparison                          ← users
  │
  ▼
DataobjectAccessType (OWNER / FULL / READONLY / MINIMAL / NONE)
  │
  ▼
hasReadAccess / hasWriteAccess → Boolean

Design Rationale

The object (Kotlin singleton) pattern was chosen because the access logic is purely functional—no state or dependencies, just computation. This allows it to be called from anywhere (services, caches, entity constructors) without injection overhead.

The tiered evaluation (owner → full → readonly → minimal → none) means the first match wins. If a user is both in the full-access group and the readonly group, they get FULL access. The oldObj parameter in hasAccess supports the pattern where permission to modify an object is checked against the pre-modification state—critical for update operations where the object's access lists may change as part of the edit.

External Dependencies

Git History

868d6abb7 2025 -> 2026
63081666f Source file headers: 2024-> 2025.
4c04cfd65 MAJOR-CHANGE! Migration of integer id's to Long id's
b6092df09 Copyright 2023 -> 2024
ab45d51fa Copyright 2001-2022 -> 2001-2023.
232a91c5a BaseUserGroupRightUtils implemented. WIP BankingPlugin and jobs-handling.

#873: BaseUserGroupRightUtils.kt

Type: Kotlin object (static utility) · Purpose: Core access rights logic · Source: projectforge-business/src/main/kotlin/org/projectforge/business/common/BaseUserGroupRightUtils.kt
Standalone utility object containing the core access rights resolution logic. Determines whether a user has OWNER, FULL, READONLY, MINIMAL, or NONE access to a BaseUserGroupRightsDO object by checking the CSV ID fields against group membership and direct user ID matching. Used by both BaseUserGroupRight (#864) and TeamCalCache.

API

isOwner(user, obj) / isOwner(userId, obj)

Returns true if userId == obj.ownerId (both non-null). Simple identity check.

getAccessType(obj, userId): DataobjectAccessType

The core resolution method. Evaluates access in priority order:

  1. Null guard: Returns NONE if obj or userId is null
  2. Owner check: Returns OWNER if userId == obj.ownerId
  3. Full access: Checks fullAccessGroupIds and fullAccessUserIds
  4. Read-only access: Checks readonlyAccessGroupIds and readonlyAccessUserIds
  5. Minimal access: Checks minimalAccessGroupIds and minimalAccessUserIds
  6. Fallback: Returns NONE

hasReadAccess(obj, userId, throwException?): Boolean

Returns true if access type is READONLY, FULL, or OWNER. Otherwise returns false, or throws AccessException("access.exception.noReadAccess") if throwException is true.

hasWriteAccess(obj, userId, throwException?): Boolean

Returns true if access type is FULL or OWNER. Otherwise returns false, or throws AccessException("access.exception.noWriteAccess").

hasAccess(obj, userId, operationType, throwException?)

Routes to hasReadAccess or hasWriteAccess based on operationType.isWriteType.

hasAccess(obj, oldObj, userId, operationType, throwException?)

For write operations, checks the old object first (allowing modification if user already had write access). Falls back to checking the new object. For read operations, checks oldObj ?: obj.

Internal Logic

isMemberOfAny(groupIds, userIds, userId): Boolean

Private helper that performs the actual group/user membership check:

Data Flow

BaseUserGroupRightsDO (CSV columns)
  │
  ├─ StringHelper.splitToLongObjects()
  │
  ├─ UserGroupCache.isUserMemberOfAtLeastOneGroup()  ← groups
  └─ Direct userId comparison                          ← users
  │
  ▼
DataobjectAccessType (OWNER / FULL / READONLY / MINIMAL / NONE)
  │
  ▼
hasReadAccess / hasWriteAccess → Boolean

Design Rationale

The object (Kotlin singleton) pattern was chosen because the access logic is purely functional—no state or dependencies, just computation. This allows it to be called from anywhere (services, caches, entity constructors) without injection overhead.

The tiered evaluation (owner → full → readonly → minimal → none) means the first match wins. If a user is both in the full-access group and the readonly group, they get FULL access. The oldObj parameter in hasAccess supports the pattern where permission to modify an object is checked against the pre-modification state—critical for update operations where the object's access lists may change as part of the edit.

External Dependencies

Git History

868d6abb7 2025 -> 2026
63081666f Source file headers: 2024-> 2025.
4c04cfd65 MAJOR-CHANGE! Migration of integer id's to Long id's
b6092df09 Copyright 2023 -> 2024
ab45d51fa Copyright 2001-2022 -> 2001-2023.
232a91c5a BaseUserGroupRightUtils implemented. WIP BankingPlugin and jobs-handling.