Разрешения на API отдыха, реализующие HATEOAS
Я пытаюсь найти правильный способ обработки разрешений в одностраничном приложении, которое напрямую взаимодействует с несколькими RESTful API, которые реализуют HATEOAS.
В качестве примера:
"Как пользователь моего приложения я могу просматривать, запускать и приостанавливать работы, но не останавливать их".
Базовый API отдыха имеет следующий ресурс:
/ jobs / {id}, который принимает GET и PUT. GET возвращает модель задания, а PUT принимает модель задания в качестве тела запроса в форме:
{
"_links" : {
"self" : "/jobs/12345678"
}
"id" : 12345678,
"description" : "foo job",
"state" : "STOPPED"
}
Принятые состояния работы могут быть: неактивными | работает | приостановлено | остановился.
Требование говорит, что на интерфейсе у меня должны быть кнопки:
СТАРТ, ПАУЗА, СТОП
... и отображать только в соответствии с разрешениями вошедшего в систему пользователя.
С точки зрения API все работает, поскольку базовая логика на сервере гарантирует, что пользователь не может обновить состояние до состояния STOPPED, когда сделан запрос (возможно, возвращается 401).
Каков наилучший способ сообщить приложению / пользовательскому интерфейсу о разрешениях пользователя, чтобы он мог скрыть любые кнопки, которые пользователь не имеет разрешения на действия?
Если API предоставляет список разрешений, может быть что-то вроде:
{
"_links" : {
"self" : "/permissions",
"jobs" : "/jobs"
}
"permissions" : {
"job" : ["UPDATE", "DELETE"],
"job-updates" : ["START", "PAUSE"]
}
}
ИЛИ если API изменится так, что разрешения будут отражены в ссылках HATEOS, может быть что-то вроде:
{
"_links" : {
"self" : "/jobs/12345678",
"start" : "/jobs/12345678/state?to=RUNNING",
"pause" : "/jobs/12345678/state?to=PAUSED",
}
"id" : 12345678,
"description" : "foo job",
"state" : "DORMANT"
}
Или это должно быть сделано совершенно по-другому?
ОБНОВИТЬ
Я нашел следующую статью, в которой предлагается ответ: https://softwareengineering.stackexchange.com/questions/215975/how-to-handle-fine-grained-field-based-acl-permissions-in-a-restful-service
3 ответа
Я хотел бы пойти с последним: подразумевать разрешения, основанные на том, какие ссылки присутствуют.
Если ссылка отсутствует, пользователь не может получить доступ к ресурсу / выполнить действие. Если это так, они могут. Это то, что я сделал бы, потому что это просто и чисто и оставляет мало на усмотрение внешнего кода. Развязка, йо.
В качестве альтернативы, если вы хотите включить все ссылки в каждый ответ, но явно указать, какие из них разрешены, а какие нет, если вы используете формат, такой как HAL, для написания ссылок, вы можете расширить его с помощью флага для каждой ссылки, например так:
{
"_links" : {
"self" : {
"href":"/jobs/12345678",
"allowed":false
},
"start" : {
"href":"/jobs/12345678/state?to=RUNNING",
"allowed":false
},
"pause" : {
"href":"/jobs/12345678/state?to=PAUSED",
"allowed":false
}
},
"id" : 12345678,
"description" : "foo job",
"state" : "DORMANT"
}
Я бы пошел с последним. Причина, по которой мне не нравится первый, заключается в том, что вы создаете дополнительную работу для клиента, требуя, чтобы он выяснил соответствие между разрешениями и ресурсами, к которым они разрешают доступ. Если вы используете hateoas и проверяете наличие типов отношений, это сопоставление выполняется для вас сервером. Это также означает, что Uris может измениться, не нарушая клиента.
Я недавно написал сообщение в блоге на эту область:
https://www.opencredo.com/2015/08/12/designing-rest-api-fine-grained-resources-hateoas-hal/
Вы должны использовать формы, а не ссылки, чтобы обеспечить переход между состояниями гипермедиа. Если вы не можете предоставить формы в своем типе мультимедиа, предоставьте ссылки на URI, которые используют другой тип мультимедиа, который поддерживает формы, например XHTML.
IANA имеет отношения связи для create-form
, edit-form
а также delete-form
для этого.
Также, пожалуйста, не используйте start
а также pause
как реальная связь отношений. Если вы определяете их сами, они должны быть URI (желательно HTTP URL, но подойдет любой URI под вашим контролем). start
имеет совершенно другое значение для того, для чего вы его используете, и pause
не определено.