/*
  Сервис реализует функционал управления доступом пользователями к ресурсам
*/
syntax = "proto3";

import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";

package keyapis.access_management.v1;

option java_package = "ru.keyapis.access_management.v1";
option java_outer_classname = "KeyapisAccessManagementV1Proto";
option java_multiple_files = false;
option java_string_check_utf8 = true;
option go_package = "/keyapis_access_management_v1";
option cc_enable_arenas = true;
option csharp_namespace = "Keyapis.AccessManagement.V1";
option objc_class_prefix = "KEYAPISACCESSMANAGEMENTV1";
option php_namespace = "Keyapis\\AccessManagement\\V1";
option ruby_package = "Keyapis::AccessManagement::V1";
option optimize_for = LITE_RUNTIME;

// Сервис управления доступом пользователей
service GroupService {
    // Метод получения группы.
    // Метод доступен для: admin, service, bti, seller
    rpc GetGroup (GetGroupRequest) returns (GetGroupResponse) {
        option (google.api.http) = {
            get: "/access_management/api/v1/group/{id}"
        };
    }
    // Метод получения списка групп.
    // Метод доступен для: admin, service, bti, seller
    rpc GetGroupList (GetGroupListRequest) returns (stream GetGroupListResponse) {
        option (google.api.http) = {
            get: "/access_management/api/v1/group/list"
        };
    }
    // Метод получения количества групп.
    // Метод доступен для: admin, service, bti, seller
    rpc GetGroupCount (GetGroupCountRequest) returns (GetGroupCountResponse) {
        option (google.api.http) = {
            get: "/access_management/api/v1/group/count"
        };
    }
    // Метод сохранения группы.
    // Поддерживает создание и обновление как UPSERT по Group.id.
    // Метод доступен для: admin, service
    rpc PostGroup (PostGroupRequest) returns (PostGroupResponse) {
        option (google.api.http) = {
            post: "/access_management/api/v1/group"
            body: "*"
        };
    }
    // Метод удаления группы.
    // Метод доступен для: admin, service
    rpc DeleteGroup (DeleteGroupRequest) returns (DeleteGroupResponse) {
        option (google.api.http) = {
            delete: "/access_management/api/v1/group/{id}"
        };
    }
    // Метод создания группы по шаблону.
    // Метод доступен для: admin, service, bti, seller
    rpc PostGroupByTemplate (PostGroupByTemplateRequest) returns (PostGroupByTemplateResponse) {
        option (google.api.http) = {
            post: "/access_management/api/v1/group/by_template"
            body: "*"
        };
    }
    // Метод привязки пользователя к группе.
    // Метод доступен для: admin, owner, service, bti, seller
    rpc PutGroupUserAttach (PutGroupUserAttachRequest) returns (PutGroupUserAttachResponse) {
        option (google.api.http) = {
            put: "/access_management/api/v1/group/{user_group.group_id}/user/{user_group.user_id}/attach",
        };
    }
    // Метод удаления привязки пользователя к группе.
    // Метод доступен для: admin, owner, service, bti, seller
    rpc PutGroupUserDetach (PutGroupUserDetachRequest) returns (PutGroupUserDetachResponse) {
        option (google.api.http) = {
            put: "/access_management/api/v1/group/{user_group.group_id}/user/{user_group.user_id}/detach",
        };
    }
    // Метод получения списка связей пользователей и групп.
    // Метод доступен для: admin, owner, service, bti, seller
    rpc GetGroupUserList (GetGroupUserListRequest) returns (stream GetGroupUserListResponse) {
        option (google.api.http) = {
            get: "/access_management/api/v1/group/user/list"
        };
    }
    // Метод получения количества связей пользователей и групп.
    // Метод доступен для: admin, owner, service, bti, seller
    rpc GetGroupUserCount (GetGroupUserCountRequest) returns (GetGroupUserCountResponse) {
        option (google.api.http) = {
            get: "/access_management/api/v1/group/user/count"
        };
    }
    // Метод получения уникальных назначенных атрибутов пользователя.
    // Метод также возвращает группы пользователя в виде атрибута (Claim) {key=GROUP_ID,value=Group.id}.
    // Бизнес-пользователи получают только свои разрешения, поэтому для всех пользователей, кроме admin, owner, service, bti, seller игнорируются GroupClaimFilter.user_id, GroupClaimFilter.user_data, GroupClaimFilter.api_key_hash, а значения для них берутся из токена.
    // Для admin, owner, service, bti, seller при наличии GroupClaimFilter.user_id отдаем разрешения для указанного GroupClaimFilter.user_id и GroupClaimFilter.user_data, при отсутствии GroupClaimFilter.user_id значения берутся из токена.
    // Для admin, service при наличии GroupClaimFilter.api_key_hash возвращаем Claim с учетом только GroupClaimFilter.api_key_hash.
    // Метод доступен для: admin, master, slave, owner, employee, service, bti, seller, external_seller, ltp_first, mrf, manager
    rpc GetGroupClaimList (GetGroupClaimListRequest) returns (stream GetGroupClaimListResponse) {
        option (google.api.http) = {
            get: "/access_management/api/v1/group/claim/list"
        };
    }
    // Метод получения количества уникальных назначенных атрибутов пользователя.
    // Метод также сосчитает и атрибуты групп {key=GROUP_ID,value=Group.id}.
    // Бизнес-пользователи получают только свои разрешения, поэтому для всех пользователей, кроме admin, owner, service, bti, seller игнорируются GroupClaimFilter.user_id, GroupClaimFilter.user_data, GroupClaimFilter.api_key_hash, а значения для них берутся из токена.
    // Для admin, owner, service, bti, seller при наличии GroupClaimFilter.user_id отдаем разрешения для указанного GroupClaimFilter.user_id и GroupClaimFilter.user_data, при отсутствии GroupClaimFilter.user_id значения берутся из токена.
    // Для admin, service при наличии GroupClaimFilter.api_key_hash возвращаем Claim с учетом только GroupClaimFilter.api_key_hash.
    // Метод доступен для: admin, master, slave, owner, employee, service, bti, seller, external_seller, ltp_first, mrf, manager
    rpc GetGroupClaimCount (GetGroupClaimCountRequest) returns (GetGroupClaimCountResponse) {
        option (google.api.http) = {
            get: "/access_management/api/v1/group/claim/count"
        };
    }
    // Метод проверки наличия назначенных атрибутов пользователя.
    // Метод также проверит и атрибуты групп {key=GROUP_ID,value=Group.id}.
    // Бизнес-пользователи получают только свои разрешения, поэтому для всех пользователей, кроме admin, owner, service, bti, seller игнорируются GroupClaimFilter.user_id, GroupClaimFilter.user_data, GroupClaimFilter.api_key_hash, а значения для них берутся из токена.
    // Для admin, owner, service, bti, seller при наличии GroupClaimFilter.user_id отдаем разрешения для указанного GroupClaimFilter.user_id и GroupClaimFilter.user_data, при отсутствии GroupClaimFilter.user_id значения берутся из токена.
    // Для admin, service при наличии GroupClaimFilter.api_key_hash возвращаем Claim с учетом только GroupClaimFilter.api_key_hash.
    // Метод доступен для: admin, master, slave, owner, employee, service, bti, seller, external_seller, ltp_first, mrf, manager
    rpc GetGroupClaimExist (GetGroupClaimExistRequest) returns (GetGroupClaimExistResponse) {
        option (google.api.http) = {
            get: "/access_management/api/v1/group/claim/exist"
        };
    }
    // Метод добавления ключа доступа ApiKey.
    // Метод доступен для: admin
    rpc PutGroupApiKeyCreate (PutGroupApiKeyCreateRequest) returns (PutGroupApiKeyCreateResponse) {
        option (google.api.http) = {
            put: "/access_management/api/v1/group/{group_id}/api_key/create"
        };
    }
    // Метод удаления ключей доступа ApiKey.
    // Метод доступен для: admin
    rpc PutGroupApiKeyDelete (PutGroupApiKeyDeleteRequest) returns (PutGroupApiKeyDeleteResponse) {
        option (google.api.http) = {
            put: "/access_management/api/v1/group/{group_id}/api_key/delete"
        };
    }
}

