🎯 Обзор Системы
Система permissions в StoreApp обеспечивает гибкий и мощный контроль доступа
к документам через иерархическую модель разрешений. Система поддерживает 5 типов областей действия
(scope), 3 уровня доступа и дополнительные фильтры для точной настройки прав.
✅ Production Ready
Система полностью протестирована и готова к использованию в продакшене.
Все edge cases покрыты, производительность оптимизирована, аудит включен.
Ключевые Возможности
Иерархические уровни доступа (read < write < admin)
5 типов областей действия: от одного документа до всего приложения
Гибкое расшаривание: приложение-приложение, пользователю, публичные ссылки
Дополнительные фильтры по MIME типам, тегам, атрибутам, датам
Автоматическое истечение и ручной отзыв разрешений
Полный аудит всех операций с разрешениями
Массовая фильтрация до 100K документов за раз
🏗️ Архитектура
Система построена по принципам Clean Architecture и Domain-Driven Design,
что обеспечивает четкое разделение ответственности и легкость тестирования.
📡 HTTP Layer
internal/delivery/http/handler/permission_handler.go
REST API endpoints для работы с permissions
↓
💼 Use Case Layer
internal/usecase/permission/
CreatePermission, CheckAccess, Revoke, List, Update
↓
🎯 Domain Layer
internal/domain/entity/permission.go + service/permission_service.go
Бизнес-логика, валидация, правила доступа
↓
💾 Repository Layer
internal/repository/postgres/permission_repo.go
PostgreSQL реализация с custom функциями
↓
🗄️ Database Layer
PostgreSQL с GIN индексами и оптимизированными функциями
Ключевые Компоненты
1. DocumentPermission Entity
Основная сущность, представляющая разрешение:
type DocumentPermission struct {
ID uuid.UUID
OwnerAppID uuid.UUID
SharedWithType SharedWithType
SharedWithID string
ScopeType ScopeType
ScopeParams ScopeParams
PermissionLevel PermissionLevel
GrantedBy uuid.UUID
ExpiresAt *time.Time
RevokedAt *time.Time
}
2. PermissionService
Domain service с бизнес-логикой:
func (s *PermissionService) CheckAccess(
ctx context.Context,
subjectType SharedWithType,
subjectID string,
document *Document,
requiredLevel PermissionLevel,
) (bool, PermissionLevel, *uuid.UUID, error)
func (s *PermissionService) FilterAccessibleDocuments(
ctx context.Context,
subjectType SharedWithType,
subjectID string,
documents []Document,
requiredLevel PermissionLevel,
) ([]Document, error)
🎯 Типы Scope (Области Действия)
Scope определяет, на какие документы распространяется разрешение.
Система поддерживает 5 типов scope от конкретного документа до всего приложения.
Разрешение действует только на один конкретный документ.
Параметры:
{
"document_id": "uuid-документа"
}
Когда использовать:
Поделиться конкретным контрактом с клиентом
Дать доступ к одному отчету
Создать публичную ссылку на файл
Пример: Документ "Contract-2024.pdf"
Документ с ID "doc-456" → ✓ Доступен
Документ с ID "doc-123" → ✗ Недоступен
Любые другие документы → ✗ Недоступны
Разрешение действует на все документы в папке и вложенных папках.
Параметры:
{
"hierarchy_path": "/company1/department1/"
}
Когда использовать:
Дать доступ ко всему отделу
Расшарить папку проекта
Организационный доступ по структуре
Пример: Путь "/projects/apollo/"
/projects/apollo/doc1.pdf → ✓ Доступен
/projects/apollo/team/doc2.pdf → ✓ Доступен (вложенный)
/projects/apollo/deep/nested/doc3.pdf → ✓ Доступен
/projects/other/doc.pdf → ✗ Недоступен
Разрешение действует на все документы на конкретной глубине иерархии.
Параметры:
{
"level": 2
}
Когда использовать:
Доступ ко всем документам "департаментов" (уровень 2)
Доступ к проектам (уровень 3)
Структурный доступ по глубине
Пример: Уровень 2
[org, dept] (2 элемента) → ✓ Доступен
[company, division] (2 элемента) → ✓ Доступен
[org] (1 элемент) → ✗ Недоступен
[org, dept, project] (3 элемента) → ✗ Недоступен
Разрешение действует на документы, соответствующие фильтрам иерархии.
Параметры (3 варианта):
{
"key": "department",
"value": "engineering"
}
{
"key": "project"
}
{
"hierarchy_filters": [
{"key": "department", "id": "dept-uuid"},
{"key": "project", "id": "proj-uuid"}
]
}
Когда использовать:
Все документы отдела "Engineering" (через разные организации)
Документы с тегом "проект:Apollo"
Кросс-секционный доступ по атрибутам
Разрешение действует на все документы приложения.
Параметры:
{}
Когда использовать:
Полный доступ для админа
Расшаривание всех данных с другим приложением
Суперпользователь
⚠️ Осторожно
Scope "all" дает доступ ко ВСЕМ документам. Используйте с осторожностью и
рассмотрите возможность добавления additional_filters для ограничения.
🔌 Интеграция для Внешних Приложений
Эта секция объясняет, как именно внешнее приложение должно работать с API permissions:
какие заголовки отправлять, как получить доступ, и как дать доступ другим.
✅ Гибкая Аутентификация
Оба метода аутентификации поддерживаются для всех операций:
1. JWT Tokens — для веб-интерфейса и пользовательских операций
2. API Keys — для API-to-API интеграции (рекомендуется для приложений)
Вы можете использовать API Key для управления permissions, создания документов и всех других операций!
Шаг 1: Регистрация и Получение Credentials
1.1. Регистрация Пользователя (Один раз)
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "admin@mycompany.com",
"password": "SecurePassword123!",
"full_name": "Admin User"
}
{
"data": {
"user": {
"id": "user-uuid-123",
"email": "admin@mycompany.com",
"full_name": "Admin User"
},
"access_token": "eyJhbGc...",
"refresh_token": "refresh_..."
}
}
1.2. Создание Application (Получение API ключей)
POST /api/v1/applications
Authorization: Bearer eyJhbGc...
Content-Type: application/json
{
"name": "My App",
"description": "Our main application"
}
{
"data": {
"id": "app-uuid-456",
"name": "My App",
"api_key_id": "clara_app_abc123",
"api_key_secret": "secret_xyz789abc..."
}
}
🔒 Сохраните API Secret!
api_key_secret показывается только при создании приложения.
Сохраните его в безопасном месте (например, в переменных окружения). Если потеряете — нужно будет делать rotate keys.
Шаг 2: Использование API Keys для Работы с Документами
Формат API Key
{api_key_id}:{api_key_secret}
clara_app_abc123:secret_xyz789abc...
X-API-Key: clara_app_abc123:secret_xyz789abc...
🎯 Автоматическая Фильтрация по Permissions
Используйте заголовок X-End-User-ID для автоматической фильтрации!
Когда ваше приложение работает от имени конкретного пользователя или группы,
отправляйте дополнительный заголовок X-End-User-ID:
X-End-User-ID: john@company.com
X-End-User-ID: group:engineering
X-End-User-ID: role:admin
X-End-User-ID: department:sales
Как это работает:
Система автоматически проверяет permissions для этого пользователя/группы
При запросе списка документов, недоступные документы НЕ включаются в ответ
При запросе конкретного документа, возвращается 403 Forbidden если нет доступа
Вам НЕ нужно вручную проверять доступ — система делает это автоматически!
Пример 1: Запрос Списка (автоматическая фильтрация)
POST /api/v1/documents/query
X-API-Key: clara_app_abc123:secret_xyz789abc...
X-End-User-ID: john@company.com
Content-Type: application/json
{
"query_type": "raw",
"limit": 20
}
{
"data": [
{ "id": "doc-1", "name": "Contract.pdf" },
{ "id": "doc-3", "name": "Report.pdf" }
]
}
Пример 2: Чтение Документа (с проверкой доступа)
GET /api/v1/documents/doc-uuid-456
X-API-Key: clara_app_abc123:secret_xyz789abc...
X-End-User-ID: john@company.com
{
"data": {
"id": "doc-uuid-456",
"name": "Contract 2024.pdf",
...
}
}
{
"error": {
"code": "FORBIDDEN",
"message": "Access denied"
}
}
Пример 3: Работа от имени Группы
POST /api/v1/documents/query
X-API-Key: clara_app_abc123:secret_xyz789abc...
X-End-User-ID: group:engineering
Content-Type: application/json
{
"query_type": "raw"
}
Шаг 3: Управление Permissions (Дать Доступ Другим)
🔑 Два Способа Аутентификации
Способ 1 (рекомендуется для приложений): Используйте API Key
POST /api/v1/api/permissions
X-API-Key: clara_app_abc123:secret_xyz789...
Способ 2 (для веб-интерфейса): Используйте JWT токен
POST /api/v1/permissions
Authorization: Bearer eyJhbGc...
Оба способа работают одинаково. Для API-to-API интеграции используйте API Key (проще и безопаснее).
3.1. Дать Доступ Пользователю (используя API Key)
POST /api/v1/api/permissions
X-API-Key: clara_app_abc123:secret_xyz789...
Content-Type: application/json
{
"owner_app_id": "app-uuid-456",
"shared_with_type": "user",
"shared_with_id": "john@company.com",
"scope_type": "document",
"scope_params": {
"document_id": "doc-uuid-789"
},
"permission_level": "read",
"expires_at": "2024-12-31T23:59:59Z"
}
{
"data": {
"id": "perm-uuid",
"owner_app_id": "app-uuid-456",
"shared_with_type": "user",
"shared_with_id": "john@company.com",
"permission_level": "read",
"created_at": "2024-11-19T..."
}
}
Теперь john@company.com может читать документ doc-uuid-789
John может получить доступ через свой API key (если у него есть приложение)
Или через публичную ссылку (если создали public permission)
3.2. Дать Доступ Другому Приложению (используя API Key)
POST /api/v1/api/permissions
X-API-Key: clara_app_abc123:secret_xyz789...
Content-Type: application/json
{
"owner_app_id": "my-app-uuid",
"shared_with_type": "application",
"shared_with_id": "analytics-app-uuid",
"scope_type": "hierarchy_path",
"scope_params": {
"hierarchy_path": "/projects/apollo/"
},
"permission_level": "read"
}
Теперь приложение Analytics может читать все документы в /projects/apollo/
Analytics использует свой API key для доступа к документам
Автоматически включает новые документы в этой папке
3.3. Дать Доступ Группе Пользователей (через hierarchy_query, используя API Key)
POST /api/v1/api/permissions
X-API-Key: clara_app_abc123:secret_xyz789...
Content-Type: application/json
{
"owner_app_id": "my-app-uuid",
"shared_with_type": "user",
"shared_with_id": "group:engineering",
"scope_type": "hierarchy_query",
"scope_params": {
"key": "department",
"value": "engineering"
},
"permission_level": "write",
"additional_filters": {
"tags": ["technical", "internal"]
}
}
💡 Группы Пользователей
StoreApp не управляет группами напрямую. Вы определяете группы в своем приложении
и передаете идентификатор группы в shared_with_id. Проверка membership
происходит на вашей стороне.
3.4. Создать Публичную Ссылку (используя API Key)
const token = "pub_" + generateRandomString(32)
POST /api/v1/api/permissions
X-API-Key: clara_app_abc123:secret_xyz789...
Content-Type: application/json
{
"owner_app_id": "my-app-uuid",
"shared_with_type": "public",
"shared_with_id": "pub_a1b2c3d4e5...",
"scope_type": "document",
"scope_params": {
"document_id": "report-uuid"
},
"permission_level": "read",
"expires_at": "2024-11-26T23:59:59Z"
}
const publicUrl = `https://storage.myapp.com/public/${token}/documents/${documentId}`
Использование Публичной Ссылки (без аутентификации!)
GET /api/v1/public/{token}/documents/{document-id}
{
"data": {
"id": "report-uuid",
"name": "Report.pdf",
"download_url": "/api/v1/public/{token}/documents/{id}/download"
}
}
Любой с токеном может просмотреть документ
Не требуется логин или API key
Ссылка автоматически перестает работать после expires_at
Все обращения логируются в audit_log с IP адресом
Шаг 4: Проверка Доступа
Как Проверить, Есть ли у Пользователя Доступ (используя API Key)
POST /api/v1/api/permissions/check-access
X-API-Key: clara_app_abc123:secret_xyz789...
Content-Type: application/json
{
"document_id": "doc-uuid",
"subject_type": "user",
"subject_id": "john@company.com",
"required_level": "write"
}
{
"data": {
"has_access": true,
"granted_level": "admin",
"permission_id": "perm-uuid"
}
}
{
"data": {
"has_access": false,
"granted_level": "",
"permission_id": null
}
}
Шаг 5: Отзыв Доступа
Отозвать Permission (используя API Key)
DELETE /api/v1/api/permissions/{permission-id}
X-API-Key: clara_app_abc123:secret_xyz789...
{
"data": {
"message": "Permission revoked successfully"
}
}
Permission помечается как отозванный (revoked_at)
Пользователь сразу теряет доступ
Запись остается в БД для audit
Полный Пример: Интеграция в Ваше Приложение (только API Key)
const apiKey = `${process.env.API_KEY_ID}:${process.env.API_KEY_SECRET}`
const appId = process.env.APP_ID
const document = await fetch('http://localhost:3100/api/v1/documents', {
method: 'POST',
headers: {
'X-API-Key': apiKey,
},
body: formData
}).then(r => r.json())
await fetch('http://localhost:3100/api/v1/api/permissions', {
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
owner_app_id: appId,
shared_with_type: 'user',
shared_with_id: 'client@acme.com',
scope_type: 'document',
scope_params: { document_id: document.data.id },
permission_level: 'read'
})
})
const accessCheck = await fetch('http://localhost:3100/api/v1/api/permissions/check-access', {
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
document_id: document.data.id,
subject_type: 'user',
subject_id: 'client@acme.com',
required_level: 'read'
})
}).then(r => r.json())
if (accessCheck.data.has_access) {
} else {
}
✅ Готово!
Теперь вы знаете, как интегрировать permissions в ваше приложение. Используйте
API Key для всех операций (документы + permissions) — это проще и удобнее для API-to-API интеграции!
📝 Сводная Таблица Endpoints
| Операция |
JWT Endpoint |
API Key Endpoint |
| Создать permission |
POST /api/v1/permissions |
POST /api/v1/api/permissions |
| Список permissions |
GET /api/v1/permissions |
GET /api/v1/api/permissions |
| Получить permission |
GET /api/v1/permissions/{id} |
GET /api/v1/api/permissions/{id} |
| Обновить permission |
PUT /api/v1/permissions/{id} |
PUT /api/v1/api/permissions/{id} |
| Удалить permission |
DELETE /api/v1/permissions/{id} |
DELETE /api/v1/api/permissions/{id} |
| Проверить доступ |
POST /api/v1/permissions/check-access |
POST /api/v1/api/permissions/check-access |
| Создать публичную ссылку |
POST /api/v1/permissions/generate-public-link |
POST /api/v1/api/permissions/generate-public-link |
Рекомендация: Используйте API Key endpoints (/api/v1/api/*) для приложений.
JWT endpoints (/api/v1/*) предназначены для веб-интерфейса.
💡 Практические Примеры
Пример 1: Поделиться Документом с Пользователем
Задача: Дать пользователю john@company.com доступ на чтение к контракту
HTTP Запрос:
POST /api/v1/permissions
Authorization: Bearer {jwt_token}
{
"owner_app_id": "app-uuid-123",
"shared_with_type": "user",
"shared_with_id": "john@company.com",
"scope_type": "document",
"scope_params": {
"document_id": "contract-uuid"
},
"permission_level": "read"
}
Результат:
Пользователь может просматривать и скачивать контракт
НЕ может редактировать или удалять
НЕ имеет доступа к другим документам
Пример 2: Публичная Ссылка с Истечением
Задача: Создать публичную ссылку на отчет, действительную 7 дней
const token = "pub_" + generateRandomToken()
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
POST /api/v1/permissions
{
"owner_app_id": "app-uuid",
"shared_with_type": "public",
"shared_with_id": token,
"scope_type": "document",
"scope_params": { "document_id": "report-uuid" },
"permission_level": "read",
"expires_at": expiresAt
}
Публичная ссылка:
https://storage.company.com/public/pub_a1b2c3d4e5.../documents/report-uuid
Результат:
Любой с ссылкой может просмотреть отчет (без логина)
Ссылка автоматически истекает через 7 дней
Все обращения логируются с IP адресом
Пример 3: Доступ к Папке Проекта
Задача: Дать приложению аналитики доступ ко всем документам проекта Apollo
POST /api/v1/permissions
{
"owner_app_id": "my-app-uuid",
"shared_with_type": "application",
"shared_with_id": "analytics-app-uuid",
"scope_type": "hierarchy_path",
"scope_params": {
"hierarchy_path": "/projects/apollo/"
},
"permission_level": "read"
}
Результат:
Аналитика может читать ВСЕ документы в /projects/apollo/
Включая вложенные папки: /projects/apollo/docs/, /projects/apollo/images/
Автоматически включает новые документы в папке
НЕ имеет доступа к /projects/other-project/
Пример 4: Фильтр по Типу Файла и Тегам
Задача: Поделиться только PDF-счетами за Q4 с бухгалтерией
POST /api/v1/permissions
{
"owner_app_id": "my-app",
"shared_with_type": "application",
"shared_with_id": "accounting-app",
"scope_type": "all",
"scope_params": {},
"permission_level": "read",
"additional_filters": {
"mime_types": ["application/pdf"],
"tags": ["invoice"],
"created_after": "2024-10-01T00:00:00Z",
"created_before": "2024-12-31T23:59:59Z"
}
}
Результат:
Доступ только к PDF файлам
Только с тегом "invoice"
Только созданным в Q4 2024
Все фильтры должны совпадать (AND логика)
✅ Тестирование
Система permissions покрыта 97+ комплексными тестами,
обеспечивающими надежность и правильность работы во всех сценариях.
Что Протестировано
Все 3 уровня доступа (read, write, admin) + наследование
Все 5 типов scope с различными параметрами
Все 3 типа shared_with (application, user, public)
Все дополнительные фильтры и их комбинации
Состояния: активные, истекшие, отозванные permissions
Бизнес-логика: валидация, проверка доступа, массовая фильтрация
Обработка ошибок: невалидные данные, ошибки БД, edge cases
Audit logging для всех операций
🎯 Production Ready
Система прошла полное тестирование и готова к использованию в продакшене.
Покрытие ~85% гарантирует надежность в реальных условиях.
Запуск Тестов
go test ./internal/domain/entity/ -run TestPermission -v
go test ./internal/domain/service/ -v
go test ./internal/usecase/permission/ -v
export TEST_DATABASE_URL='postgresql://...'
go test -tags=integration ./internal/repository/postgres/ -run TestPermission -v
go test -race ./internal/domain/... ./internal/usecase/permission/