Skip to content
bitzorcas
中EN

Guide

Documents Knowledge Bases, Categories, Members, and Access

Knowledge-base and category trees, embedded members, document ACL data, permission catalogs, tenant filtering, referential integrity, and a secure target design.

Last updated

Knowledge bases, categories, members, and access rules describe how content is organized and who should theoretically reach it. The current model persists that information, but member and ACL data are not part of request admission. This chapter separates the model from effective security.

1. Three independent hierarchies

Root knowledge base

Child knowledge base

Root category

Child category

Parent document

Child document

KnowledgeBase.ParentId organizes spaces, Category.ParentId organizes navigation within a knowledge base, and Document.ParentId organizes documents. The database and handlers do not universally validate these identifiers.

2. Creating a knowledge base

Create a private knowledge-base record
POST /api/v1/knowledgebases HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9
Content-Type: application/json
{
"name": "Support operations",
"description": "Maintained by support and operations",
"type": "KnowledgeBase",
"icon": "headset",
"color": "#2864DC",
"parentId": null,
"sortOrder": 10,
"isPublic": false,
"defaultAccessLevel": "View"
}

The caller must be a User; TenantId and CreateBy come from the current identity. DefaultAccessLevel has a null! C# default, so clients should always send a registered value.

Creation does not check same-tenant name uniqueness, parent existence/tenant, cycles, or the validity of IsPublic plus DefaultAccessLevel. The tree builder recursively follows ParentId without a visited set. A detached cycle can disappear from the root result; a reachable cycle can recurse indefinitely.

3. Members are persisted aggregate data

Members live in MembersJson. Add/Remove maintains MemberCount and rejects duplicates/missing members.

Add a knowledge-base member
// The member and level are appended to the knowledge-base MembersJson collection.
using var response = await api.PostAsJsonAsync(
"/api/v1/knowledgebases/kb-support/members",
new { userId = "user-2048", accessLevel = "Edit" },
cancellationToken);
// Success proves persistence only; reads and writes do not consult this member yet.
response.EnsureSuccessStatusCode();

SetKnowledgeBaseAccessCommand can change IsPublic and DefaultAccessLevel but has no generated endpoint. Detail, list, tree, and document queries do not filter by members, public state, or default access.

4. Category uniqueness and references

Categories have a unique index on (TenantId, KnowledgeBaseId, ParentId, Name). The handler first queries for duplicates, and the database index closes concurrent races.

RepositoryCreateCategory handlerClientRepositoryCreateCategory handlerClientalt[parent supplied]unique index is the final race guardKB + parent + namefind parent in tenantparentverify same KBcheck sibling namesave

The handler does not verify that KnowledgeBaseId exists. Delete trusts DocumentCount > 0 and does not check child categories. Since document handlers do not maintain category count, the guard may be stale.

5. Document access rules

An access rule contains PrincipalId, PrincipalType, and AccessLevel. A principal/type pair is unique. Document.HasAccess grants its User creator all levels and otherwise compares the stored level numerically.

Aggregate access semantics
// Current HTTP handlers do not call HasAccess; this illustrates the aggregate method only.
var canEdit = document.HasAccess(
currentUserId,
PrincipalType.User,
AccessLevel.Edit);
// Once integrated, denial should flow through the framework Problem Details mapping.
if (!canEdit)
return Result.Failure(DocsErrors.Document.AccessDenied);

No production caller of HasAccess was found. Adding a rule changes the DTO and database but not the reachability of detail, list, versions, diff, update, publish, or delete.

6. Effective four-layer model

Authentication

Tenant predicate

IAuthorizedRequest

Members / ACL / state

PathAuthenticatedTenantGeneric permissionMember/ACL
Document writesYesUser.TenantIdYesNo
Document GET/version/diffYesUser.TenantIdNoNo
Knowledge-base writesYesUser.TenantIdYesNo
Knowledge-base readsYesUser.TenantIdNoNo
Category writesYesUser.TenantIdMessage yes, catalog missingNo
Category treeYesUser.TenantIdNoNo
Whiteboard equivalentsYesUser.TenantIdWrites yes/reads no; catalog missingNo

The catalog contains document view/create/update/delete/publish/manage and knowledge-base view/create/manage/delete. Category and whiteboard resource constants exist without permission definitions. Publish, tag, and ACL commands use Update, leaving publish/manage constants disconnected from those actions.

7. Target resource policy

Resource-policy contract
public interface IDocumentAccessPolicy
{
// Detail, list, version, search, and hub paths share this operation model.
Task<Result> AuthorizeAsync(
DocumentResourceSnapshot resource,
DocumentOperation operation,
CurrentUser caller,
CancellationToken cancellationToken);
}
// Carry only immutable decision facts instead of exposing a mutable aggregate.
public sealed record DocumentResourceSnapshot(
string TenantId,
string DocumentId,
string KnowledgeBaseId,
string? CreatorId,
bool KnowledgeBaseIsPublic,
AccessLevel KnowledgeBaseDefault,
IReadOnlyList<KnowledgeBaseMember> Members,
IReadOnlyList<DocumentAccessRule> Rules);

A production policy should fail tenant mismatch first, define audited administrative bypass, use a deterministic merge order, push visibility into list/search storage predicates, and serve detail/version/diff/download/search/hub consistently. Member or ACL changes must invalidate caches and active sessions.

8. Referential-integrity orchestration

Validate organization before creating a document
public async Task<Result<Document>> CreateDocumentAsync(
CreateDocument request,
CurrentUser caller,
CancellationToken cancellationToken)
{
// Resolve every parent through the trusted tenant boundary.
var kb = await knowledgeBases.FindAsync(
caller.TenantId, request.KnowledgeBaseId, cancellationToken);
if (kb is null)
return Result.Failure(DocsErrors.KnowledgeBase.NotFound);
if (request.CategoryId is not null)
{
var category = await categories.FindAsync(
caller.TenantId, request.CategoryId, cancellationToken);
if (category is null || category.KnowledgeBaseId != kb.Id)
return Result.Failure(DocsErrors.Category.ParentKnowledgeBaseMismatch);
}
// Apply resource policy before constructing or saving the new aggregate.
var allowed = await access.AuthorizeCreateAsync(kb, caller, cancellationToken);
if (allowed.IsFailure) return Result.Failure<Document>(allowed.Error);
return Document.Create(/* validated ids and server-derived tenant */);
}

This is a target orchestration example for the current gap, not a copy of the existing handler.

9. Delete behavior

ResourceCurrent guardRisk
Knowledge baseRepository query for documentsNo child KB/category/whiteboard checks; soft delete
CategoryStored DocumentCountCount is not maintained; no child-category check
DocumentImmediate soft deleteKB/category counts unchanged; no observed integration-event publish
WhiteboardImmediate soft deleteMembers and KB status ignored

Use authoritative reference queries before delete. Denormalized counts may accelerate displays, but they are not integrity unless maintained and reconciled.

10. Required tests

  • unauthorized principals for every GET, version, diff, search, and hub path;
  • same-tenant outsider, cross-tenant member, creator, user/role principal types;
  • cache and live-connection behavior after ACL/member revocation;
  • cross-KB category parents, concurrent duplicate names, orphans, and cycles;
  • delete with documents, child categories, child knowledge bases, or whiteboards;
  • impersonation where User.TenantId and EffectiveTenantId differ;
  • permission-catalog parity for every IAuthorizedRequest.

Back to Documents

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%