// Запрос на добавления группе ключа доступа ApiKey
message PutGroupApiKeyCreateRequest {
  // Идентификатор группы.
  // # Тип: Guid
  string group_id = 1 [(google.api.field_behavior) = REQUIRED];
}

// Ответ на запрос на добавления группе ключа доступа ApiKey
message PutGroupApiKeyCreateResponse {
  // Тип результата
  oneof type {
    // Значение ключа доступа ApiKey.
    // # Тип: Guid
    string data = 1;
  }
}

// Запрос на удаление ключей доступа ApiKey
message PutGroupApiKeyDeleteRequest {
  // Идентификатор группы.
  // # Тип: Guid
  string group_id = 1 [(google.api.field_behavior) = REQUIRED];
}

// Ответ на запрос на удаление ключей доступа ApiKey
message PutGroupApiKeyDeleteResponse {}

// Атрибут пользователя
message Claim {
    // Ключ атрибута
    string key = 1 [(google.api.field_behavior) = REQUIRED];
    // Значение атрибута
    string value = 2;
}

// Связка пользователя с группой
message UserGroup {
    // Идентификатор пользователя.
    // Соответствует subject, sub, client_id из JWT.
    // # Диапазон: 3..256
    string user_id = 1 [(google.api.field_behavior) = REQUIRED];
    // Идентификатор группы.
    // # Тип: Guid
    string group_id = 2 [(google.api.field_behavior) = REQUIRED];
}

