Dynamics 365 Security Model: Roles, Permissions, and Access Control

Dynamics 365 Security Model: Roles, Permissions, and Access Control

Introduction

Dynamics 365 security balances data protection with collaboration through a layered model: business units partition data ownership, security roles grant record-level permissions, field-level security restricts sensitive data, and hierarchy security enables manager access. This guide covers role configuration, privilege depths, team-based access, sharing, and audit trails for compliance.

Security Architecture Overview

Security Layers

User Authentication (Azure AD)
    ↓
Business Unit Assignment → Organizational hierarchy
    ↓
Security Roles → Record-level permissions (Create, Read, Write, Delete, Append, Assign, Share)
    ↓
Privilege Depth → Scope of access (None, User, Business Unit, Parent:Child BU, Organization)
    ↓
Field-Level Security → Column restrictions
    ↓
Hierarchy Security → Manager-subordinate access
    ↓
Record Sharing → Ad-hoc access grants
    ↓
Audit Logging → Track access and changes

Permission Model

Privilege Description Example
Create Add new records Sales Rep creates Leads
Read View records Support Agent views Cases
Write Modify existing records Account Manager updates Accounts
Delete Remove records Admin deletes test data
Append Associate records Link Contact to Account
Append To Be associated Account accepts Contact link
Assign Change owner Manager reassigns Opportunity
Share Grant access to others Share Account with Sales Team

Security Roles

Creating Custom Roles

Sales Representative role:

Business Management Tab:
├── Account: Read (Business Unit), Write (User), Create (User)
├── Contact: Read (Business Unit), Write (User), Create (User)
├── Lead: Read (Business Unit), Write, Delete (User), Create (User)
├── Opportunity: Read (Business Unit), Write, Delete (User), Create (User)
├── Quote: Read (User), Write (User), Create (User)
└── Order: Read (User)

Core Records Tab:
├── Activity: Read (Business Unit), Write (User), Create (User)
├── Email: Read (Business Unit), Write (User), Create (User)
├── Phone Call: Read (Business Unit), Write (User), Create (User)
└── Task: Read (Business Unit), Write (User), Create (User)

Customization Tab:
└── All: None (no customization access)

Business Management - Additional:
├── Price List: Read (Organization)
├── Product: Read (Organization)
└── Currency: Read (Organization)

Privilege depth meanings:

  • None (⊗): No access
  • User (👤): Own records only
  • Business Unit (🏢): Records owned by users in same BU
  • Parent: Child Business Units (🏢⇄): Current BU + child BUs
  • Organization (🌐): All records across organization

Role Composition Pattern

Modular role design:

Base Role: "Employee Base"
├── Read (Organization): Account, Contact, Product
├── Read (Business Unit): Activity, Email
└── Write (User): Phone Call, Task

Sales Role: "Employee Base" + "Sales Team Member"
├── Inherits: Employee Base permissions
├── Adds: Lead (CRUD at User level)
├── Adds: Opportunity (CRUD at Business Unit level)
└── Adds: Quote (Create/Read/Write at User level)

Sales Manager Role: "Sales Team Member" + "Sales Manager Add-On"
├── Inherits: Sales Team Member
├── Elevates: Opportunity Read/Write to Parent:Child BU
├── Adds: Assign privileges for Leads/Opportunities
└── Adds: Share privileges for Accounts

System Administrator vs. Custom Admin

Delegated administration pattern:

System Administrator:
└── Full access to everything (use sparingly)

Custom Admin Role: "Environment Admin"
├── Full Customization privileges
├── User Management (Read/Write Organization)
├── Security Role Management (Read/Write Organization)
├── Business Unit (Read Organization)
├── Team (Read/Write Business Unit)
└── NO access to audit logs or system settings

Data Admin Role: "Data Steward"
├── Read/Write/Delete (Organization) for business tables
├── Import/Export privileges
├── Duplicate Detection
├── Bulk Delete
└── NO customization or user management

Business Units

Organizational Structure

Hierarchy design:

Contoso (Root BU)
├── Sales
│   ├── Sales North America
│   │   ├── Sales US East
│   │   └── Sales US West
│   ├── Sales EMEA
│   └── Sales APAC
├── Service
│   ├── Service Tier 1
│   └── Service Tier 2
└── Marketing
    └── Marketing Campaigns

Data isolation rules:

  • User sees records owned by users in their BU (if Read = Business Unit)
  • Manager with Parent:Child BU sees their BU + all child BUs
  • Records owned by BU, not individual users when team-owned

Creating Business Units

PowerShell automation:

# Connect to Dynamics 365
Install-Module Microsoft.Xrm.Data.PowerShell
$conn = Get-CrmConnection -ConnectionString "AuthType=Office365; Url=https://org.crm.dynamics.com; Username=admin@org.com; Password=****"

# Create Business Unit
$buId = New-CrmRecord -conn $conn -EntityLogicalName businessunit -Fields @{
    "name" = "Sales EMEA"
    "parentbusinessunitid" = @{
        "LogicalName" = "businessunit"
        "Id" = "parent-bu-guid"
    }
}

