ObjectStackObjectStack

Security Model Guide

Complete guide to implementing enterprise-grade security in ObjectStack with fine-grained permissions and data access controls

Security Model Guide

Complete guide to implementing enterprise-grade security in ObjectStack with fine-grained permissions and data access controls.

Table of Contents

  1. Security Architecture
  2. Profiles
  3. Permission Sets
  4. Role Hierarchy
  5. Sharing Rules
  6. Field-Level Security
  7. Best Practices

Security Architecture

ObjectStack implements a multi-layered security model:

┌─────────────────────────────────────┐
│   Application Visibility            │ ← Which apps can users access?
├─────────────────────────────────────┤
│   Object Permissions                │ ← CRUD on objects
├─────────────────────────────────────┤
│   Record-Level Security             │ ← Which records can be accessed?
│   - Organization-Wide Defaults      │
│   - Role Hierarchy                  │
│   - Sharing Rules                   │
│   - Manual Sharing                  │
├─────────────────────────────────────┤
│   Field-Level Security              │ ← Read/Write specific fields
└─────────────────────────────────────┘

Security Layers

  1. Object Permissions: Control create, read, update, delete on entire objects
  2. Record-Level Security: Control access to specific records
  3. Field-Level Security: Control visibility and editability of fields
  4. Row-Level Security: Control access based on data values

Profiles

Profiles define a user's baseline permissions.

Profile Structure

import type { Profile } from '@objectstack/spec/security';

export const SalesRepProfile: Profile = {
  name: 'sales_rep',
  label: 'Sales Representative',
  description: 'Standard sales rep permissions',
  
  // Object-level permissions
  objectPermissions: {
    account: {
      create: true,
      read: true,
      update: true,
      delete: false,
      viewAll: false,    // See all records regardless of owner
      modifyAll: false,  // Edit all records regardless of owner
    },
    opportunity: {
      create: true,
      read: true,
      update: true,
      delete: false,
      viewAll: false,
      modifyAll: false,
    },
  },
  
  // Field-level permissions
  fieldPermissions: {
    account: {
      annual_revenue: { read: true, update: false },
      description: { read: true, update: true },
    },
  },
  
  // Tab visibility
  tabVisibility: {
    account: 'default',     // Default tab
    lead: 'default',
    opportunity: 'default',
    case: 'hidden',         // Not visible
    product: 'available',   // Available but not default
  },
  
  // Application access
  applicationVisibility: {
    crm_example: true,
  },
};

Object Permission Levels

PermissionDescription
createCreate new records
readView records they own or have access to
updateEdit records they own or have access to
deleteDelete records they own or have access to
viewAllView ALL records regardless of ownership
modifyAllEdit ALL records regardless of ownership

Standard Profiles

Sales Representative

export const SalesRepProfile: Profile = {
  name: 'sales_rep',
  objectPermissions: {
    lead: { create: true, read: true, update: true, delete: false },
    account: { create: true, read: true, update: true, delete: false },
    contact: { create: true, read: true, update: true, delete: false },
    opportunity: { create: true, read: true, update: true, delete: false },
    quote: { create: true, read: true, update: true, delete: false },
    product: { create: false, read: true, update: false, delete: false },
  },
  fieldPermissions: {
    account: {
      annual_revenue: { read: true, update: false }, // Read-only
    },
  },
};

Sales Manager

export const SalesManagerProfile: Profile = {
  name: 'sales_manager',
  objectPermissions: {
    lead: { 
      create: true, read: true, update: true, delete: true,
      viewAll: true, modifyAll: true 
    },
    account: { 
      create: true, read: true, update: true, delete: true,
      viewAll: true, modifyAll: true 
    },
    // ... full access to sales objects
  },
};

Service Agent

export const ServiceAgentProfile: Profile = {
  name: 'service_agent',
  objectPermissions: {
    case: { create: true, read: true, update: true, delete: false },
    task: { create: true, read: true, update: true, delete: true },
    account: { create: false, read: true, update: false, delete: false },
    contact: { create: false, read: true, update: true, delete: false },
  },
  fieldPermissions: {
    case: {
      is_sla_violated: { read: true, update: false },
      resolution_time_hours: { read: true, update: false },
    },
  },
};

Permission Sets

Permission sets extend profile permissions without changing the profile.

import type { PermissionSet } from '@objectstack/spec/security';