// Группа атрибутов пользователя
message Group {
    // Идентификатор группы.
    // # Тип: Guid
    string id = 1;
    // Наименование.
    // # Диапазон: 3..256
    string title = 2 [(google.api.field_behavior) = REQUIRED];
    // Описание.
    // # Диапазон: 3..256
    string description = 3;
    // Массив атрибутов группы.
    // Эти атрибуты назначены пользователям, которые принадлежат группе.
    // # Диапазон: 0..100
    repeated Claim claims = 4;
    // Требования, означающие принадлежность пользователя к данной группе.
    // Пользователь принадлежит группе, если все атрибуты требований группы присутствуют у пользователя и имеют те же значения.
    // # Диапазон: 0..10
    repeated Claim requirements = 5;
    // Дата удаления группы из использования.
    // Группа не участвует в определении разрешений пользователя, если дата deleted_at непустая и уже в прошлом
    google.protobuf.Timestamp deleted_at = 6;
    // Теги группы.
    // # Диапазон: 0..10
    repeated string tags = 7;
}

// Запрос получения группы
message GetGroupRequest {
    // Идентификатор группы.
    // # Тип: Guid
    string id = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос получения группы
message GetGroupResponse {
    // Ошибка запроса получения группы
    message Error {
        // Причина ошибки
        oneof reason {
            // Ошибка валидации
            ValidationError validation = 1;
        }
    }
    // Тип результата
    oneof type {
        // Группа
        Group data = 1;
        // Ошибка
        Error error = 2;
    }
}

// Запрос получения списка групп
message GetGroupListRequest {
    // Фильтр
    GroupFilter filter = 1;
    // Вариант разбиения на страницы
    oneof pagination {
        // Пагинация
        GroupPaging paging = 2;
    }
}
// Ответ на запрос получения списка групп
message GetGroupListResponse {
    // Ошибка запроса получения списка групп
    message Error {
        // Причина ошибки
        oneof reason {
            // Ошибка валидации
            ValidationError validation = 1;
        }
    }
    // Тип результата
    oneof type {
        // Группа
        Group data = 1;
        // Ошибка
        Error error = 2;
    }
}

// Запрос получения количества групп
message GetGroupCountRequest {
    // Фильтр
    GroupFilter filter = 1;
}
// Ответ на запрос получения количества групп
message GetGroupCountResponse {
    // Ошибка запроса получения количества групп
    message Error {
        // Причина ошибки
        oneof reason {
            // Ошибка валидации
            ValidationError validation = 1;
        }
    }
    // Тип результата
    oneof type {
        // Всего групп
        int32 data = 1;
        // Ошибка
        Error error = 2;
    }
}

// Фильтр групп
message GroupFilter {
    // По тексту.
    // Если значение не передано то поиск по нему не производится.
    // # Диапазон: 3..64.
    // # Поиск производится по полям:
    // # - Наименование
    string text = 1;
    // По requirements группы.
    // Возвращаем группы, у которых Group.requirements.key=GroupFilter.requirements[n].key И Group.requirements.value=GroupFilter.requirements[n].value.
    // При пустом GroupFilter.requirements[n].value возвращаем все группы, у которых Group.requirements.key=GroupFilter.requirements[n].key.
    // При передаче нескольких элементов map они работают в выборке через И
    map<string, string> requirements = 2;
    // По claims группы.
    // Возвращаем группы, у которых Group.claim.key=GroupFilter.claims[n].key И Group.claims.value=GroupFilter.claims[n].value.
    // При пустом GroupFilter.claims[n].value возвращаем все группы, у которых Group.claims.key=GroupFilter.claims[n].key.
    // При передаче нескольких элементов map они работают в выборке через И
    map<string, string> claims = 3;
    // По тегам.
    // # Диапазон: 0..10
    repeated string tags = 4;
    // Скрывать удалённые элементы.
    // Если значение — true, возвращаются только записи, у которых deleted_at = null или deleted_at > текущего времени.
    // Если значение — false или не задано, возвращаются все записи, включая удалённые
    google.protobuf.BoolValue hide_deleted = 5;
}

// Пагинация групп
message GroupPaging {
    // Справочник типов значений сортировки.
    // # Тип: byte
    enum OrderByType {
        // Значение не указано
        ORDER_BY_TYPE_UNKNOWN = 0;
        // По идентификатору
        ID = 1;
        // По наименованию
        TITLE = 2;
    }
    // Тип значения сортировки.
    // Если значение не передано, то будет взято значение по умолчанию.
    // # По умолчанию: ID
    OrderByType order_by_type = 1;
    // Справочник типов направлений сортировки.
    // # Тип: byte
    enum DirectionType {
        // Значение не указано
        DIRECTION_TYPE_UNKNOWN = 0;
        // От большего к меньшему
        DESC = 1;
        // От меньшего к большему
        ASC = 2;
    }
    // Тип направления сортировки.
    // # По умолчанию: DESC
    DirectionType direction_type = 2;
    // Количество записей на страницу.
    // Если значение 0 (не передано), то будет взято значение по умолчанию.
    // # Диапазон: 0..100.
    // # По умолчанию: 20
    int32 limit = 3;
    // Сдвиг.
    // # Диапазон: 0..2147483647
    int32 offset = 4;
}

// Запрос сохранения группы
message PostGroupRequest {
    // Группа
    Group data = 1 [(google.api.field_behavior) = REQUIRED];
}
// Запрос создания группы по шаблону
message PostGroupByTemplateRequest {
    // Идентификатор шаблона группы.
    // # Тип: Guid
    string group_template_id = 1 [(google.api.field_behavior) = REQUIRED];
    // Наименование создаваемой по шаблону группы.
    // В наименование группы будет добавлен суффикс GroupTemplate.group_title_suffix.
    // # Диапазон: 3..256
    string group_title = 2 [(google.api.field_behavior) = REQUIRED];
    // Специфичные для создаваемой по шаблону группы Claim requirements, они будут добавлены к requirements создаваемой по шаблону группы.
    // # Диапазон: 0..10
    repeated Claim group_requirements = 3;
}
// Ответ на запрос сохранения группы
message PostGroupResponse {
    // Ошибка запроса сохранения группы
    message Error {
        // Причина ошибки
        oneof reason {
            // Ошибка валидации
            ValidationError validation = 1;
        }
    }
    // Тип результата
    oneof type {
        // Группа
        Group data = 1;
        // Ошибка
        Error error = 2;
    }
}

// Ответ на запрос создания группы по шаблону
message PostGroupByTemplateResponse {
    // Ошибка запроса создания группы
    message Error {
        // Причина ошибки
        oneof reason {
            // Ошибка валидации
            ValidationError validation = 1;
            // Шаблон не найден
            GroupTemplateNotFoundError group_template_not_found_error = 2;
        }
    }
    // Тип результата
    oneof type {
        // Группа
        Group data = 1;
        // Ошибка
        Error error = 2;
    }
}

// Запрос удаления группы
message DeleteGroupRequest {
    // Идентификатор группы.
    // # Тип: Guid
    string id = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос удаления группы
message DeleteGroupResponse {}

// Запрос на добавление связи пользователя и группы
message PutGroupUserAttachRequest {
    // Связь пользователя и группы
    UserGroup user_group = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на добавление связи пользователя и группы
message PutGroupUserAttachResponse {}

// Запрос на удаление связи пользователя и группы
message PutGroupUserDetachRequest {
    // Связь пользователя и группы
    UserGroup user_group = 1 [(google.api.field_behavior) = REQUIRED];
}

// Запрос получения списка связей пользователей и групп
message GetGroupUserListRequest {
    // Фильтр связей пользователей и групп
    GroupUserFilter filter = 1;
    // Вариант разбиения на страницы
    oneof pagination {
        // Пагинация
        GroupUserPaging paging = 2;
    }
}

// Пагинация связей пользователей и групп
message GroupUserPaging {
    // Справочник типов сортировки
    enum OrderByType {
        // Значение не указано
        ORDER_BY_TYPE_UNKNOWN = 0;
        // По Идентификатору группы
        GROUP_ID = 1;
        // По Идентификатору пользователя
        USER_ID = 2;
    }
    // Тип значения сортировки.
    // По умолчанию: ID
    OrderByType order_by_type = 1;
    // Справочник типов направлений сортировки
    enum DirectionType {
        // Значение не указано
        DIRECTION_TYPE_UNKNOWN = 0;
        // От большего к меньшему
        DESC = 1;
        // От меньшего к большему
        ASC = 2;
    }
    // Тип направления сортировки.
    // По умолчанию: DESC
    DirectionType direction_type = 2;
    // Количество записей на страницу.
    // Минимальное значение: 1.
    // Максимальное значение: 100.
    // По умолчанию: 20.
    // Если значение 0 (не передано), то выставляем значение по умолчанию
    int32 limit = 3;
    // Сдвиг.
    // По умолчанию: 0
    int32 offset = 4;
}

// Фильтр для запроса связей пользователей и групп.
// При передаче массива в параметр фильтра элементы массива работают в выборке через ИЛИ.
// При передаче нескольких разных параметров фильтра они работают в выборке через И
message GroupUserFilter {
    // По Идентификаторам группы
    repeated string group_ids = 1;
    // По Идентификаторам пользователя
    repeated string user_ids = 2;
}

// Ответ на запрос получения списка связей пользователей и групп
message GetGroupUserListResponse {
    // Тип результата
    oneof type {
        // Связь пользователя и группы
        UserGroup data = 1;
    }
}

// Запрос получения количества связей пользователей и групп
message GetGroupUserCountRequest {
    // Фильтр связей пользователей и групп
    GroupUserFilter filter = 1;
}

// Ответ на запрос получения количества связей пользователей и групп
message GetGroupUserCountResponse {
    // Тип результата
    oneof type {
        // Всего связей пользователей и групп
        int32 data = 1;
    }
}

// Ответ на запрос удаления точки доступа в профиль
message PutGroupUserDetachResponse {}

// Запрос получения количества уникальных назначенных атрибутов пользователя
message GetGroupClaimCountRequest {
    // Фильтр атрибутов пользователя
    GroupClaimFilter filter = 1;
}
// Ответ на запрос получения количества уникальных назначенных атрибутов пользователя
message GetGroupClaimCountResponse {
    // Тип результата
    oneof type {
        // Всего уникальных атрибутов пользователя
        int32 data = 1;
    }
}

// Запрос проверки назначенных атрибутов пользователя
message GetGroupClaimExistRequest {
    // Фильтр атрибутов пользователя
    GroupClaimFilter filter = 1;
}
// Ответ на запрос проверки назначенных атрибутов пользователя
message GetGroupClaimExistResponse {
    // Ошибка запроса
    message Error {
        // Ошибка пустого фильтра по атрибутам пользователя
        message ClaimFilterEmptyError {}
        // Причина ошибки
        oneof reason {
            // Ошибка пустого фильтра по атрибутам пользователя
            ClaimFilterEmptyError claim_filter_empty = 1;
        }
    }
    // Тип результата
    oneof type {
        // Флаг наличия
        bool data = 1;
        // Ошибка
        Error error = 2;
    }
}

// Запрос уникального списка назначенных атрибутов пользователя
message GetGroupClaimListRequest {
    // Фильтр атрибутов пользователя
    GroupClaimFilter filter = 1;
    // Вариант разбиения на страницы
    oneof pagination {
        // Пагинация по атрибутам пользователя
        GroupClaimPaging paging = 2;
    }
}
// Ответ на запрос уникального списка назначенных атрибутов пользователя
message GetGroupClaimListResponse {
    // Тип результата
    oneof type {
        // Атрибут пользователя
        Claim data = 1;
    }
}

// Фильтр атрибутов пользователя
message GroupClaimFilter {
    // По идентификатору пользователя.
    // Соответствует subject, sub, client_id из JWT.
    // Для пользователя Ключа это user_id Ключа
    google.protobuf.StringValue user_id = 1;
    // По назначенным атрибутам пользователя.
    // Фильтр ограничивает возвращаемые атрибуты данным списком.
    // Возвращаем атрибуты групп, у которых Claim.key=claims[n].key И (Claim.value=claims[n].value ИЛИ claims[n].value пустой).
    // При пустом GroupClaimFilter.claims не ограничиваем выдачу, отдаем все Claims, назначенные пользователю
    map<string, string> claims = 2;
    // Условие принадлежности к группе
    message UserData {
        // Атрибуты пользователя, claims которого мы хотим получить
        map<string, string> claims = 1;
    }
    // По условию принадлежности к группе.
    // Пользователь принадлежит группе, если user_data полностью удовлетворяет требованиям группы, то есть user_data содержит в себе Group.requirements целиком
    UserData user_data = 3;
    // Хешированное SHA256 значение ключа доступа ApiKey
    string api_key_hash = 4;
}

// Пагинация атрибутов пользователя
message GroupClaimPaging {
    // Справочник типов значений сортировки.
    // # Тип: byte
    enum OrderByType {
        // Значение не указано
        ORDER_BY_TYPE_UNKNOWN = 0;
        // По ключу атрибута
        KEY = 1;
        // По значению атрибута
        VALUE = 2;
    }
    // Тип значения сортировки.
    // Если значение не передано, то будет взято значение по умолчанию.
    // # По умолчанию: KEY
    OrderByType order_by_type = 1;
    // Справочник типов направлений сортировки.
    // # Тип: byte
    enum DirectionType {
        // Значение не указано
        DIRECTION_TYPE_UNKNOWN = 0;
        // От большего к меньшему
        DESC = 1;
        // От меньшего к большему
        ASC = 2;
    }
    // Тип направления сортировки.
    // # По умолчанию: DESC
    DirectionType direction_type = 2;
    // Количество записей на страницу.
    // Если значение 0 (не передано), то будет взято значение по умолчанию.
    // # Диапазон: 0..100.
    // # По умолчанию: 20
    int32 limit = 3;
    // Сдвиг.
    // # Диапазон: 0..2147483647
    int32 offset = 4;
}

// Ошибки валидации.
// Эти проверки выполняются до обращения в базу данных
message ValidationError {
    // Путь к полю в формате наименования protobuf
    string path = 1 [(google.api.field_behavior) = REQUIRED];
    // Валидационное сообщение
    string message = 2 [(google.api.field_behavior) = REQUIRED];
}

// Ошибки создания группы по шаблону
message GroupTemplateNotFoundError {
    // Идентификатор шаблона группы, который не найден
    string group_template_id = 1 [(google.api.field_behavior) = REQUIRED];
    // Сообщение
    string message = 2 [(google.api.field_behavior) = REQUIRED];
}