# Move user to new BU
Set-CrmRecord -conn $conn -EntityLogicalName systemuser -Id "user-guid" -Fields @{
    "businessunitid" = @{
        "LogicalName" = "businessunit"
        "Id" = $buId
    }
}

Field-Level Security

Securing Sensitive Fields

Salary field protection:

Step 1: Enable field security

// Via SDK or Power Platform CLI
var attributeRequest = new RetrieveAttributeRequest
{
    EntityLogicalName = "systemuser",
    LogicalName = "annualincome"
};

var attributeResponse = (RetrieveAttributeResponse)service.Execute(attributeRequest);
var attributeMetadata = (MoneyAttributeMetadata)attributeResponse.AttributeMetadata;

attributeMetadata.IsSecured = true;

var updateRequest = new UpdateAttributeRequest
{
    Attribute = attributeMetadata,
    EntityName = "systemuser"
};
service.Execute(updateRequest);

Step 2: Create field security profile

Navigate to: Settings → Security → Field Security Profiles

Profile: "HR Salary Access"
├── Name: HR Salary Access
├── Members: [HR Manager, HR Director users]
└── Field Permissions:
    └── Annual Income (systemuser)
        ├── Read: ✓
        ├── Update: ✓
        └── Create: ✓

Profile: "Manager Salary View"
├── Members: [Department Managers team]
└── Field Permissions:
    └── Annual Income (systemuser)
        ├── Read: ✓
        ├── Update: ✗
        └── Create: ✗

Behavior:

  • Users without profile membership: Field appears blank, cannot update
  • Users with Read permission: See field value
  • Users with Update permission: Can modify field

Programmatic Field Security

Check field access in plugin:

public void Execute(IServiceProvider serviceProvider)
{
    var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
    var service = ((IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)))
        .CreateOrganizationService(context.UserId);
    
    if (context.MessageName == "Update" && context.InputParameters.Contains("Target"))
    {
        var target = (Entity)context.InputParameters["Target"];
        
        if (target.Contains("annualincome"))
        {
            // Check if user has field security permission
            var query = new QueryExpression("fieldpermission");
            query.Criteria.AddCondition("systemuserid", ConditionOperator.Equal, context.UserId);
            query.Criteria.AddCondition("fieldsecurityprofileid", ConditionOperator.NotNull);
            query.Criteria.AddCondition("canupdate", ConditionOperator.Equal, true);
            query.Criteria.AddCondition("attributelogicalname", ConditionOperator.Equal, "annualincome");
            
            var results = service.RetrieveMultiple(query);
            
            if (results.Entities.Count == 0)
            {
                throw new InvalidPluginExecutionException("You do not have permission to update the Annual Income field.");
            }
        }
    }
}

Hierarchy Security

Manager Access Configuration

Enable position hierarchy:

Settings → System Settings → General Tab
└── Enable Hierarchy Security: ✓

Settings → Hierarchy Security
├── Model: Manager Hierarchy (Position-based)
├── Depth: 3 levels
└── Enabled Entities:
    ├── Account
    ├── Contact
    ├── Opportunity
    ├── Lead
    └── Case

How it works:

CEO Position
├── VP Sales (reports to CEO)
│   ├── Sales Manager NA (reports to VP Sales)
│   │   └── Sales Rep 1 (reports to Sales Manager NA)
│   └── Sales Manager EMEA
└── VP Service
    └── Service Manager

With Hierarchy Security:
- CEO sees: All records (own + 3 levels down)
- VP Sales sees: Own + Sales Manager NA/EMEA + Sales Rep 1 records
- Sales Manager NA sees: Own + Sales Rep 1 records
- Sales Rep 1 sees: Only own records

Position setup:

Navigate to: Settings → Security → Positions

Position: "Sales Manager - North America"
├── Name: Sales Manager - North America
├── Parent Position: VP Sales
└── Users in Position:
    └── John Doe (john@contoso.com)

Position: "Sales Representative"
├── Parent Position: Sales Manager - North America
└── Users in Position:
    └── Jane Smith (jane@contoso.com)

Manager Hierarchy vs. Position Hierarchy

Feature Manager Hierarchy Position Hierarchy
Based on Manager field on User Position entity relationships
Flexibility Single manager only Multiple users per position
Depth control System-wide setting Per-entity configuration
Changes Automatic (user field) Manual (position entity)
Best for Simple hierarchies Matrix organizations

Teams and Ownership

Owner Teams vs. Access Teams

Owner Teams:

Team: "Sales EMEA Team"
├── Type: Owner
├── Business Unit: Sales EMEA
├── Security Roles: Sales Team Member
├── Members:
│   ├── User A (default team)
│   ├── User B (default team)
│   └── User C (additional team)
└── Owned Records:
    ├── Accounts: 150
    ├── Opportunities: 87
    └── Quotes: 45

Behavior:
- Records assigned to team (not individual users)
- All members have role privileges on team-owned records
- Used for shared ownership (support queues, regional accounts)

Access Teams:

Access Team: Auto-created per Account record

