Skip to content

Cesivi - Permissions Guide

Overview

Cesivi implements a comprehensive permission system that mirrors real SharePoint behavior, including:

  • Active Directory (AD) Integration: Support for AD users, AD groups, and nested group membership
  • SharePoint Users and Groups: Native SharePoint principals
  • Role-Based Permissions: Role definitions and role assignments
  • Permission Inheritance: Objects can inherit permissions from parents or have unique permissions
  • Effective Permissions Calculation: Resolves combined permissions from all applicable roles and group memberships

Architecture

The permission system consists of several key components:

1. AD Storage Service (IADStorageService)

Manages Active Directory identities (users and groups) loaded from a JSON file.

Key Features: - Load AD users and groups from MockData/ActiveDirectory/identities.json - Resolve users by sAMAccountName, Distinguished Name (DN), or User Principal Name (UPN) - Resolve groups by sAMAccountName or DN - Calculate nested group membership (transitive closure)

File Location: Cesivi.Server/Services/ADMockStorageService.cs

2. ACL Service (IACLService)

Evaluates Access Control Lists and calculates effective permissions.

Key Features: - Resolve identities (AD or SharePoint) - Get all groups a user belongs to (AD + SharePoint) - Calculate effective permissions for a user on an object - Check specific permissions - Get role assignments for objects (web, list, item, file)

File Location: Cesivi.Server/Services/ACLService.cs

3. Identity Models

Active Directory Models: - ADUser: Represents an AD user with attributes like sAMAccountName, UPN, Email, Department, etc. - ADGroup: Represents an AD group with members and nested group relationships - ADIdentity: Base class for AD users and groups

File Location: Cesivi.Server/Models/ActiveDirectory/ADIdentity.cs

SharePoint Models: - SPUser: SharePoint user principal - SPGroup: SharePoint group - SPRoleAssignment: Role assignment linking a principal to role definitions - SPRoleDefinition: Defines a set of permissions (Full Control, Contribute, Read, etc.)

Configuration

1. AD Identities File

Create or modify MockData/ActiveDirectory/identities.json to define AD users and groups.

Example Structure:

{
  "Users": [
    {
      "DistinguishedName": "CN=John Doe,OU=Users,DC=contoso,DC=com",
      "SamAccountName": "jdoe",
      "UserPrincipalName": "jdoe@contoso.com",
      "DisplayName": "John Doe",
      "GivenName": "John",
      "Surname": "Doe",
      "Email": "jdoe@contoso.com",
      "Department": "IT",
      "Title": "System Administrator",
      "ObjectGuid": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
      "ObjectSid": "S-1-5-21-1234567890-1234567890-1234567890-1001",
      "MemberOf": [
        "CN=IT Admins,OU=Groups,DC=contoso,DC=com",
        "CN=Domain Users,OU=Groups,DC=contoso,DC=com"
      ]
    }
  ],
  "Groups": [
    {
      "DistinguishedName": "CN=IT Admins,OU=Groups,DC=contoso,DC=com",
      "SamAccountName": "IT Admins",
      "DisplayName": "IT Administrators",
      "Description": "IT department administrators",
      "ObjectGuid": "20000000-0000-0000-0000-000000000002",
      "ObjectSid": "S-1-5-21-1234567890-1234567890-1234567890-2001",
      "GroupType": 0,
      "GroupScope": 0,
      "Members": [
        "CN=John Doe,OU=Users,DC=contoso,DC=com"
      ],
      "MemberOf": [
        "CN=All Admins,OU=Groups,DC=contoso,DC=com"
      ]
    },
    {
      "DistinguishedName": "CN=All Admins,OU=Groups,DC=contoso,DC=com",
      "SamAccountName": "All Admins",
      "DisplayName": "All Administrators",
      "Description": "All administrative groups (nested)",
      "ObjectGuid": "40000000-0000-0000-0000-000000000004",
      "ObjectSid": "S-1-5-21-1234567890-1234567890-1234567890-2003",
      "Members": [
        "CN=IT Admins,OU=Groups,DC=contoso,DC=com"
      ]
    }
  ]
}

Key Properties:

For Users: - DistinguishedName: Unique LDAP path (required) - SamAccountName: Pre-Windows 2000 login name (required) - UserPrincipalName: Email-style login (required) - DisplayName: User's display name - Email: Email address - Department: Department name - Title: Job title - MemberOf: Array of group DNs this user is a member of

For Groups: - DistinguishedName: Unique LDAP path (required) - SamAccountName: Group name (required) - DisplayName: Group display name - GroupType: 0 = Security, 1 = Distribution - GroupScope: 0 = Global, 1 = DomainLocal, 2 = Universal - Members: Array of user/group DNs that are members - MemberOf: Array of group DNs this group is a member of (for nested groups)

2. Service Registration

Services are automatically registered in Program.cs:

// Goal 8: ACL and AD Integration services
builder.Services.AddSingleton<IADStorageService, ADMockStorageService>();
builder.Services.AddSingleton<IACLService, ACLService>();

Usage

1. Identity Resolution

Resolve a login name to an identity (AD or SharePoint):

var aclService = serviceProvider.GetRequiredService<IACLService>();

// Resolve by sAMAccountName
var identity = await aclService.ResolveIdentityAsync("jdoe");

// Resolve by UPN
var identity = await aclService.ResolveIdentityAsync("jdoe@contoso.com");

// Resolve by DN
var identity = await aclService.ResolveIdentityAsync("CN=John Doe,OU=Users,DC=contoso,DC=com");

// Check identity type
if (identity.Type == IdentityType.ADUser)
{
    Console.WriteLine($"AD User: {identity.DisplayName}");
    Console.WriteLine($"SharePoint User ID: {identity.SharePointUserId}");
}
else if (identity.Type == IdentityType.ADGroup)
{
    Console.WriteLine($"AD Group: {identity.DisplayName}");
    Console.WriteLine($"SharePoint Group ID: {identity.SharePointGroupId}");
}

Identity Types: - IdentityType.ADUser: Active Directory user - IdentityType.ADGroup: Active Directory group - IdentityType.SharePointUser: SharePoint user - IdentityType.SharePointGroup: SharePoint group - IdentityType.Unknown: Could not resolve

2. Get User Groups

Get all groups a user is a member of (including nested groups):

var allGroups = await aclService.GetUserAllGroupsAsync("jdoe");

// Returns a list of group identifiers:
// - AD group sAMAccountNames
// - AD group Distinguished Names
// - SharePoint group Titles
// - SharePoint group identifiers (SPGroup:{ID})

3. Check Permission

Check if a user has a specific permission on an object:

// Check if user has ViewListItems permission on a list
bool hasPermission = await aclService.CheckPermissionAsync(
    userLogin: "jdoe",
    objectPath: "list:/sites/site/Lists/Documents",
    permission: SPBasePermissions.ViewListItems
);

if (hasPermission)
{
    Console.WriteLine("User can view list items");
}

Object Path Formats: - Web: web:/sites/site - List: list:/sites/site/Lists/ListName - Item: item:/sites/site/Lists/ListName/ItemID - File: file:/sites/site/Shared Documents/file.docx

Common Permissions (SPBasePermissions): - ViewListItems (0x01): View items in lists and documents in libraries - AddListItems (0x02): Add items to lists and documents to libraries - EditListItems (0x04): Edit items in lists and documents in libraries - DeleteListItems (0x08): Delete items from lists and documents from libraries - OpenItems (0x10): View the source of items with file handlers - ViewPages (0x20000): View pages in a site - Open (0x10000): Allow users to open a site - ManageLists (0x800): Create and delete lists, add or remove columns - FullMask (0xFFFFFFFFFFFFFFFF): Full control

4. Get Effective Permissions

Get the combined permission mask for a user on an object:

ulong effectivePermissions = await aclService.GetEffectivePermissionsAsync(
    userLogin: "jdoe",
    objectPath: "list:/sites/site/Lists/Documents"
);

// Check specific permissions
bool canView = (effectivePermissions & SPBasePermissions.ViewListItems) == SPBasePermissions.ViewListItems;
bool canEdit = (effectivePermissions & SPBasePermissions.EditListItems) == SPBasePermissions.EditListItems;
bool canDelete = (effectivePermissions & SPBasePermissions.DeleteListItems) == SPBasePermissions.DeleteListItems;