export const AdvancedReportingPermissionSet: PermissionSet = {
  name: 'advanced_reporting',
  label: 'Advanced Reporting',
  description: 'Additional permissions for advanced reporting',
  
  objectPermissions: {
    opportunity: {
      viewAll: true,  // Override profile restriction
    },
    account: {
      viewAll: true,
    },
  },
  
  fieldPermissions: {
    opportunity: {
      amount: { read: true },
      probability: { read: true },
    },
  },
  
  systemPermissions: {
    runReports: true,
    exportReports: true,
    createDashboards: true,
  },
};

export const BulkDataPermissionSet: PermissionSet = {
  name: 'bulk_data_access',
  label: 'Bulk Data Access',
  description: 'Permissions for bulk data operations',
  
  systemPermissions: {
    bulkApiEnabled: true,
    viewAllData: true,
  },
};

Assigning Permission Sets

// Assign to user
await assignPermissionSet({
  userId: 'user123',
  permissionSetName: 'advanced_reporting',
});

// Assign to multiple users
await assignPermissionSetToGroup({
  permissionSetName: 'bulk_data_access',
  userGroup: 'data_analysts',
});

Role Hierarchy

Roles control record-level access through a hierarchy.

export const RoleHierarchy = {
  name: 'crm_role_hierarchy',
  label: 'CRM Role Hierarchy',
  
  roles: [
    // Top level
    {
      name: 'executive',
      label: 'Executive',
      parentRole: null,
    },
    
    // Sales hierarchy
    {
      name: 'sales_director',
      label: 'Sales Director',
      parentRole: 'executive',
    },
    {
      name: 'sales_manager',
      label: 'Sales Manager',
      parentRole: 'sales_director',
    },
    {
      name: 'sales_rep',
      label: 'Sales Representative',
      parentRole: 'sales_manager',
    },
    
    // Service hierarchy
    {
      name: 'service_director',
      label: 'Service Director',
      parentRole: 'executive',
    },
    {
      name: 'service_manager',
      label: 'Service Manager',
      parentRole: 'service_director',
    },
    {
      name: 'service_agent',
      label: 'Service Agent',
      parentRole: 'service_manager',
    },
  ],
};

How Role Hierarchy Works

                   Executive
                  /          \
        Sales Director    Service Director
             |                   |
        Sales Manager      Service Manager
             |                   |
         Sales Rep          Service Agent

Grant Access UP: Users see records owned by:

  • Themselves
  • Their subordinates
  • Their subordinates' subordinates (all levels down)

Example: Sales Director sees all records owned by Sales Managers and Sales Reps.


Sharing Rules

Sharing rules extend access beyond the role hierarchy.

Organization-Wide Defaults (OWD)

Set baseline access for all users:

export const OrganizationDefaults = {
  lead: {
    internalAccess: 'private',      // Users see only their own records
    externalAccess: 'private',
  },
  account: {
    internalAccess: 'private',
    externalAccess: 'private',
  },
  contact: {
    internalAccess: 'controlled_by_parent', // Access controlled by Account
    externalAccess: 'private',
  },
  opportunity: {
    internalAccess: 'private',
    externalAccess: 'private',
  },
  campaign: {
    internalAccess: 'public_read_only', // All users can read
    externalAccess: 'private',
  },
  product: {
    internalAccess: 'public_read_only',
    externalAccess: 'private',
  },
};

Access Levels

LevelDescription
privateOwner only (+ role hierarchy)
public_read_onlyAll users can read
public_read_writeAll users can read and edit
controlled_by_parentControlled by parent object

Criteria-Based Sharing Rules

Share records based on field criteria:

export const AccountTeamSharingRule: SharingRule = {
  name: 'account_team_sharing',
  label: 'Share Active Customers with Sales Team',
  objectName: 'account',
  type: 'criteria_based',
  
  // Criteria: Which records to share
  criteria: {
    type: { $eq: 'customer' },
    is_active: { $eq: true },
  },
  
  // Who to share with
  sharedWith: {
    type: 'role',
    roles: ['sales_manager', 'sales_director'],
  },
  
  // Access level granted
  accessLevel: 'read_write',
  
  // Also share related records
  includeRelatedObjects: [
    { objectName: 'contact', accessLevel: 'read_only' },
    { objectName: 'opportunity', accessLevel: 'read_only' },
  ],
};

Owner-Based Sharing Rules

Share based on record owner characteristics:

export const OpportunityOwnerSharingRule: SharingRule = {
  name: 'opportunity_owner_sharing',
  label: 'Share Opportunities within Same Territory',
  objectName: 'opportunity',
  type: 'owner_based',
  
  // Share records owned by users in these roles
  ownedBy: {
    type: 'role',
    roles: ['sales_rep'],
  },
  
  // Share with users in these roles
  sharedWith: {
    type: 'role',
    roles: ['sales_rep'],
    sameTerritory: true, // Only same territory
  },
  
  accessLevel: 'read_only',
};

