## КРАТКО

Django_yauth -- это подключаемое приложение для Django-проектов, реализующее
Яндекс- и доменную авторизацию.

Страница на wiki: http://wiki.yandex-team.ru/Python/common/djangoyauth

Если вам нужна старая документация, использующая старые мидлварины, она доступна по ссылке:
https://github.yandex-team.ru/common-python/python-django-yauth/blob/dc36bacfe87acb256a4f08f10194ec4a34b4c17f/README.md

Приложение умеет:

- определять UID пользователя из браузерной cookie
- получать tvm-тикет в ответ на куку Session_id
- Кроме того есть устаревшая функциональность, почитать о которой можно в [GitHub](https://github.yandex-team.ru/common-python/python-django-yauth/blob/dc36bacfe87acb256a4f08f10194ec4a34b4c17f/README.md)

## Тест

./run_tests.sh

## УСТАНОВКА

1.  Счекаутить папку с приложением, положить куда-нибудь в питоновский путь.
    Прямо в проект класть не рекомендуется, потому что ее тогда придется
    коммитить в репозиторий проекта и следить за актуальностью самостоятельно.

    Альтернативно -- установить пакет `django_yauth` c локального PyPI или deb-пакет `python-django-yauth`.

2.  Прописать установленный пакет в INSTALLED_APPS:

        INSTALLED_APPS = (
           # ...
           'django_yauth',
        )

    Это приложение должно стоять раньше стандартного "django.contrib.auth",
    чтобы перекрывать шаблон логинной формы в админку, которая в django_yauth
    перенаправлена в Яндекс.Паспорт. Это нужно, если вы собираетесь использовать
    Яндекс-авторизацию в админке.

3.  В проектных setting'ах включить settings.py от django_yauth:

        from django_yauth.settings import *

    Это необходимо, потому что код завязан на обязательное наличие некоторых
    setting'ов.

4.  После этого импорта указать тип авторизации (intranet, mobile или desktop)
    для настройки URL'ов к Паспорту. Пример для внутренней авторизации:

        YAUTH_TYPE = 'intranet' # по умолчанию - None, т.е. автоопределение

    Для динамического определения версии можно установливать request.yauth_type
    в необходимое значение.

5.  Убедиться, что модуль `yenv` выдаёт правильное окружение для выбора
    сервера blackbox. Снова пример для внутренней авторизации:

        $ python -c "import yenv; print yenv.name"
        intranet

    (Если выдача отличается, а нужна внутренняя авторизация, поставьте пакет
    yandex-environment-intranet).

Авторизация внешних и внутренних пользователей отличается только URL'ами к
Паспорту и blackbox'у. Дальнейшие настройки применимы и к внутренней, и к
внешней авторизации.

Один из способов авторизации в Django-админке описан в последнем разделе.


## АВТОРИЗАЦИЯ ЯНДЕКС-ПОЛЬЗОВАТЕЛЕЙ В ПРИЛОЖЕНИИ

Приложение использует `AUTHENTICATION_BACKENDS` из джанго. Это значит что вы можете
вызывать в своем приложении:

```(python)
user = django.contrib.auth.authenticate(request)
if user is not None:
  if user.has_perms('fly_japan', 'bow_to_statue'):
    start_sacred_journey(user, 'hidden temple')
  else:
    sorry_bro(user)
```

Приведенный выше пример работает для oauth.

В пакете django_yauth.authentication_mechanisms перечислены поддерживаемые
механизмы аутентификации. Рекомедуется первым ставить TVM механизм, так как
он позволяет проводить оффлайн авторизацию, а механизмы применяются последовательно.

Вы можете выбирать какие из них вам требуется подключить:

```(python)
AUTHENTICATION_BACKENDS=[
    'django_yauth.authentication_mechanisms.tvm.Mechanism',
    'django_yauth.authentication_mechanisms.oauth.Mechanism',
    'django_yauth.authentication_mechanisms.cookie.Mechanism',
]
```

Для указания домена, который передаётся для авторизации в Паспорт, можно
включить contrib-приложение sites и установить текущий сайт на
*.yandex[-team].ru (в зависимости от авторизации - внутренней или внешней).
Если sites не установлен (или в settings указано `YAUTH_USE_SITES = False`),
текущий сайт будет взят из request.

Вызов django.contrib.auth.authenticate вернет вам либо None (пользователь неаутентифицирован)
либо объект YandexUser. У него будут следующие возможности:

- `user.is_authenticated()` -- признак авторизованности
- `user.uid` -- UID
- `user.login` -- логин
- `user.has_perm` - можно проверять доступы, например, oAuth скоупы.

Для получения tvm-тикета необходимо задать настройки YAUTH_TVM_CLIENT_ID,
YAUTH_TVM_CLIENT_SECRET и YAUTH_BLACKBOX_CONSUMER.
Среди полей пользователя появится поле `user.ticket`, содержащее tvm-тикет.

Различные настройки подробно описаны в settings.py. Изменять их только нужно
не в нем самом, а в настройках проекта после строчки с импортом настроек
django_yauth.

Вы можете использовать мидлварь YandexAuthBackendMiddleware. Предпочитаемый способ ее
использования - унаследоваться от нее и определить нужную вам логику.
Можно предусмотреть, например возврат 403 для неаутентифицированного пользователя.
В этом преимущество перед YandexAuthMiddleware, которая умела только перенаправлять
на паспорт.
Кроме того, унаследовавшись от нее, можно связать возвращенного YandexUser
c вашей AUTH_USER_MODEL.
Она обеспечивает совместимость с template tags, context processors из yauth.

## Механизм TVM авторизации
**Внимание: поддерживается только TVM2**

Для подключения данного способа авторизации следует:
- завести tvm приложение (ссылка ниже)
- прописать `client_id/secret` в настройках
- добавить `django_yauth.authentication_mechanisms.tvm.Mechanism` в список `AUTHENTICATION_BACKENDS`

Информация про заведение приложения - https://wiki.yandex-team.ru/passport/tvm2/quickstart/#1-zavodimprilozhenie

Для работы данного механизма следует указать в настройках следующие переменные:
- `YAUTH_TVM2_ALLOWED_CLIENT_IDS` - список `client_id` приложений, которым разрешено приходить с
тикетами в ваше приложение. **В будущем эта настройка будет удалена в пользу работы через ABC.**

- `YAUTH_TVM2_CLIENT_ID` - значение `client_id` выданное при регистрации приложения в tvm

- `YAUTH_TVM2_SECRET` - значение `secret` выданное при регистрации приложения в tvm

Так же поддерживается работа с `qloud tvm2 демоном`, для этого нужно кроме настроек выше указать
переменную окружения `TVM2_USE_QLOUD` и выполнить все подготовительные шаги со стороны qloud 
https://wiki.yandex-team.ru/passport/tvm2/tvm-daemon/tvm-getting-started/.

Если не прописана настройка `YAUTH_TVM2_GET_USER_INFO` поля указанные в `PASSPORT_FIELDS/YAUTH_PASSPORT_FIELDS` при авторизации
по TVM2 игнорируются. Если же `YAUTH_TVM2_GET_USER_INFO` указана - при запросе с валидным user_ticket
будет вызван метод user_info и данные пользователя будут обогащены его ответом.

Механизм обрабатывает тикеты переданные в заголовках `X-Ya-User-Ticket` и `X-Ya-Service-Ticket` для персонализированных тикетов и
`X-Ya-Service-Ticket` для обезличенных (сервисных) тикетов. То есть для успешной авторизации в запросе обязательно
должен быть заголовок `X-Ya-Service-Ticket`, если пришли с сервисным тикетом, и оба заголовка `X-Ya-User-Ticket` и
`X-Ya-Service-Ticket`, если пришли с персонализированным тикетом  - в этом случае тикеты из двух этих заголовков будут проверены на
валидность и если хотя бы один из тикетов не валиден - авторизация произведена не будет.

После успешной авторизации в `request.yauser` будет либо экземпляр класса `TvmImpersonatedRequest`, если
к нам пришли с персонализированным тикетом, либо экземпляр класса `TvmServiceRequest`, если к нам пришли с
обезличенным тикетом (сервисным).
При этом стоит учесть что в одном тикете может быть информация сразу о нескольких
пользователях (значения их `uid`). Их список можно получить вызывав свойство `uids` у экземпляра
класса `TvmImpersonatedRequest`, при этом также остался доступен атрибут `uid`, который содержит
`default_uid` переданного тикета (в случае если запрос был сделан с персонализированным тикетом,
иначе - `None`).

В случае, если система авторизации возвратила объект пользователя,
у него будут доступны следующие возможности:

- `user.is_authenticated()` -- признак авторизованности
- `user.uids` - список `uid` пользователей в тикете (будет отсутствовать если пришли с
сервисным тикетом).
- `user.uid` - `None` (если пришли с обезличенным тикетом) / `default_uid` (если пришли с песонализированным)
- `user.scopes` - список скоупов пользователя
- `user.check_scopes` - позволяет проверять наличие определенных скоупов
- `user.is_impersonated` - `False`, если пришли с обезличенным тикетом, `True`,
если пришли с песонализированным
- `user.user_ticket` - будет содержать распарсенный пользовательский тикет (если пришли с песонализированным
тикетом)
- `user.service_ticket` - будет содержать распарсенный сервисный тикет

### Дополнительные поля

Если используется blackbox'овый бэкенд, то django_yauth умеет выдавать
дополнительные поля, связанные с юзером. На все поля, которые нужны проекту,
он должен иметь административный доступ в Паспорте. Именно поэтому поля, нужные
проекту не зашиты в код навечно, а оформлены настройкой YAUTH_PASSPORT_FIELDS. Это
список названий запрашиваемых полей. Справочник по полям есть здесь:
<https://wiki.yandex-team.ru/passport/BlackboxDbFields>


### Паспортные алиасы

Помимо дополнительных полей можно получать паспортные алиасы пользователя - `YandexUser.get_alias`.
Алиасы относят аккаунт к какой-либо категории или идентифицируют его в других системах.
Для использования их нужно явно заказать в ответе ЧЯ:

        YAUTH_BLACKBOX_PARAMS = {
            'aliases': 'all',
            ...
        }


### Lite-пользователя

В Яндексе есть так называемые лайт-пользователи <http://wiki.yandex-team.ru/passport/Lite>.
Информацию о них можно получить от blackbox. Они тоже имеют uid'ы, которые
не пересекаются с uid'ами обычных пользователей.

Поскольку они не равны в правах с обычными яндексовыми пользователями, то в рамках
django_yauth они всегда считаются не авторизованымы, т.е. is_authenticated()
возвращает False.

Для идентификации лайт-пользователей у yuser есть метод is_light().

### Пользователи в админке

Для того чтобы была возможность использовать YandexUser пользователей в админке,
необходимо будет чтобы у них был атрибут is_staff равный True, а has_perm возвращало
True на запросы админки.

yauth этого не предоставляет и вам необходимо реализовывать это самим.

### Пользователи в шаблонах

Почти наверняка вы захотите отображать пользователя в шаблонах приложения, для чего
нужно подключить соответствующий процессор контекста:

        TEMPLATE_CONTEXT_PROCESSORS = (
            # ...
            'django_yauth.context.yauth',
        )

Установить приложение django_template_common (поставив сначала пакет
python-django-template-common):

        INSTALLED_APPS = (
            # ...
            'django_template_common',
        )

В шаблоне подключить библиотеку:

        {% load yauth %}

И использовать шаблонные теги:

- `{% yauth %}` -- стандартная верстка текущего юзера и всех нужных ссылок для шапки
- `{% login_form %}` -- всплывающая форма логина

(Ещё нужно скопировать к себе в проект и использовать yauth.css из папки css в
django_yauth.)

Кроме тегов, в шаблоне появляется переменная `yauser` с текущим пользователем, а также
переменные с URL'ами Паспорта:

- login_url: форма логина
- logout_url: логаут
- register_url: процесс регистрации
- account_url: общеяндексовый аккаунт пользователя (http://i.yandex.ru/)
- passport_account_url: аккаунт пользователя в Паспорте

Ещё в контексте появляется переменная passport_host, которой пользуется python-django-lego2, чтобы
в лего-шапке были правильные адреса до паспорта.


### Blackbox

Яндекс-авторизация django_yauth работает через авторизационный бэкенд --
[Blackbox][1].

- Blackbox умеет узнавать пользователя по куке Session_id и по паре
  логин/пароль. Также умеет выдавать расширенную личную информацию пользователя
  (ФИО, email), для чего требует специальных дырок и прав. Также умеет выдавать tvm-тикет.

[1]: https://wiki.yandex-team.ru/passport/blackbox

- Чтобы ходить в Blackbox с использованием `TVM2` сервисных тикетов укажите в настройках свои
`YAUTH_TVM2_CLIENT_ID`, `YAUTH_TVM2_SECRET` и задайте `YAUTH_USE_TVM2_FOR_BLACKBOX = True`.

- Чтобы при авторизации по oauth/session_id получать пользовательский тикет задайте
`YAUTH_USE_TVM2_FOR_BLACKBOX = True` (см. предыдущий пункт) и `YAUTH_TVM2_GET_USER_TICKET = True`
после чего у объекта пользователя в атрибуте `raw_user_ticket` будет tvm2 пользовательский тикет.

### Проверка oauth scopes
Что бы начать проверять oauth scopes в вашем приложении, достаточно добавить настройку
`YAUTH_OAUTH_AUTHORIZATION_SCOPES` типа list. Если настройка пуста, проверка scopes производиться
не будет. Если ни один из scope токена не присутствует в данной настройке то пользователь не
будет авторизован.


## ИСПОЛЬЗОВАНИЕ НАТИВНОГО DJANGO USER

Этот режим был сделан специально для elephantum@ :-). Заключается он
в том, что вместо встроенной авторизации для определения `request.user` используется
текущий залогиненный пользователь Яндекса. Его также пускают в админку в том случае,
если существует (заведен вручуную) Джанго-пользователь с таким же логином, и у него
есть права на логин в админку (`is_staff = True`).

Этот режим по умолчанию отключен и включается настройкой `YAUTH_USE_NATIVE_USER`.

При его использовании надо убедиться, что `YandexAuthMiddleware` стоит в
настройках **вместо** стандартной `AuthenticationMiddleware`, потому что она
переписывает под себя `request.user`.

Замечание: Раньше можно было не удалять стандартную middleware, поместив
YandexAuthMiddleware после него. Но, начиная с пакета python-django версии
1.3-20110901.svn16714-yandex1, `YAUTH_USE_NATIVE_USER = True` не работает при
включенной `AuthenticationMiddleware`.

Для авторизованного через django_yauth пользователя есть возможность
автоматически создавать и сохранять в БД объект User при обращении к
`request.user`. Регулируется переменной settings.YAUTH_CREATE_USER_ON_ACCESS
(по-умолчанию `False`).

Параметр `YAUTH_YAUSER_USERNAME_FIELD` указывает, какое поле yauser будет
ставится в соответствие `USERNAME_FIELD` джанго пользователя (по умолчанию `login`).
`uid` позволяет одновременно работать с внутренним и внешним Паспортом,
где `login` может совпадать.
Нужно учитывать, что поле должно быть уникальным, и что стандартный
auth.User не поддерживает 64-битный яндексовый uid.

При создании джанго пользователя можно сохранять произвольные поля yauser.
Это задается настройкой `YAUTH_USER_EXTRA_FIELDS`, которая хранит список пар вида
(yauser_field, user_field). Содержимое user_field нового User будет взято из
yauser_field яндексового пользователя.


## РАБОТА В MASTER/SLAVE РЕЖИМЕ БД

Внимание, документация в этом месте устарела.

Использование `django_replicated` и опции `YAUTH_CREATE_USER_ON_ACCESS` и/или
`YAUTH_CREATE_PROFILE_ON_ACCESS` приводят к тому, что пользователь и/или профиль
пытаются создаваться даже на GET/HEAD запросах, которые обычно идут на slave базу.

Для того чтобы это обойти, существует специальный редирект (если запрос GET/HEAD)
на отдельную вьюху (указывается в настройке `YAUTH_CREATE_PROFILE_VIEW`),
которая создаёт пользователя и/или все нужные профили. Чтобы его включить
нужно выставить `YAUTH_CREATION_REDIRECT` в `True`, и не забыть в настройках
`django_replicated` указать для данной вьюхи форсирование хождение в master.

Особенность использования `YAUTH_CREATION_REDIRECT` состоит в том, что этот
механизм провоцирует "проверочные" запросы в базу за пользователем и/или профилями,
чтобы удостовериться, что они уже есть и не нужен редирект.

## Тестирование

Для того чтобы в тестах можно было удобно переопределять пользователя или приложение
нужно заменить `YandexAuthMiddleware` на `YandexAuthTestMiddleware` и выставить
настройку `YAUTH_TEST_USER` и `YAUTH_TEST_APPLICATION` (если требуется OAuth
аутентификация).


## Разработка
Для разработки на хосте, где нет возможности получить blackbox-гранты есть
возможность замокать создание YandexUser, используя 
[Django auth backends](https://docs.djangoproject.com/en/1.11/ref/settings/#std:setting-AUTHENTICATION_BACKENDS)
-cовместимые бэкенды из модуля `django_yauth.authentication_mechanisms.dev`.

Настройка в приложении будет выглядеить примерно так:

```python
import yenv

MIDDLEWARE_CLASSES = [
    # ...
    'django_yauth.middleware.YandexAuthBackendMiddleware',
    # ...
]

if yenv.type == 'development':
    AUTHENTICATION_BACKENDS = [
        'django_yauth.authentication_mechanisms.dev.UserFromHttpHeaderAuthBackend',
        'django_yauth.authentication_mechanisms.dev.UserFromCookieAuthBackend',
        'django_yauth.authentication_mechanisms.dev.UserFromOsEnvAuthBackend',
    ]
else:
    AUTHENTICATION_BACKENDS = [
        'django_yauth.authentication_mechanisms.cookie.Mechanism',
        'django_yauth.authentication_mechanisms.oauth.Mechanism',
    ]

```
Тогда в `development` окружении и тестах будет проставляться `request.yauser` 
через http-заголовок `DEBUG-LOGIN:username` или куку `yandex_login`, 
а по умолчанию будет браться логин из переменной окружения `USER`.
