Skip to main content

Developer Guide

This guide covers the technical implementation of Access Control for CX Assistants, including data models, the access check logic, and API reference.

Architecture Overview

Access Control restricts which users can access specific Assistants through a group-based permission system:

Data Model

AssistantUserGroup

Groups that can be assigned to both users and Assistants to control access.

FieldTypeRequiredDescription
idintegerAutoPrimary key
namestringYesUnique group name (max 255 characters)
usersManyToManyNoUsers who belong to this group
created_atdatetimeAutoTimestamp when the group was created
updated_atdatetimeAutoTimestamp when the group was last updated

Django Model Definition:

class AssistantUserGroup(TimestampedModel):
name = models.CharField(
max_length=255,
unique=True,
error_messages={"unique": "Group with this name already exists."}
)

class Meta:
verbose_name = "User Group"
verbose_name_plural = "User Groups"

Assistant (Access Control Fields)

The Assistant model includes group-based authorization:

FieldTypeDescription
authorized_groupsManyToManyGroups that have access to this Assistant

Django Model Definition (relevant excerpt):

class Assistant(TimestampedModel):
# ... other fields ...
authorized_groups = models.ManyToManyField(
"assistants.AssistantUserGroup",
blank=True,
related_name="authorized_assistants"
)

CustomUser (Group Membership)

Users are assigned to groups through the assistant_user_groups field:

FieldTypeDescription
assistant_user_groupsManyToManyGroups the user belongs to

Django Model Definition (relevant excerpt):

class CustomUser(AbstractBaseUser, PermissionsMixin):
# ... other fields ...
assistant_user_groups = models.ManyToManyField(
"assistants.AssistantUserGroup",
blank=True,
related_name="users"
)

User (Domain Model)

The internal domain representation used during evaluation:

class User(BaseModel):
user_id: int
assistant_groups: list[int] = []

Access Control Logic

user_has_access Method

The user_has_access method on the Assistant domain model determines whether a user can access the Assistant:

def user_has_access(self, user: User) -> bool:
if not self.authorized_groups:
return True

return any(group in user.assistant_groups for group in self.authorized_groups)

Logic:

  1. If the Assistant has no authorized_groups configured, all users have access
  2. If authorized_groups are configured, the user must belong to at least one of those groups

Evaluation Check

The access check is performed in the AssistantEvaluator._evaluate_assistant method:

if user is not None and not assistant.user_has_access(user):
logger.info(
"Assistant %s: user cannot access (evaluation trace ID: %s)",
assistant.code,
evaluation_trace_id
)
return USER_HAS_NO_ACCESS_MESSAGE, evaluation_id, EvaluationStatus.NO_ACCESS_TO_ASSISTANT

Constants:

  • USER_HAS_NO_ACCESS_MESSAGE = "User has no access to this assistant."
  • EvaluationStatus.NO_ACCESS_TO_ASSISTANT - Returned when access is denied

User Group Resolution

When evaluating an Assistant, the user's groups are retrieved from the database:

class UserRepository:
async def get_user(self, user_uuid: str) -> User:
orm_user = await orm.CustomUser.objects.filter(uuid=user_uuid).afirst()
if not orm_user:
raise ValueError(f"User with UUID {user_uuid} not found")
return User(
user_id=orm_user.id,
assistant_groups=[group.id async for group in orm_user.assistant_user_groups.all()],
)

API Reference

Assistant User Groups API

CRUD operations for managing user groups.

List Groups

GET /api/v1/assistants/user-groups/

Response:

[
{
"id": 1,
"name": "Pilot Group",
"users": [
{
"id": 42,
"email": "agent@example.com",
"first_name": "John",
"last_name": "Doe"
}
],
"assistants": [
{
"id": 10,
"code": "support-assistant",
"name": "Support Assistant"
}
]
}
]

Create Group

POST /api/v1/assistants/user-groups/

Request Body:

{
"name": "Technical Support",
"users": [42, 43, 44]
}

Update Group

PUT /api/v1/assistants/user-groups/{id}/

Request Body:

{
"name": "Technical Support Team",
"users": [42, 43, 44, 45]
}

Delete Group

DELETE /api/v1/assistants/user-groups/{id}/
warning

Deleting a group immediately removes access for all users in that group to any Assistants that only authorized that group.

Evaluation Response

When a user lacks access to an Assistant, the evaluation endpoint returns:

{
"output": "User has no access to this assistant.",
"evaluation_id": "abc-123",
"evaluation_status": "no_access_to_assistant"
}

Integration with On-Demand Assistants

For on-demand Assistants displayed in the UI, the profile serializer filters Assistants based on user group membership:

if user and user.assistant_user_groups.exists():
assistant_user_group_ids = list(user.assistant_user_groups.values_list("id", flat=True))
assistant_id_list += list(
Assistant.authorized_groups.through.objects.filter(
assistantusergroup_id__in=assistant_user_group_ids
).values_list("assistant_id", flat=True)
)

This ensures users only see Assistants they have access to in the frontend.

Access Control Matrix

Assistant ConfigUser GroupsResult
No authorized groupsAnyAccess granted
Groups: [A, B]User in [A]Access granted
Groups: [A, B]User in [B, C]Access granted
Groups: [A, B]User in [C, D]Access denied
Groups: [A]User in []Access denied

Source Code References

ComponentLocation
AssistantUserGroup modelassistants/models.py:291-301
Assistant.authorized_groupsassistants/models.py:221-222
CustomUser.assistant_user_groupsdashboard/models/custom_user.py:53
User domain modelbackend/domains/assistants/models/user.py
user_has_access methodbackend/domains/assistants/models/assistant.py:150-154
Access check in evaluatorbackend/domains/assistants/services/evaluate.py:188-192
UserRepositorybackend/domains/assistants/repositories/user.py
AssistantUserGroupViewSetassistants/views.py:385-390