Territory-Based Sharing

Share based on geographic territories:

export const TerritorySharingRules = [
  {
    name: 'north_america_territory',
    label: 'North America Territory',
    objectName: 'account',
    type: 'territory_based',
    
    criteria: {
      billing_address: {
        country: { $in: ['US', 'CA', 'MX'] },
      },
    },
    
    sharedWith: {
      type: 'territory',
      territory: 'north_america',
    },
    
    accessLevel: 'read_write',
  },
];

Field-Level Security

Control visibility and editability of specific fields.

Field Permissions in Profiles

fieldPermissions: {
  account: {
    // Field-level permissions
    annual_revenue: { 
      read: true,      // Can view
      update: false    // Cannot edit
    },
    description: { 
      read: true, 
      update: true 
    },
    ssn: {
      read: false,     // Hidden field
      update: false
    },
  },
  opportunity: {
    amount: { read: true, update: true },
    probability: { read: true, update: false },
  },
}

Hidden vs. Read-Only

// Hidden: Field not visible at all
{ read: false, update: false }

// Read-Only: Field visible but not editable
{ read: true, update: false }

// Editable: Field visible and editable
{ read: true, update: true }

Best Practices

1. Profile Design

DO:

  • Create minimal profiles (start restrictive, extend with permission sets)
  • Use descriptive names
  • Document the purpose of each profile
  • Assign one profile per user

DON'T:

  • Create too many profiles
  • Give excessive permissions "just in case"
  • Mix unrelated permissions in one profile

2. Permission Sets

DO:

  • Use for temporary or special access
  • Group related permissions
  • Name clearly (e.g., "Advanced Reporting", "Bulk Data Access")
  • Remove when no longer needed

DON'T:

  • Use as a replacement for profiles
  • Grant system-wide permissions unnecessarily

3. Role Hierarchy

DO:

  • Mirror your organizational structure
  • Keep hierarchies simple
  • Document reporting relationships
  • Review regularly

DON'T:

  • Create overly deep hierarchies
  • Mix different org structures
  • Grant too much upward access

4. Sharing Rules

DO:

  • Start with most restrictive OWD
  • Use sharing rules to open up access
  • Document business justification
  • Test thoroughly

DON'T:

  • Set OWD to Public unless necessary
  • Create redundant sharing rules
  • Grant more access than needed

5. Field-Level Security

DO:

  • Protect sensitive data (SSN, salary, etc.)
  • Use read-only for calculated fields
  • Document security classifications
  • Audit regularly

DON'T:

  • Hide required fields
  • Restrict access unnecessarily
  • Forget about API access

Security Checklist

Initial Setup

  • Define user roles and hierarchies
  • Create profiles for each role
  • Set organization-wide defaults
  • Configure field-level security
  • Create sharing rules

Ongoing Maintenance

  • Review user access quarterly
  • Audit permission changes
  • Remove inactive users promptly
  • Update sharing rules as needed
  • Monitor security health checks

Compliance

  • Document security model
  • Maintain access request process
  • Log security changes
  • Conduct security reviews
  • Train users on security policies

Real-World Example

Complete security setup for a sales team:

// 1. Organization-Wide Defaults
OrganizationDefaults = {
  account: { internalAccess: 'private' },
  opportunity: { internalAccess: 'private' },
  contact: { internalAccess: 'controlled_by_parent' },
};

// 2. Role Hierarchy
RoleHierarchy = {
  roles: [
    { name: 'sales_vp', parentRole: null },
    { name: 'sales_manager', parentRole: 'sales_vp' },
    { name: 'sales_rep', parentRole: 'sales_manager' },
  ],
};

// 3. Profiles
SalesRepProfile = {
  objectPermissions: {
    account: { create: true, read: true, update: true, delete: false },
    opportunity: { create: true, read: true, update: true, delete: false },
  },
  fieldPermissions: {
    account: {
      annual_revenue: { read: true, update: false }, // Read-only
    },
  },
};

// 4. Sharing Rules
AccountSharingRule = {
  // Share high-value accounts with all sales reps
  criteria: { annual_revenue: { $gte: 1000000 } },
  sharedWith: { type: 'role', roles: ['sales_rep'] },
  accessLevel: 'read_only',
};

// 5. Permission Set
AdvancedReportingPermissionSet = {
  // For sales analysts
  systemPermissions: {
    runReports: true,
    exportReports: true,
    viewAllData: true,
  },
};

Next: Automation →

On this page