Разграничение доступа к GraphQL-операциям и данным#
Компонент DataSpace Core предоставляет функциональность разграничения доступа к GraphQL-операциям и данным.
Примечание
На протоколах, отличных от GraphQL (JSON-RPC, gRPC, REST), функциональность разграничения доступа не предоставляется.
Разграничение доступа к данным осуществляется при помощи следующих возможностей:
Ограничение выполняемых GraphQL-операций заданным списком (далее список разрешенных к выполнению операций). Каждый элемент списка фиксирует имя и структуру (тело) одной разрешенной к выполнению операции GraphQL. Структура (тело) операции ограничивает типы и атрибуты типов, с которыми осуществляется взаимодействие (изменение и выборка в рамках
mutation, выборка в рамках операцииquery). Как следствие, безымянные GraphQL-операции не разрешены к выполнению.Проверка доступа путем выполнения проверочных запросов (далее —
CheckSelects) перед выполнением GraphQL-операции. Операция будет выполнена, только если все указанные для нее проверочные запросы вернут непустой результат. Проверочный запрос можно использовать, например, для проверки прав и значений переменных перед операциями mutation.Ограничение доступа к определенным данным путем наложения дополнительных условий фильтрации через переменную (далее —
ParamAdditions). Дополнительное условие фильтрации связывается с переменной GraphQL-операции и применяется в месте использования указанной переменной в качестве значения атрибутаcond. Дополнительные условия фильтрации через переменную позволяют ограничить выбираемые данные вне зависимости от пользовательского (переданного в переменной) условия фильтрации.Ограничение доступа к определенным данным путем наложения дополнительных условий фильтрации по пути к полю (далее —
PathConditions). Дополнительное условие фильтрации связывается с полем, к которому применима фильтрация (имеет аргументcond), и накладывает дополнительные ограничения в выборке. Дополнительные условия фильтрации через путь к полю позволяют ограничить выбираемые данные вне зависимости от пользовательского (переданного в переменной или заданного явно) условия фильтрации при его наличии.Проверка доступа путем выполнения проверочных запросов непосредственно перед фиксацией внесенных операцией изменений в БД (before commit transaction).
Для того чтобы воспользоваться функциональностью разграничения доступа, необходимо:
Объявить порт, на котором будет применяться функциональность разграничения доступа при помощи настройки
dataspace.security.jwt-only.port. Значение данной настройки может как совпадать, так и не совпадать со значением настройкиserver.port. Если значения не совпадают, то автоматически поднимается дополнительный порт приложения. Данный порт позволяет принимать и использовать JWT при разграничении доступа к GraphQL-операциям.Сформировать список разрешенных к исполнению GraphQL-операций с CheckSelects, ParamAdditions и PathConditions (далее — конфигурация разграничения доступа). Структура элементов списка описана ниже.
Передать подготовленную на предыдущем шаге конфигурацию разграничения доступа в DataSpace Core (на этапе разработки — через специализированный API, на этапе сборки дистрибутива — через файл).
Способ передачи конфигурации определяется настройкой dataspace.security.permissions.source, принимающей одно из следующих значений:
db— в dataspace-core поднимается endpoint/security/permissionsдля управления конфигурацией (API приведено ниже).file— конфигурация разграничения доступа загружается из файла конфигурации.
Структура конфигурации разграничения доступа не зависит от значения настройки dataspace.security.permissions.source.
Перечень разрешенных GraphQL-операций описывается в формате JSON.
Примечание
Режим
dataspace.security.permissions.source=dbпредназначен для разработки и не рекомендован к использованию в промышленной эксплуатации.
Конфигурация разграничения доступа#
Конфигурация разграничения доступа состоит из описания разрешенных к выполнению GraphQL-операций с CheckSelects, ParamAdditions и PathConditions.
Пример описания разрешенной GraphQL-операции с проверкой CheckSelects:
[
{
"name": "createProduct",
"body": "mutation createProduct($someCode: String, $level: Int) { packet { createProduct(input: {id:\"prod1\", code: $someCode, level: $level}) { id } }}",
"allowEmptyChecks": false,
"disableJwtVerification": false,
"checkSelects": [
{
"typeName": "SubjectPrivileges",
"conditionValue": "${Integer:level} < 10 && it.personnelNumber==${jwt:employeeID} && it.privilege=='createProduct'",
"description": "Only a user with the appropriate permission can make this request.",
"orderValue": "0",
"beforeOperationDisable": "false",
"beforeCommitEnable": "false"
}
],
"paramAdditions": []
}
]
Пример описания разрешенной GraphQL-операции с ограничением ParamAdditions:
[
{
"name": "searchProduct",
"body": "query searchProduct($sort: [_SortCriterionSpecification!], $limit: Int, $offset: Int, $searchCond: String) { searchProduct(cond: $searchCond, limit: $limit, offset: $offset, sort: $sort) { count elems { code } }}",
"allowEmptyChecks": true,
"disableJwtVerification": false,
"checkSelects": [],
"paramAdditions": [
{
"paramName": "searchCond",
"paramAddition": "it.level < 10 && entities{type=SubjectPrivileges, cond=it.personnelnumber==${jwt:employeeID} && it.privilege=='searchProduct'}.$exists"
}
]
}
]
Пример описания разрешенной GraphQL-операции с ограничением PathConditions:
[
{
"name": "searchProduct",
"body": "query searchProduct($sort: [_SortCriterionSpecification!], $limit: Int, $offset: Int, $searchCond: String) { searchProduct(cond: $searchCond, limit: $limit, offset: $offset, sort: $sort) { count elems { code } }}",
"allowEmptyChecks": true,
"disableJwtVerification": false,
"checkSelects": [],
"pathConditions": [
{
"path": "searchProduct",
"cond": "it.level < 10 && entities{type=SubjectPrivileges, cond=it.personnelnumber==${jwt:employeeID} && it.privilege=='searchProduct'}.$exists"
}
]
}
]
Элементы описания разрешенной GraphQL-операций:
name— имя операции GraphQL (название, которое идет послеqueryилиmutation, обязательный атрибут);body— текст GraphQL-операции (используется для сверки без учета пробельных символов и переносов строк, обязательный атрибут);allowEmptyChecks— тип boolean — указывает явно, что для данной операции ограничения (CheckSelects) могут быть не заданы, в противном случае не позволяет выполнить операцию, пока не будут указаны проверочные условияCheckSelects(значение по умолчанию — «false», необязательный атрибут). Если коллекцияCheckSelectпустая, то данный атрибут должен быть выставлен в «true»;disableJwtVerification— признак разрешения анонимного доступа (не требующего аутентификации и авторизации пользователя) (значение по умолчанию —false, необязательный атрибут);checkSelects— коллекция проверок, которые выполняются перед выполнением операции (необязательный атрибут);typeName— базовый тип, на котором строится запрос к БД. Если поле отсутствует, то в качестве базового типа используется предопределенный системный типSysRootSecurity(необязательный атрибут);conditionValue— ограничивающее выборку условие (обязательный атрибут);description— описание ограничения (необязательный атрибут);orderValue— вспомогательное поле, позволяющее упорядочить и избежать перемешиванияCheckSelectsпри выводе на интерфейсе (необязательный атрибут);beforeOperationDisable— «true» отключает выполнение проверки до запроса, когда необходимо выполнить проверку только перед commit в БД (значение по умолчанию —false, необязательный атрибут);beforeCommitEnable— «true» включает выполнение проверки непосредственно перед фиксацией внесенных операцией изменений в БД (before commit transaction) (значение по умолчанию —false, необязательный атрибут);
paramAdditions— дополнительно накладываемые условия фильтрации при использовании соответствующих переменных (данный подход устарел с появлениемpathConditions) в качестве значенийcondусловий (необязательный атрибут):paramName— имя параметра (обязательный атрибут);paramAddition— условие, добавляемое в месте использования переменной в качестве значенияcond(обязательный атрибут);
pathConditions— дополнительно накладываемые условия фильтрации по указанным путям в запросе (необязательный атрибут):path— путь в GraphQL-запросе (обязательный атрибут);cond— условие, накладываемое по указанному пути (обязательный атрибут).
Примечание
По умолчанию запросы интроспекции схемы не подлежат обработке безопасностью, т.е. разрешены. Безопасность на запросах интроспекции можно включить с помощью настройки
dataspace.security.graphql.introspection.asoperation=true, после чего следует объявить конфигурацию безопасности для таких запросов (аналогично любым другим запросам).Также имеется возможность указать единственную проверку, работающую аналогично CheckSelect и выполняемую перед запросами интроспекции. Для этого необходимо указать в качестве значения настройки
dataspace.security.graphql.introspection.query.check-conditionстроковое выражение желаемой проверки. Данная проверка будет выполняться только на запросах интроспекции, т.е. в запросах, содержащих исключительно поля получения сведений о структуре схемы GraphQL (__schema,__type).
Примечание
ParamAdditionsявляются устаревшим подходом к наложению дополнительных ограничений выборки данных. Им на замену были введеныPathConditions, с помощью которых можно указать дополнительное условие для конкретного поля из GraphQL запроса.
Примечание
Дополнительные условия
ParamAdditions,PathConditions, а такжеCheckSelectsприменимы как кquery-операциям, так и кmutation-операциям. Но выборку данных вmutation-операциях необходимо использовать с осторожностью. Ограничение выбираемых данных вmutation-операциях рекомендуется осуществлять только за счет фиксирования структуры операции.Каждая проверка
CheckSelectsприводит к дополнительному обращению к БД. Дляquery-операций вместо проверокCheckSelectsрекомендуется рассмотреть использование дополнительных условийPathConditionsилиParamAdditions, применяемых при выполнении переданной операции, если это отвечает требованиям безопасности. В большинстве случаевCheckSelectsможет быть преобразован кPathConditionsиParamAdditions.Провал проверки
CheckSelectsприводит к возврату сообщения об ошибке. Дополнительное ограничениеPathConditionsилиParamAdditionsне приводит к возвращению ошибки, но влияет на объем выбираемых данных (вплоть до пустого результата).
Путь в PathConditions является полным путем от корня запроса (не включая имя запроса) до требуемого поля, также используются псевдонимы для поля при наличии.
Если в запросе используется запрос со слиянием через merge-поле, то в качестве узла в пути используется имя соответствующего интерфейса, указанного во встроенном фрагменте.
Пример запроса и путей PathConditions:
query pathConditionsExampleQuery {
searchProduct {
elems {
id
servicesAlias: services(cond: "it.code == 'goodCode'") {
elems {
id
name
}
}
}
}
merge {
elems {
... on Product @mergeReqSpec(cond: "it.code $like 'product%'") {
code
services {
elems {
id
name
}
}
}
... on Document {
name
}
}
}
}
В примере выше пути:
searchProduct— путь к выбираемым продуктам;searchProduct.elems.servicesAlias— путь к выбираемым услугам у выбранных продуктов (используется псевдоним для поляservices);merge.elems.Product— путь к выбираемым продуктам в запросе слиянияmerge;merge.elems.Product.services— путь к выбираемым услугам у выбранных продуктов в запросе слиянияmerge;merge.elems.Document— путь к выбираемым документам в запросе слиянияmerge.
Получение конфигурации разграничения доступа из файла#
Получение конфигурации разграничения доступа из файла включается настройкой dataspace.security.permissions.source=file.
Конфигурация разграничения доступа загружается из файла по пути, указанном в настройке dataspace.security.permissions.path относительно classPath DataSpace Core. Значение по умолчанию — securityConfig\graphql-operations.json.
Для того чтобы файл попал в class path DataSpace Core, необходимо разместить файл security-operations.json в папке securityConfig рядом с файлом model.xml в проекте сборки DataSpace, тогда плагин генерации модели при сборке поместит файл в состав артефакта DataSpace Core.

