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