Аутентификация и Авторизация

Авторизация/аутентификация производится с помощью HTTP заголовков Authorization или Ticket. Для разных типов клиентов используется разная схема аутентификации и авторизации. В случае каких-либо проблем с аутентификацией, возарвщается код ответа 401, а при проблем с авторизацией возвращается ответ с кодом 403.

Использование заголовков

Внутренние клиенты (TVM)

Внутренними клиентами считаются сервисы Яндекса, для которых не нужно получать OAuth токены для манипуляции данными пользователя. Примеры таких клиентов:

  • Yamb

  • Диск

  • Почта

  • Портал директории

Внутреним клиентам рекомендуется “ходить” в API Директории с TVM тикетами.

Для авторизации по тикету необходимо сопроводить запрос HTTP заголовком X-Ya-Service-Ticket следующего вида:

X-Ya-Service-Ticket: {ticket}

После того, как вы завели своё приложение на https://tvm.yandex-team.ru, надо создать в очереди DIR тикет, с указанием client_id вашего TVM приложения, названием сервиса и коротким названием сервиса, состоящим из латинских букв, цифр и знака -.

TVM Тикет может содержать uid пользователя. Если он не содержит uid, а ручка которую вы вызываете, требует наличия пользователя, то uid надо передать в заголовке X-UID.

Warning

Если пользователь использует мультиавторизацию, и вы меняете его куки на TVM тикет, то Директория получит из него больше одного уида. В этой ситуации, Директория будет считать запрос неаутентифицированным и вернёт 401.

Чтобы этого не произошло, лучше всегда явно указывать UID текущего пользователя через заголовок X-UID.

В любом случае, если ручке нужно работать от имени пользователя, то следует дополнительно прокинуть его реальный IP в заголовке X-User-IP:

X-UID: 123
X-USER-IP: 1.2.3.4

Для некоторых ручек, например тех, которые читают данные про организацию, пользователь не требуется, и нужно указать лишь ID организации в заголовке X-Org-ID:

X-Org-ID: 12345

В случае, когда передан лишь id организации, будут работать только ручки, отдающие данные про компанию. Выполнять какие-либо действия необходимо от имени какого-то сотрудника организации, а для этого следует передавать X-UID и X-USER-IP.

В случае, если нужно получить данные от uid-а, который был Админом в ПДД (обычный пользователь яндекса), то нужно передавать и X-UID, и X-ORG-ID, т.к. у админов ПДД было несколько доменов и может быть несколько организаций. Такого пользователя мы называем Внешним Админом организации, логика и связанные таски тут

Внешние клиенты (OAuth)

Для аутентификации по OAuth токену необходимо сопроводить запрос HTTP заголовком Authorization следующего вида:

Authorization: OAuth {token}

Логика под капотом

При аутентификации запроса, мы выясняем, от какого Сервиса и Пользователя он пришёл.

Под сервисом тут понимается некий сервис, чей идентификатор (OAuth client_id, TVM client_id или же токен зашитый в конфиг) известен Директории.

При этом могут быть запросы:

  • Анонимные, для которых неизвестен ни Сервис, ни Пользователь.

  • Такие, где Сервис известен, а Пользователь нет.

  • Такие, где известен и Сервис и Пользователь.

  • Такие, где известен Пользователь, но не сервис.

Последний случай может быть, когда некто завёл OAuth приложение, которое не добавлено в Директорию как сервис, и выписал для него OAuth токен от имени Пользователя.

Аутентификация

В результате аутентификации, мы ассоциируем с текущим запросом атрибуты service, user, org_id и scopes, первые два могут быть словарями с информацией, или же принимать значение None, а атрибут scopes – set из строк со скоупами, привязанными к сервису.

Атрибут org_id заполняется в том случае, если передан заголовок X-Org-ID.

Сервис известен, а Пользователь нет

Такая ситуация получается, в нескольких случаях:

  • Сервис пришёл с выданным ему когда-то токеном, который захардкожен в конфиге.

  • Сервис пришёл с OAuth токеном, и Директория нашла его client_id в своей базе.

  • Сервис пришёл с TVM токеном, и Директория нашла его tvm_client_id в своей базе.

В случае с OAuth и TVM токеном, список скоупов доступных приложению, достаётся сначала из базы Директории, а потом, если это OAuth, то к нему добавляются скоупы отданные Паспортом. Это позволяет нам иметь TVM сервисы с разным набором привилегий, а так же специальные OAuth Сервисы со скоупами, которые не доступны людям снаружи Яндекса.

Анонимные запросы