Пример содержимого файла:
[
{
"name": "searchProduct",
"body": "query searchProduct($sort: [_SortCriterionSpecification!], $limit: Int, $offset: Int, $searchCond: String) { searchProduct(cond: $searchCond, limit: $limit, offset: $offset, sort: $sort) { count elems { code services { elems { code }} } }}",
"paramAdditions": [
{
"paramName": "searchCond",
"paramAddition": "it.level < 10 && entities{type=SubjectPrivileges, cond=it.personnelNumber==${jwt:employeeID} && it.privilege=='searchProduct'}.$exists"
}
],
"pathConditions": [
{
"path": "searchProduct.elems.services",
"cond": "it.code $like '%target'"
}
],
"checkSelects": [],
"allowEmptyChecks": true
},
{
"name": "createProduct",
"body": "mutation createProduct($someCode: String, $level: Int) { packet { createProduct(input: {id:\"prod1\", code: $someCode, level: $level}) { id } }}",
"checkSelects": [
{
"typeName": "SubjectPrivileges",
"conditionValue": "${Integer:level} < 10 && it.personnelNumber==${jwt:employeeID} && it.privilege=='createProduct'",
"description": "Only a user with the appropriate permission can make this request.",
"orderValue": "0"
}
]
}
]
Получение конфигурации разграничения доступа из БД и ее загрузка через REST#
Включение загрузки разрешенных GraphQL-операций в БД через REST осуществляется следующими настройками:
dataspace.security.permissions.source=db— поднимает необходимые REST-контроллеры и устанавливает источником конфигурации БД.server.port.security-api-access=true— разрешает на порту, заданном настройкойserver.port, принятие запросов управления конфигурацией.
Внимание!
Порт
dataspace.security.jwt-only.portсчитается публично доступным. В случае равенства значений настроекdataspace.security.jwt-only.portиserver.portвызов REST API управления конфигурацией запрещается вне зависимости от любых других настроек. Для использования REST API управления конфигурацией необходимо, чтобы значения указанных настроек отличались.
Управление конфигурацией разграничения доступа в БД осуществляется через следующий REST API:
HTTP-метод |
Путь |
Параметры |
Описание |
|---|---|---|---|
POST |
/security/permissions/operations-bulk/replaceAll |
В теле запроса передается JSON-конфигурация разграничения доступа |
Удаление всех текущих разрешенных GraphQL-операций с последующим созданием новых операций |
POST |
/security/permissions/operations-bulk/replace |
В теле запроса передается JSON-конфигурация разграничения доступа |
Добавление новых разрешенных GraphQL-операций с заменой при пересечении по имени |
POST |
/security/permissions/operations-bulk/deleteAll |
— |
Удаление из БД всех разрешенных GraphQL-операций |
POST |
/security/permissions/operations-bulk/merge |
В теле запроса JSON [{name : String, body : String}] |
Создание в БД тел новых разрешенных GraphQL-операций по имени или обновление тел существующих операций |
POST |
/security/permissions/operations-bulk/create |
В теле запроса передается JSON-разграничения доступа |
Добавление в БД коллекции новых разрешенных GraphQL-операций. В случае конфликта по имени выбрасывается исключение |
POST |
/security/permissions/operations |
В теле запроса передается JSON одной операции из конфигурации разграничения доступа |
Добавление в БД одной новой разрешенной GraphQL-операции. В случае конфликта по имени выбрасывается исключение |
GET |
/security/permissions/operations |
name : String — шаблон имен операций (используется поиск по like), page: Integer, pageSize: Integer |
Получение списка разрешенных GraphQL-операций |
PUT |
/security/permissions/operations/{operationName} |
operationName : String — имя замещаемой операции, тело — конфигурация замещаемой операции (без имени) |
Замещение разрешенной GraphQL-операции по имени |
DELETE |
/security/permissions/operations/{operationName} |
operationName : String — имя удаляемой операции |
Удаление разрешенной GraphQL-операции по имени |
Все полученные для сохранения конфигурации разграничения доступа проходят следующую валидацию:
Корректность тела GraphQL-запроса.
CheckSelects— корректность условия.ParamAdditions— корректность условия.PathConditions— корректность условия.Отсутствие
PathConditionsс одинаковым путем.Отсутствие
ParamAdditionsс одинаковым путем.Отсутствие
PathConditionsс некорректным путем.Отсутствие
ParamAdditionsна необъявленные переменные.Отсутствие неиспользуемых
PathConditions.Отсутствие
PathConditionsна поля, к которым неприменима фильтрация, т.е. не имеющие аргументаcond(дляmerge-запросов фильтрация всегда применима за счет наличия директивы@mergeReqSpec).Отсутствие
ParamAdditionsна любые переменные, используемые в качестве аргумента, отличного отcond(при этом разрешается использованиеParamAdditionна таких переменных, если есть хотя бы одно использование в качествеcond).
Ограничения на длины полей конфигурации при размещении в БД#
Когда конфигурация разграничения доступа размещается в БД (dataspace.security.permissions.source=db) действуют следующие ограничения на длины полей:
name— 254 символа;body— не ограничен;checkSelects.typeName— 254 символа;checkSelects.conditionValue— не ограничен;checkSelects.description— 254 символа;paramAdditions.paramName— 254 символа;paramAdditions.paramAddition— 4000 символов;pathConditions.path— не ограничен;pathConditions.cond— не ограничен.
Передача и верификация JWT при обращении на порт с разграничением доступа#
При вызове GraphQL-операции через порт dataspace.security.jwt-only.port передача JWT (JSON Web Token) осуществляется в HTTP-заголовке Authorization (тип Bearer).
Данные, передаваемые в JWT, могут быть использованы в CheckSelects (поле conditionValue), ParamAdditions (поле paramAddition) и PathConditions (поле cond).
Передача JWT при обращении на порт dataspace.security.jwt-only.port не является обязательной. Необходимость наличия JWT для
конкретной GraphQL-операции определяется значением атрибута disableJwtVerification конфигурации разграничения доступа:
Если значение —
disableJwtVerification=true, то GraphQL-операция может быть выполнена без наличия JWT в HTTP-запросе. При этом ограничивающие и проверочные условия либо должны отсутствовать, либо в них не должно быть подстановок значений из JWT. В противном случае возникнет ошибка в runtime.Если значение —
disableJwtVerification=false, то GraphQL операция не может быть выполнена без наличия JWT в HTTP-запросе вне зависимости от наличия или отсутствия ограничивающих и проверочных условий.
Для валидации JWT необходимо указать способ получения JWKS (JSON Web Key). Для этого необходимо задать значение настройки dataspace.security.jwks.source. Допустимые значения: url, file, property, db.
В случае значения, отличного от db, необходимо предоставить дополнительную информацию в настройке dataspace.security.jwks.value, соответствующую значению dataspace.security.jwks.source.
Значение |
Значение |
Пример значения |
|---|---|---|
|
URL, по которому будет запрошен JWKS |
|
|
Путь к файлу, в котором расположен JWKS, в classPath DataSpace Core. По умолчанию используется приведенное в примере значение. Для того чтобы файл попал в class path DataSpace Core, необходимо разместить файл jwks.json в папке securityConfig рядом с файлом model.xml в проекте сборки DataSpace, тогда плагин генерации модели при сборке поместит файл в состав артефакта DataSpace Core |
|
|
Строковое представление JWKS |
{ «keys»: […]} |
|
JWKS заносится в БД через endpoint |
Значение отсутствует |
Примечание
Endpoint
/security/jwksнедоступен при значенияхdataspace.security.jwks.sourceотличных отdb. Данный endpoint доступен на портуserver.port. Данный endpoint недоступен на портуdataspace.security.jwt-only.port, как следствие он не будет доступен при равенстве значений настроекdataspace.security.jwt-only.portиserver.port.
По умолчанию DataSpace Core не запустится, если указан dataspace.security.jwt-only.port, но не указан dataspace.security.jwks.source.
Для явного отключения необходимости верификации JWT (например, при проведении локальных тестов) необходимо установить значение настройки dataspace.security.jwt.validation.disable=true.
При данном значении настройки отключается любая верификация JWT по JWKS (в том числе, когда JWKS задан).
Все JWT признаются валидными.
Внимание!
На промышленных и тестовых стендах не рекомендуется выставлять значение
dataspace.security.jwt.validation.disable=true.
При верификации JWT проверяются поля exp (время истечения срока действия) и nbf (время, до которого JWT не должен обрабатываться).
В случае рассинхронизации времени между разными модулями, использующими один и тот же JWT, можно воспользоваться следующими настройками:
dataspace.security.jwt.expDelta— задает количество секунд, в течение которых JWT еще считается действующим после истечения времениexp.dataspace.security.jwt.nbfDelta— задает количество секунд, в течение которых JWT уже считается действующим до наступления времениnbf.
Вышеуказанные настройки позволяют снизить вероятность наступления ситуации, когда один модуль считает токен еще действующим, а другой уже просроченным.
При верификации JWT дополнительно могут быть проверены следующие поля (если заданы соответствующие настройки):
aud(идентификатор получателя, для которого предназначен JWT) (настройкаdataspace.security.jwt.aud);iss(идентификатор выдавшей стороны) (настройкаdataspace.security.jwt.iss).
Примечание
Если
dataspace.security.jwks.source=db, то значения для настроекdataspace.security.jwt.expDelta,dataspace.security.jwt.nbfDelta,dataspace.security.jwt.aud,dataspace.security.jwt.issберутся так же из БД. Процесс сохранения значений настроек в БД описан ниже.
Сигнатура API установки JWKS в БД#
В случае использования БД в качестве источника JWKS (настройка dataspace.security.jwks.source=db) управление JWKS осуществляется через endpoint /security/jwks.
Для работы указанного endpoint необходимо наличие настройки server.port.security-api-access=true, также значение настройки server.port не должно совпадать со значением настройки dataspace.security.jwt-only.port.
Данный endpoint предоставляет следующий API:
HTTP-метод |
Путь |
Параметры |
Описание |
|---|---|---|---|
POST |
/security/jwks |
В теле запроса JWKS |
Загрузка в БД JSON Web Key Sets |
GET |
/security/jwks |
— |
Получение сохраненного в БД JSON Web Key Sets |
DELETE |
/security/jwks |
— |
Удаление из БД сохраненного JSON Web Key Sets |
Сигнатура API изменения настроек валидации JWT в БД#
В случае использования БД в качестве источника JWKS (при настройках dataspace.security.jwks.source=db, server.port.security-api-access=true, а также, если значение настройки server.port не совпадает со значением настройки dataspace.security.jwt-only.port) становятся доступны следующие endpoint:
HTTP-метод |
Путь |
Параметры |
Описание |
|---|---|---|---|
POST |
/security/jwt/check/config |
{«iss»:» |
Сохранение параметров в БД. При вызове может быть передан любой набор из приведенных параметров. Если в качестве значения параметра указывается пустая строка, то значение параметра в БД обнуляется (выставляется «null») |
Примечание
Данный API предназначен для использования только при разработке и не должен использоваться в промышленной эксплуатации. При вызове API изменяются настройки только того экземпляра dataspace-core, который обработал вызов. В случае наличия нескольких реплик dataspace-core, работающих с одной схемой БД, для применения измененных параметров остальные экземпляры dataspace-core необходимо перезагрузить вручную.
Использование подстановки переменных в ограничивающих строковых условиях#
Подстановка значений из JWT#
Проверки и ограничения разрешенных GraphQL-операций включают строковые выражения DataSpace (поле conditionValue в CheckSelects, поле paramAddition в ParamAdditions и поле cond в PathCondition).
В указанных строковых выражениях могут использоваться подстановки значений claim из JWT, переданного в заголовке Authorization запроса
на порт dataspace.security.jwt-only.port. Также выполняется подстановка переменных GraphQL.
Примечание
Подстановки значений
claimиз JWT работают только в строковых выраженияCheckSelects(полеconditionValue),ParamAdditions(полеparamAddition) иPathConditions(полеcond). В строковых выражениях внутри GraphQL-операции подстановка значений из JWT не производится.Подставлять можно только примитивные значения и массивы примитивных значений.
Подстановка значения из JWT в общем случае имеет следующий вид: ${String[]:jwt:userdata.roles}, где:
String[]— тип значения из JWT,[]указывает на то, что значение является массивом примитивов, а не одиночным примитивом;jwt— указывает на получение значения переменной из JWT;userdata.roles— путь относительно полезной нагрузки JWT. Результатом пути может быть только примитивный элемент или коллекция примитивных элементов. В пути не допускаются промежуточные коллекционные объекты.
Пример строкового выражения с подстановкой значения из JWT: "'adminRole' $in ${String[]:jwt:userdata.roles}".
Если значение переменной String, то тип может быть опущен: "'adminRole'==${jwt:userdata.mainRole}". Это аналогично "'adminRole'==${String:jwt:userdata.mainRole}". Тип String[] может быть сокращен до [].
Подстановка значения из переменных GraphQL-запроса#
Пример строкового выражения с подстановкой значения из GraphQL-переменной: "${String:productCode} $like '%somePostfix'".
Тип String может быть также опущен, как и при подстановке из JWT.
В некоторых операциях, например, createMany, есть возможность передать в GraphQL-переменной массив объектов. Этот массив тоже можно использовать в строковых условиях. В этом случае строковое выражение с подстановкой массива из N объектов превращается в N условий с подстановкой примитивов, результаты проверки которых объединяются через логическую операцию И.
При подстановке переменной-массива следует учесть:
в одном строковом выражении можно использовать не более одного массива объектов;
при этом один и тот же массив объектов можно использовать несколько раз;
результатом вычисления пути должен быть примитив.
Например, если для нижеприведенного запроса задать строковое выражение ${input.code}==${input.name}, это приведет к тому, что для каждого элемента в массиве input будет проверяться условие на равенство полей code и name:
mutation manyOnTopLevelSecurity(${'$'}input:[_CreateProductPartyInput!]!) {
packet {
cp: createManyProductParty(input: ${'$'}input)
}
}
Алгоритм разграничения доступа к данным#
Алгоритм разграничения доступа к данным осуществляется следующим образом:
Проверяется наличие заголовка
Authorization(типBearer) с токеном доступа (JWT) во входящем запросе.Если
dataspace.security.jwt.validation.disable=true, то JWT не проверяется.Если заголовок с JWT обнаружен и
dataspace.security.jwt.validation.disable=false, то выполняется валидация JWT при помощи JWKS. Проверяется подпись JWT и время действия токена.
Определяется имя GraphQL-операции.
В конфигурации разграничения доступа ищется запись по имени операции. Если запись не найдена, то возвращается информация об отсутствии операции в перечне разрешенных к исполнению. В противном случае выбирается найденная запись.
Проверяется совпадение hash переданной операции с hash эталонной операции (из найденной записи конфигурации). Если значения hash не совпадают, то возвращается соответствующая ошибка.
Выполняются
CheckSelect. Если всеCheckSelectвернули непустой результат, то выполнение операции продолжается. В противном случае возвращается ошибка с информацией о том, что операция не прошла проверку безопасности.Проверяется наличие условий
ParamAddition. При их наличии определяется место их применения по месту использованию соответствующих переменных в качестве значенийcondпараметров. При выполнении операции дополнительно накладываются условияParamAddition,PathConditionк переданным через переменную условиям фильтрации данных. Условия добавляются с использованием оператора&&следующим образом:(userCond) && (additional cond).Проверяется наличие условий
PathCondition. При их наличии определяется место их применения по явно указанному путиpath. При выполнении операции дополнительно накладываются условияPathConditionвне зависимости от того, чем являетсяcondпо данному пути: явно заданным условием, переменной или вообще отсутствует. Условия добавляются с использованием оператора&&следующим образом:(userCond) && (additional cond).При наличии
ParamAdditionиPathConditionодновременно, условия добавляются с использованием оператора&&следующим образом:(userCond) && (additional paramAddition cond) && (additional pathCondition cond).
Блок-схема алгоритма:

Взаимодействие backend-to-backend#
Взаимодействие backend-to-backend, когда оба backend находятся в защищенном контуре и являются доверенными, может быть осуществлено без разграничения доступа к операциям и данным, но без доступа к управлению конфигурацией разграничения доступа, когда конфигурация берется из БД.
Для осуществления такого взаимодействия поднимается дополнительный порт, путем задания значения настройки dataspace.security.unsecure.port.
Значение указанной настройки может как совпадать, так и не совпадать со значением настройки server.port.
Примечание
При совпадении значений настроек
dataspace.security.unsecure.portиserver.portвозможность управления конфигурацией разграничения доступа и JWKS отключается.
Если значения настроек dataspace.security.unsecure.port и server.port совпадают, то в приложении поднимается указанный порт.
Внимание!
На порту
dataspace.security.unsecure.portне применяются правила разграничения доступа к GraphQL-операциям и данным. Разграничение же доступа в рамках поддержки мультитенантности применяется.
Настройки функциональности разграничения доступа к GraphQL-операциям и данным#
Настройка |
Допустимые значения |
Описание |
|---|---|---|
|
file, db |
Указывает источник, из которого берется конфигурация разграничения доступа |
|
Имя файла или путь до файла относительно classPath dataspace-core |
Содержит путь к файлу с конфигурацией разграничения доступа относительно classPath dataspace-core |
|
url, file, property, db |
Указывает источник JWKS |
|
Зависит от настройки |
В зависимости от настройки |
|
Номер свободного порта, например, 7575 |
Задает номер порта, на котором действует разграничение доступа. Значение может как совпадать, так и не совпадать со значением настройки |
|
true или false (по умолчанию — false) |
Доступны ли REST API по загрузке конфигурации разграничения доступа и JWKS, если |
|
true или false (по умолчанию — false) |
Определяет отключение валидации JWT. При значении «true» никакой проверки JWT не осуществляется, JWT автоматически признается валидным |
|
Номер свободного порта, например, 7070 |
Задает номер порта, на котором не действует разграничение доступа к GraphQL операциям и данным, предназначенный для backend-to-backend взаимодействия. Значение может как совпадать, так и не совпадать со значением настройки |
Субъекты доступа#
Определены три категории потребителей:
Администратор системы (Admin) — пользователь, определяющий политики и правила безопасности в рамках системы разграничения прав. Основные задачи:
Управление внешней по отношению к DataSpace системой аутентификации и управления доступом, далее — IAM.
Конфигурирование системы разграничения доступа на стороне DataSpace (в режиме разработки/тестирования/отладки приложения):
Загрузка JWKS для проверки корректности JWT.
Формирование правил проверки GraphQL-операций на основании содержимого JWT и бизнес-правил конкретной реализации системы DataSpace.
Пользователь системы (User) — потребитель основных функций DataSpace посредством GraphQL операций. Основные задачи:
Аутентификация в IAM для получения JWT и дальнейшего использования в обращении к DataSpace.
Передача GraphQL операций в систему DataSpace с использованием JWT, при успешной аутентификации в IAM.
Backend-приложение (Backend App). Приложения, имеющие доступ к данным DataSpace на чтение/запись посредством GraphQL/Json RPC/gRPC API без дополнительного контроля доступа в DataSpace.
Объекты доступа#
В качестве объектов доступа выступают сами GraphQL-операции, формируемые потребителями, и данные. Администратор фиксирует набор возможных GraphQL-операций, определяя для каждой из них:
набор проверочных запросов с учетом содержимого JWT исходной GraphQL-операции;
условия дополнительной фильтрации набора данных, возвращаемых исходной GraphQL-операцией с учетом содержимого JWT.
Пример построения системы с разграничением доступа к GraphQL операциям и данным#
Вариант схемы работы системы с разграничением прав доступа:

На схеме:
server.port— определяет номер порта для работы пользователя с ролью «Администратор системы». Доступные точки доступа (endpoint’ы): /packet, /search, /graphql, /dictionaries, /security.dataspace.security.jwt-only.port— определяет номер порта для работы пользователя с ролью «Пользователь системы». Доступные endpoint’ы: /graphql. При обработке GraphQL-запросов применяется механизм проверки и применения политик разграничения прав.dataspace.security.unsecure.port— определят номер порта для работы пользователя с ролью «Backend-приложение». Доступные endpoint’ы: /packet, /search, /graphql, /dictionaries.
Пример разграничения доступа к GraphQL-операциям и данным#
Для иллюстрации правил и возможностей функциональности разграничения доступа рассмотрим ее использование на примере «Система учета заказов пользователями определенных видов товаров» (далее — «Система»).
Модель данных «Системы» и граф статусов для сущности «Заказ» приведены по ссылке model.xml.
Требования к разграничению доступа#
Примечание
Роли пользователей назначаются в системе аутентификации и управления доступом пользователей (IAM), у которой пользователь получает JWT с его ролью и идентификационными данными.
Для пользователей предусмотрены роли:
менеджер (роль manager в IAM);
клиент (роль customer в IAM);
супервайзер (роль supervisor в IAM).
Привилегии в «Системе» должны быть назначены в соответствии с таблицей:
Привилегия |
Менеджер |
Клиент |
Супервайзер |
|---|---|---|---|
Просматривать список товаров |
Разрешено |
Разрешено |
Разрешено |
Создавать/изменять типы товаров (GoodType) |
Разрешено |
— |
— |
Создавать заказ (Order) |
— |
Разрешено |
— |
Просматривать список заказов (Order) |
все заказы в статусе „FIXED“ |
Только свои заказы в любом статусе (включая детали заказа) |
все заказы в любом статусе |
Наполнять заказ деталями (Detail), определяющими список товаров в рамках заказа |
— |
Только «открытые» заказы (в статусе „DRAFT“) |
— |
Иметь только один «открытый» заказ (в статусе „DRAFT“) |
— |
Разрешено |
— |
Удалять детали только своего «открытого» заказа |
— |
Разрешено |
— |
Переводить свои заказы в статус „FIXED“ |
— |
Разрешено |
— |
Определять свои персональные данные (CustomerPersonalData) |
— |
Разрешено |
— |
Просматривать персональные данные клиентов |
— |
— |
Разрешено |
В этом примере в качестве идентификатора пользователя (Customer.id) используется его e-mail.
GraphQL-запросы#
Для реализации прикладной логики используются GraphQL-запросы, приведенные по ссылке (requests.graphql):
Перечень GraphQL-запросов:
Поименованный GraphQL-запрос |
Описание |
|---|---|
query getCustomerInfo($cond: String!) |
Получить имя и адрес клиента |
mutation addCustomerInfo($customerInput: _CreateCustomerInput!) |
Создать клиента |
query searchGoodType |
Получить список типов товаров |
mutation addGoodTypeInfo($goodTypeInput: _CreateGoodTypeInput!) |
Добавить тип товара |
query searchOrder($cond: String) |
Получить список заказов |
query searchAllOrder($cond: String) |
Получить список всех заказов |
mutation addOrderDetail($customerId: String!, $goodTypeId: String!) |
Добавить товар в заказ |
mutation fixOrder($orderId: ID!) |
Перевести заказ в статус FIXED |
mutation deleteDetail($detailId: ID!) |
Удалить товар из заказа |
Разрешения#
Разрешения для GraphQL-запросов, реализующие требования по разграничению доступа, представлены в файле: (permissions.json). Наиболее значимые из них:
addCustomerInfo: добавить/изменить информацию о пользователе может только сам пользователь, за это отвечает проверка checkSelects на равенство передаваемого идентификатора
${customerInput.id}полю JWT${jwt:email}.{ "name": "addCustomerInfo", "body": "mutation addCustomerInfo($customerInput: _CreateCustomerInput!) {\n packet {\n updateOrCreateCustomer(input: $customerInput) {\n returning {\n ...CustomerAttributes\n }\n }\n }\n}\n fragment CustomerAttributes on _E_Customer {\n id\n __typename\n data {\n firstName\n lastName\n }\n defaultDeliveryAddress {\n ...AddressAttributes\n }\n}\n fragment AddressAttributes on _G_DeliveryAddress {\n countryIso\n regionIso\n city\n street\n flatNumber\n}", "allowEmptyChecks": false, "disableJwtVerification": false, "checkSelects": [ { "conditionValue": "${customerInput.id} == ${jwt:email}", "orderValue": "0", "typeName": "", "description": "Only user can define his own personal data" } ] }deleteOrderDetail: удалить деталь имеет право только сам собственник заказа, если заказ находится в статусе „DRAFT“. Следует обратить внимание на то, что в данном ограничении для поиска также указана сущность Detail, так как для проверки необходимо обратиться непосредственно к уже хранящимся в DataSpace данным:
найти деталь, которую хочет удалить пользователь
it.$id == ${detailId};проверить, что владельцем заказа является пользователь, инициировавший запрос:
it.order.customer.entityId == ${jwt:email};убедиться, что найденный заказ находится в статусе „DRAFT“:
it.order.statusForCUSTOMER == 'DRAFT'.
{ "name": "deleteOrderDetail", "body": "mutation deleteOrderDetail($orderDetailId: ID!) {\n packet {\n deleteOrderDetail(id: $orderDetailId)\n }\n}", "allowEmptyChecks": false, "disableJwtVerification": false, "checkSelects": [ { "conditionValue": "it.$id == ${orderDetailId} && it.order.customer.entityId == ${jwt:email} && it.order.statusForVendor.code == 'DRAFT'", "orderValue": "0", "typeName": "OrderDetail", "description": "Only owner can delete detail of his order AND only when status of this order is DRAFT" } ] }searchAllOrder: используется для отображения всех заказов для пользователей с ролями manager и supervisor. В отличие от supervisor, пользователи с ролью manager могут просматривать заказы только в состоянии „FIXED“. Данное ограничение реализуется дополнительным условием фильтрации paramAdditions :
cond: 'supervisor' $in ${[]:jwt:realm_access.roles} || ('manager' $in ${[]:jwt:realm_access.roles} && it.statusForCUSTOMER.code == 'FIXED' ).{ "name": "searchAllOrder", "body": "query searchAllOrder($cond: String) {\n searchOrder(cond: $cond, sort: {crit: \"it.customer.entityId\", order: DESC}) {\n elems {\n customer {\n entityId\n }\n ...OrderAttributes\n }\n }\n}\n fragment OrderAttributes on _E_Order {\n id\n __typename\n openOrderFlag\n orderDate\n comment\n statusForVendor {\n code\n }\n orderDetailList {\n elems {\n ...OrderDetailAttributes\n }\n }\n}\n fragment OrderDetailAttributes on _E_OrderDetail {\n id\n __typename\n goodType {\n entity {\n ...GoodTypeAttributes\n }\n }\n}\n fragment GoodTypeAttributes on _E_GoodType {\n id\n __typename\n name\n descr\n price\n vendor {\n entity {\n id\n __typename\n name\n }\n }\n}", "allowEmptyChecks": false, "disableJwtVerification": false, "checkSelects": [ { "conditionValue": "'supervisor' $in ${[]:jwt:realm_access.roles} || 'manager' $in ${[]:jwt:realm_access.roles}", "orderValue": "0", "typeName": "", "description": "Only manager or supervisor can see all orders" } ], "paramAdditions": [ { "paramName": "cond", "paramAddition": "'supervisor' $in ${[]:jwt:realm_access.roles} || ('manager' $in ${[]:jwt:realm_access.roles} && it.statusForVendor.code == 'FIXED' )" } ] }searchGoodType: список товаров разрешен для просмотра всем пользователям. Установлен флаг Разрешить без проверок (
allowEmptyChecks).{ "name": "searchGoodType", "body": "query searchGoodType {\n searchGoodType {\n elems {\n ...GoodTypeAttributes\n }\n }\n}\n fragment GoodTypeAttributes on _E_GoodType {\n id\n __typename\n name\n descr\n price\n vendor {\n entity {\n id\n __typename\n name\n }\n }\n}", "allowEmptyChecks": true, "disableJwtVerification": false }searchOrder: используется для поиска заказов для пользователей с ролями customer. Дополнительно накладывается ограничение, что выбираются только заказы текущего пользователя. Данное ограничение реализуется дополнительным условием фильтрации pathConditions : {
path: searchOrder,cond: it.customer.entityId == ${jwt:email}}.{ "name": "searchOrder", "body": "query searchOrder($cond: String) {\n searchOrder(cond: $cond, sort: {crit: \"it.orderDate\", order: DESC}) {\n elems {\n ...OrderAttributes\n }\n }\n}\n fragment OrderAttributes on _E_Order {\n id\n __typename\n openOrderFlag\n orderDate\n comment\n statusForVendor {\n code\n }\n orderDetailList {\n elems {\n ...OrderDetailAttributes\n }\n }\n}\n fragment OrderDetailAttributes on _E_OrderDetail {\n id\n __typename\n goodType {\n entity {\n ...GoodTypeAttributes\n }\n }\n}\n fragment GoodTypeAttributes on _E_GoodType {\n id\n __typename\n name\n descr\n price\n vendor {\n entity {\n id\n __typename\n name\n }\n }\n}", "allowEmptyChecks": false, "disableJwtVerification": true, "checkSelects": [ { "conditionValue": "'customer' $in ${[]:jwt:realm_access.roles}", "orderValue": "0", "typeName": "", "description": "Only customer can see his own orders" } ], "pathConditions": [ { "path": "searchOrder", "cond": "it.customer.entityId == ${jwt:email}" } ] }