Console.WriteLine($"Can View: {canView}");
Console.WriteLine($"Can Edit: {canEdit}");
Console.WriteLine($"Can Delete: {canDelete}");

5. Get Role Assignments

Get all role assignments for an object:

var roleAssignments = await aclService.GetRoleAssignmentsAsync("list:/sites/site/Lists/Documents");

foreach (var assignment in roleAssignments)
{
    Console.WriteLine($"Principal ID: {assignment.PrincipalId}");
    Console.WriteLine($"Role Definition IDs: {string.Join(", ", assignment.RoleDefinitionIds)}");
}

Permission Inheritance

Cesivi supports permission inheritance, where objects can inherit permissions from their parent or have unique permissions.

Inheritance Hierarchy

  1. Web Application (root)
  2. Site Collection
    • Web (Site)
    • List/Library
      • List Item
      • File

Checking Inheritance

Objects have a HasUniqueRoleAssignments property: - true: Object has unique permissions (does not inherit) - false: Object inherits permissions from parent

Setting Unique Permissions

To set unique permissions on a Web:

var web = await storageService.LoadWebAsync("Default", "RootSite");
if (web != null)
{
    web.HasUniqueRoleAssignments = true;
    await storageService.SaveWebAsync("Default", "RootSite", web);
}

To set unique permissions on a List:

var list = await storageService.LoadListAsync("Default", "RootSite", "Documents");
if (list != null)
{
    list.HasUniqueRoleAssignments = true;
    await storageService.SaveListAsync("Default", "RootSite", list);
}

To set unique permissions on a List Item:

var item = await storageService.LoadListItemAsync("Default", "RootSite", "Documents", 1);
if (item != null)
{
    item.Settings["HasUniqueRoleAssignments"] = true;
    await storageService.SaveListItemAsync("Default", "RootSite", "Documents", item);
}

Nested Group Membership

The ACL service supports nested group membership, where groups can contain other groups.

Example

Given this structure: - User: John Doe - Member of: IT Admins - Group: IT Admins - Member of: All Admins - Group: All Admins

When checking John Doe's groups:

var allGroups = await aclService.GetUserAllGroupsAsync("jdoe");
// Returns: ["IT Admins", "CN=IT Admins,OU=Groups,DC=contoso,DC=com", "All Admins", "CN=All Admins,OU=Groups,DC=contoso,DC=com"]

The algorithm uses breadth-first search (BFS) to traverse the group hierarchy and find all transitive memberships.

AD and SharePoint Integration

Dual Identity Resolution

Users can exist in both AD and SharePoint: 1. First, ACL service checks AD storage for the user 2. If found in AD, it also checks if the user exists in SharePoint storage 3. The resolved identity includes both AD DN and SharePoint User ID 4. This allows permissions to be assigned to either the AD identity or the SharePoint identity

Example

var identity = await aclService.ResolveIdentityAsync("jdoe");

// identity.Type = IdentityType.ADUser
// identity.ADDistinguishedName = "CN=John Doe,OU=Users,DC=contoso,DC=com"
// identity.SharePointUserId = 1 (if user exists in SharePoint)

Group Membership Resolution

When calculating effective permissions, the ACL service considers: 1. Direct user assignments 2. SharePoint group memberships 3. AD group memberships 4. Nested AD group memberships 5. AD groups that are members of SharePoint groups

Best Practices

1. Use Nested Groups

Organize permissions using nested groups for easier management:

All Admins (has permissions)
  ├─ IT Admins
  │   └─ John Doe
  └─ SharePoint Admins
      └─ Jane Smith

2. Assign Permissions to Groups

Prefer assigning permissions to groups rather than individual users: - Easier to manage - More scalable - Follows real SharePoint best practices

3. Use Inheritance Where Possible

Only break inheritance when necessary: - Reduces complexity - Easier to troubleshoot - Better performance

4. Document Permission Changes

When breaking inheritance or setting unique permissions, document why in the Settings dictionary:

item.Settings["HasUniqueRoleAssignments"] = true;
item.Settings["PermissionNote"] = "Unique permissions for confidential document";

5. Test Permission Scenarios

Use the ACL service to verify permissions before deployment:

// Test that regular users cannot delete
var canDelete = await aclService.CheckPermissionAsync("regularuser", "list:/sites/site/Lists/Documents", SPBasePermissions.DeleteListItems);
Assert.False(canDelete);

// Test that admins can delete
canDelete = await aclService.CheckPermissionAsync("admin", "list:/sites/site/Lists/Documents", SPBasePermissions.DeleteListItems);
Assert.True(canDelete);

Troubleshooting

User Cannot Access Content

  1. Check identity resolution:

    var identity = await aclService.ResolveIdentityAsync("username");
    Console.WriteLine($"Type: {identity.Type}");
    Console.WriteLine($"Display Name: {identity.DisplayName}");
    

  2. Check group membership:

    var groups = await aclService.GetUserAllGroupsAsync("username");
    Console.WriteLine($"Member of {groups.Count} groups:");
    foreach (var group in groups)
    {
        Console.WriteLine($"  - {group}");
    }
    

  3. Check effective permissions:

    var permissions = await aclService.GetEffectivePermissionsAsync("username", "list:/sites/site/Lists/Documents");
    Console.WriteLine($"Effective Permissions: 0x{permissions:X}");
    Console.WriteLine($"Can View: {(permissions & SPBasePermissions.ViewListItems) != 0}");
    

  4. Check role assignments:

    var assignments = await aclService.GetRoleAssignmentsAsync("list:/sites/site/Lists/Documents");
    Console.WriteLine($"Found {assignments.Count} role assignments");
    

Permission Changes Not Taking Effect

  1. Verify HasUniqueRoleAssignments is set:

    var list = await storageService.LoadListAsync("Default", "RootSite", "Documents");
    Console.WriteLine($"Has Unique Permissions: {list.HasUniqueRoleAssignments}");
    

  2. Check parent permissions: If object inherits permissions, check the parent's role assignments.

  3. Reload data: If using InMemory storage, restart the server to reload from disk.

Nested Groups Not Working

  1. Verify MemberOf arrays: Ensure groups have correct MemberOf arrays in the AD identities file.

  2. Check for circular references: Circular group membership (Group A → Group B → Group A) is not supported.

  3. Test group resolution:

    var adStorage = serviceProvider.GetRequiredService<IADStorageService>();
    var allGroups = await adStorage.GetUserAllGroupsAsync("CN=John Doe,OU=Users,DC=contoso,DC=com");
    Console.WriteLine($"Found {allGroups.Count} groups");
    

API Reference

IACLService

public interface IACLService
{
    // Check if user has specific permission
    Task<bool> CheckPermissionAsync(string userLogin, string objectPath, ulong permission);

    // Get combined permission mask
    Task<ulong> GetEffectivePermissionsAsync(string userLogin, string objectPath);

    // Get all groups (AD + SharePoint)
    Task<List<string>> GetUserAllGroupsAsync(string userLogin);

    // Resolve identity
    Task<ResolvedIdentity?> ResolveIdentityAsync(string login);

    // Get role assignments for object
    Task<List<SPRoleAssignment>> GetRoleAssignmentsAsync(string objectPath);
}

IADStorageService

public interface IADStorageService
{
    // Load all users/groups
    Task<List<ADUser>> GetAllUsersAsync();
    Task<List<ADGroup>> GetAllGroupsAsync();

    // Find users
    Task<ADUser?> GetUserBySamAccountNameAsync(string samAccountName);
    Task<ADUser?> GetUserByDNAsync(string distinguishedName);
    Task<ADUser?> GetUserByUPNAsync(string userPrincipalName);

    // Find groups
    Task<ADGroup?> GetGroupBySamAccountNameAsync(string samAccountName);
    Task<ADGroup?> GetGroupByDNAsync(string distinguishedName);

    // Group membership
    Task<List<ADGroup>> GetUserDirectGroupsAsync(string userDN);
    Task<List<ADGroup>> GetUserAllGroupsAsync(string userDN);
    Task<List<ADIdentity>> GetGroupDirectMembersAsync(string groupDN);
    Task<List<ADIdentity>> GetGroupAllMembersAsync(string groupDN);
    Task<bool> IsUserMemberOfGroupAsync(string userDN, string groupDN);

    // Reload data
    Task ReloadAsync();
}

See Also