Account: "Contoso Ltd"
├── Owner: John Doe
└── Access Team Members:
    ├── Jane (Sales Rep) - via Account Team template
    ├── Bob (Support) - via manual share
    └── Alice (Marketing) - via record sharing

Access Rights (via Access Team Template):
- Jane: Read, Write, Append
- Bob: Read
- Alice: Read

Behavior:
- No explicit assignment, just shared access
- Different permissions per member
- Used for collaboration without changing ownership

Team Templates

Configure access team template:

Settings → Security → Access Team Templates

Template: "Account Team"
├── Entity: Account
├── Members:
│   └── (Added dynamically via Account Team subgrid)
└── Access Rights:
    ├── Read: ✓
    ├── Write: ✓
    ├── Append: ✓
    ├── Assign: ✗
    └── Delete: ✗

Add to Account form:
└── Insert → Subgrid
    ├── Name: Account Team
    ├── Entity: Users
    ├── Team Template: Account Team
    └── View: Associated Users

Record Sharing

Manual Sharing

Share opportunity with another user:

var grantAccessRequest = new GrantAccessRequest
{
    Target = new EntityReference("opportunity", opportunityId),
    PrincipalAccess = new PrincipalAccess
    {
        Principal = new EntityReference("systemuser", userId),
        AccessMask = AccessRights.ReadAccess | AccessRights.WriteAccess
    }
};

service.Execute(grantAccessRequest);

UI: Share button on record:

Navigate to: Opportunity record → Share button

Share Opportunity
├── User/Team: [Select user or team]
├── Read: ✓
├── Write: ✓
├── Delete: ✗
├── Assign: ✗
└── Share: ✗ (cannot re-share)

Programmatic Sharing in Workflows

Power Automate: Share account with sales team:

Trigger: When an Account is created
├── Condition: Annual Revenue > $1M
├── Get Team: "Enterprise Sales Team"
└── Perform unbound action: GrantAccess
    ├── Target: @{triggerOutputs()?['body/accountid']}
    ├── PrincipalId: @{outputs('Get_Team')?['body/teamid']}
    ├── AccessMask: Read, Write, Append

Audit Logging

Enable Auditing

System-wide:

Settings → System Settings → Auditing Tab
├── Start Auditing: ✓
├── Audit user access: ✓
├── Log access: Read, Create, Update, Delete
└── Retention: 90 days (adjust per compliance requirements)

Per-entity:

Settings → Customizations → Entities → Account
└── Data Services
    ├── Auditing: ✓
    └── Track changes: ✓

Settings → Customizations → Entities → Account → Fields
└── annualrevenue
    ├── Auditing: ✓ (track field-level changes)

Viewing Audit History

Via UI:

Account record → Related → Audit History

Displays:
├── Changed Date: 2025-10-13 14:23:00
├── Changed By: John Doe
├── Event: Update
└── Changes:
    ├── Annual Revenue: $500,000 → $750,000
    └── Credit Limit: $50,000 → $75,000

Via SDK/API:

var query = new QueryExpression("audit");
query.Criteria.AddCondition("objectid", ConditionOperator.Equal, accountId);
query.Criteria.AddCondition("createdon", ConditionOperator.LastXDays, 30);
query.Orders.Add(new OrderExpression("createdon", OrderType.Descending));

var auditRecords = service.RetrieveMultiple(query);

foreach (var audit in auditRecords.Entities)
{
    var auditDetails = service.Retrieve("audit", audit.Id, new ColumnSet(true));
    // Parse AuditDetail XML for old/new values
}

Best Practices

  1. Principle of Least Privilege: Grant minimum necessary access
  2. Role Composition: Build modular roles, avoid duplicating base permissions
  3. Team Ownership: Use for shared queues and regional accounts
  4. Field Security: Protect PII, financial data, and sensitive fields
  5. Hierarchy Security: Enable for management visibility without role elevation
  6. Regular Audits: Review user access quarterly
  7. Document Roles: Maintain spreadsheet of role → permission mappings

Troubleshooting

User cannot see records they should access:

  • Verify security role assignment (user may have multiple roles)
  • Check business unit (user in wrong BU?)
  • Confirm privilege depth (User vs. Business Unit vs. Organization)
  • Review field-level security (field appears blank = no FLS permission)

"Access Denied" error in plugin:

  • Plugin runs as calling user by default (limited by user's security)
  • Use CreateOrganizationService(null) to run as SYSTEM (full access)
  • Or register plugin step as "Run in User Context" = specific user

Key Takeaways

  • Business units partition organizational data ownership
  • Security roles define CRUD privileges with scoped depth (User/BU/Org)
  • Field-level security restricts sensitive column access
  • Hierarchy security enables manager visibility without role changes
  • Teams enable shared ownership and collaborative access
  • Audit logging tracks all data access and changes for compliance

Next Steps

  • Implement Azure AD security groups for role assignment automation
  • Configure data loss prevention policies
  • Enable customer lockbox for Microsoft support access
  • Use privileged access workstations for admin tasks

Additional Resources


Secure by design, collaborative by nature.