Для анонимных запросов мы просто заполняем service=None, user=None, scopes=set().

Авторизация

На этом этапе Директория может возвращать как 401 ошибки, так и 403.

  • 401 возвращается в тех случаях, когда что-то не так с переданными в API данными – например организация или пользователь с указанным id не существуют.

  • 403 означает, что данному сервису или пользователю по тем или иным причинам недоступна текущая операция. Например, для создания Команды требуется наличие пользователя, но Сервис не указал от имени кого выполняется запрос. Либо Сервис указал сотрудника, от чьего имени выполняется запрос, но не при этом не имеет полномочий (скоупа work_on_behalf_of_any_user).

Сначала Директория вынимает из метабазы информацию про пользователя. И если был явно указан X-Org-Id, то проверяет, что пользователь состоит в этой организации. Если нет, то возвращается 403 ошибка.

Далее, Директория смотрит со сколькими организациями связан пользователь. Если организация одна, то её id прописывается в org_id. Если организаций несколько, то org_id остаётся None.

Если org_id указан, но пользователь не известен, то Директория проверяет, есть ли такая организация в базе, и если нет, то возвращает 401 ошибку Organization does not exist.

Таким образом, org_id либо None, либо соответствует id организации из базы Директории.

Если org_id так или иначе стал известен, то Директория выполняет ещё ряд проверок:

  1. Достаёт организацию из метабазы и узнаёт её шард.

  2. Если организация в процессе миграции (атрибут ready==False), то возвращает 403 Organization is not ready.

  3. Получает ревизию организации.

  4. Если известен Сервис, то проверяет, что он подключен для данной организации либо имеет скоуп work_with_any_organization. Если ни того, ни другого нет, то 403 Service is not enabled.

  5. Если известен и Сервис и Пользователь, то проверяет, что сервис имеет скоуп work_on_behalf_of_any_user, либо Пользователь - робот, заведённый Директорией специально для этого сервиса. Если нет, то 403 Unable to work on behalf of the user.

  6. Дальше проверяется, что если ручка API требует наличия пользователя, то он известен, если нужно знать org_id, то он известен, а так же то, что если ручка внутренняя, то сервис должен иметь специальную пометку is_internal=True. Если “внешний” сервис пытается получить доступ к внутренней ручке, ему возвращается 404 Not found.

Права на выполнение отдельных ручек, проверяются специальными декораторами и внутри самих ручек. Далее описаны общие правила того, как это происходит.

Соответствуют ли скоупы?

Обычно, скоупы на ручки у нас двух типов, к примеру users_read, users_write. Скоупы заканчивающиеся на _read, говорят, что можно ходить с запросами в читающие ручки, относящиеся к данной категории, а скоупы заканчивающиеся на _write говорят о том, что можно ходить и в читающие и в пишущие ручки. Поэтому все читающие ручки про пользователей помечены, что они требуют либо users_read, либо users_write, а пишущие помечены только скоупом users_write.

Получается, что у запроса должен быть хотя бы один из скоупов, нужен ручке. К примеру, если вызывается ручка GET /users/, то у запроса должен быть либо users_read, либо users_write, но могут быть и оба, хотя в случае, когда есть _write, _read скоуп иметь не обязательно.

Итак, на первом этапе, проверяются скоупы. Если пересечение скоупов требуемых ручкой и скоупов которые есть у запроса пустое, то отвечаем 403.

Имеет ли пользователь право на выполнение действия?

Если ручка вызывается от имени пользователя, то дополнительно проверяются его permissions, которые вычисляются исходя из следующих правил:

  • внешним администраторам позволено всё;

  • администраторам организации позволено всё кроме отката организации обратно в ПДД;

  • команды создавать может любой сотрудник;

  • редактировать команду может только тот сотрудник, который её создал;

  • сотрудник, редактируя данные о себе, может менять только контакты, день рождения и аватарку;

  • сотрудник с логином yandex-yamb-support-user лишён всех прав и редактировать он тоже ничего не может.

Для yandex-team организации, синхронизирующейся со стаффом, отключена возможность редактирования отделов и сотрудников. Даже для админов.

Дальше, у каждой ручки перечислен список прав, которые требуются, чтобы действие было произведено. Могут требоваться либо все эти права, либо хотя бы одно.

В сложных случаях, как например редактирование пользователя, когда только внутри метода можно проверить, какие именно поля редактируются, проверка прав осуществляется не в декораторе а в самой view. Скажем, ручка PATCH /users/ внутри смотрит, изменяется ли поле birthday и если да, то проверяет, есть ли у пользователя право edit_birthday.