Chapter 9: Контроль доступа

Контроль доступа

Auth
Access Control
RBAC
DAC
MAC

web2py включает в себя мощный и настраиваемый механизм управления доступом на основе ролей Role-Based Access Control (RBAC).

Вот определение из Википедии:

"Управление доступом на основе ролей (RBAC) это способ ограничению доступа к системе для авторизованных пользователей. Это новый альтернативный подход к обязательному контролю доступа mandatory access control (MAC) и избирательному контролю доступа discretionary access control (DAC). RBAC иногда называют безопасностью на основе ролей.

RBAC обладает нейтральной и гибкой политикой и достаточно мощной технологией управления доступом для моделирования DAC и MAC. С другой стороны, MAC может моделировать RBAC, если роль графа ограничивается деревом, а не частично упорядоченным множеством.

До разработки RBAC, MAC и DAC рассматривались как единственные известные модели контроля доступа: если модель была не MAC, то она считалась моделью DAC, и наоборот. Исследования, проведенные в конце 1990-х годов показали, что RBAC не относится ни к одной из этих категорий.

В рамках организации, роли создаются для различных рабочих функций. Разрешения на выполнение определенных операций назначены определенным ролям. Членам персонала (или другим пользователям системы) назначаются определенные роли, и через назначенные роли получают разрешения для выполнения конкретных функций системы. В отличие от управления доступом на основе контекста(CBAC), RBAC не обращает внимания на контекст сообщения (такой как источник подключения).

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

RBAC отличается от списков контроля доступа (ACL), используемых в традиционных системах дискреционного управления доступом, тем, что он назначает разрешения на определенные операции со смыслом в организации, а не к объектам данных низкого уровня. Например, список контроля доступа может быть использован для разрешения или запрета доступа на запись файла в определенной системе, но это не будет диктовать как такой файл может быть изменен."

Класс web2py, который реализует RBAC называется Auth.

Auth требует (и определяет) следующие таблицы:

  • auth_user хранит имена пользователей, адреса электронной почты, пароль и статус (в ожидании регистрация, принят, заблокирован)
  • auth_group хранит группы или роли для пользователей в многие-ко-многим структуре. По умолчанию каждый пользователь находится в своей группе, но пользователь может находиться в нескольких группах, и каждая группа может содержать несколько пользователей. Группа идентифицируется через роль и описание.
  • auth_membership связывает пользователей и группы в многие-ко-многим структуре.
  • auth_permission связывает группы и разрешения. Разрешение идентифицируется по имени и, необязательно, таблицы и записи. Например, члены определенной группы могут иметь "update" разрешения на конкретной записи конкретной таблицы.
  • auth_event журналы изменений в других таблицах и успешный доступ через CRUD к объектам, контролируемым через RBAC.
  • auth_cas используется для Центральной службы аутентификации Central Authentication Service (CAS). Каждое web2py приложение является поставщиком CAS и опционально может быть CAS-потребителем.

Схема воспроизводится графически на изображении ниже:

image

В принципе, нет никаких ограничений на имена функций и имена разрешений; разработчик может создать их, чтобы установить роли и разрешения в организации. После того, как они будут созданы, web2py предоставляет API для проверки, если пользователь вошел в систему, если пользователь является членом данной группы, и/или если пользователь является членом какой-либо группы, имеющей заданное необходимое разрешение.

web2py также предоставляет декораторы, чтобы ограничить доступ к любой функции на основе логина, членства и разрешений.

web2py также понимает некоторые специфические разрешения, то есть те, которые имеют имя, которое соответствуют методам CRUD (создание, чтение, обновление, удаление) и может обеспечить их соблюдение в автоматическом режиме без необходимости использования декораторов.

В этой главе мы будем обсуждать различные части RBAC один за другим.

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

Для того, чтобы использовать RBAC, пользователи должны быть идентифицированы. Это означает, что они должны зарегистрировать (или быть зарегистрированы) и войти в систему.

Auth предоставляет несколько методов авторизации. По умолчанию первый состоит из идентификации пользователей на основе локальной auth_user таблицы. В качестве альтернативы, он может войти под пользователем от систем аутентификации и единого входа сторонних производителей таких поставщиков, как Google, PAM, LDAP, Facebook, LinkedIn, Dropbox, OpenID, OAuth и т.д..

Чтобы начать использовать Auth, вам необходим, по крайней мере этот код в файле модели, который также предоставляется с web2py приложением "welcome" и предполагает наличие объекта подключения db:

from gluon.tools import Auth
auth = Auth(db)
auth.define_tables(username=False,signature=False)

По умолчанию, web2py использует электронную почту для входа в систему. Если вместо этого вы хотите использовать имя пользователя для входа в систему, то задайте auth.define_tables(username=True)

Настройка signature = True добавляет пользователя и проставляет штамп даты для auth таблицы, чтобы отслеживать изменения.

Auth имеет необязательный secure=True аргумент, который заставит аутентифицируемые страницы перейти на HTTPS.

https

По умолчанию, Auth защищает логины против кросс-сайтовых подделок запросов (CSRF). Это на самом деле обеспечивается стандартной защитой от CSRF web2py всякий раз, когда формы генерируются в сессии. Тем не менее, при определенных обстоятельствах, накладные расходы на создание сессии для входа в систему, запроса пароля и сброса попыток могут быть нежелательными. DOS атаки теоретически возможны. Защиту CSRF можно отключить для Auth форм (по состоянию на v 2.6):

Auth = Auth(..., csrf_prevention = False)

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

Поле password из таблицы db.auth_user по умолчанию имеет CRYPT валидатор, который нуждается в hmac_key. На устаревших web2py приложениях вы можете увидеть дополнительный аргумент, передаваемый в конструктор Auth: hmac_key = Auth.get_or_create_key(). Последнее является функцией, которая читает ключ HMAC из файла "private/auth.key" в папке приложения. Если файл не существует, она создает случайный hmac_key. Если несколько приложений совместно используют одни и те же базы данных аутентификации, то убедитесь, что они также используют один и тот же hmac_key. Это больше не является необходимым для новых приложений, так как пароли засаливаются с индивидуальной случайной солью.

Если несколько приложений совместно используют одни и те же базы данных авторизации вы можете отключить миграции: auth.define_tables(migrate=False).

Чтобы выставить Auth, вам также необходима следующая функция в контроллере (например, в "default.py"):

def user(): return dict(form=auth())

Объект auth и действие user уже определены в скаффолдинг-приложении.

web2py также включает в себя образец представления "welcome/views/default/user.html" для визуализации данной функции должным образом, которое выглядит следующим образом:

{{extend 'layout.html'}}
<h2>{{=T( request.args(0).replace('_',' ').capitalize() )}}</h2>
<div id="web2py_user_form">
  {{=form}}
  {{if request.args(0)=='login':}}
    {{if not 'register' in auth.settings.actions_disabled:}}
      <br/><a href="{{=URL(args='register')}}">register</a>
    {{pass}}
    {{if not 'request_reset_password' in auth.settings.actions_disabled:}}
      <br/>
      <a href="{{=URL(args='request_reset_password')}}">lost password</a>
    {{pass}}
  {{pass}}
</div>

Обратите внимание на то, что эта функция просто отображает form и, следовательно, она может быть настроена с помощью обычного синтаксиса пользовательской формы. Единственное ограничение в том, что отображаемая через form=auth() форма зависит от request.args(0); Поэтому, если вы замените по умолчанию auth() логин форму на пользовательскую логин форму, то вам потребуется оператор if в представлении вроде такого:

{{if request.args(0)=='login':}}...custom login form...{{pass}}

auth.impersonate
auth.is_impersonating

Контроллер выше выставляет несколько действий:

http://.../[app]/default/user/register
http://.../[app]/default/user/login
http://.../[app]/default/user/logout
http://.../[app]/default/user/profile
http://.../[app]/default/user/change_password
http://.../[app]/default/user/verify_email
http://.../[app]/default/user/retrieve_username
http://.../[app]/default/user/request_reset_password
http://.../[app]/default/user/reset_password
http://.../[app]/default/user/impersonate
http://.../[app]/default/user/groups
http://.../[app]/default/user/not_authorized
  • register позволяет пользователям регистрироваться. Действие интегрировано с CAPTCHA, хотя эта возможность по умолчанию отключена. Действие также интегрировано с калькулятором энтропии на стороне клиента, определенного в "web2py.js". Калькулятор указывает на силу нового пароля. Вы можете использовать IS_STRONG валидатор, чтобы предотвратить web2py от принятия слабых паролей.
  • login позволяет зарегистрированным пользователям войти в систему (если регистрация проверена и не требует проверки, если она была одобрена или не требует одобрения, и если он не был заблокирован).
  • logout делает то, что можно было бы ожидать, но также, как и другие методы, регистрирует событие и может быть использован для вызова какого-либо события.
  • profile позволяет пользователям редактировать свой профиль, то есть содержимое auth_user таблицы. Обратите внимание на то, что эта таблица не имеет фиксированную структуру и может быть настроена.
  • change_password позволяет пользователям изменять свой пароль отказобезопасным способом.
  • verify_email. Если проверка электронной почты включена, то посетители, при регистрации, получат электронное письмо с ссылкой для подтверждения информации об адресе электронной почты. Ссылка указывает на это действие.
  • retrieve_username. По умолчанию ** ** Auth использует электронную почту и пароль для логина, но он может, при необходимости, использовать имя пользователя, а не электронную почту. В этом последнем случае, если пользователь забудет свое имя, то retrieve_username метод позволяет пользователю ввести адрес электронной почты и получить имя пользователя по электронной почте.
  • request_reset_password. Позволяет пользователям, которые забыли свой пароль запросить новый пароль. Они получат подтверждение по электронной почте, указывая на reset_password.
  • impersonate позволяет пользователю "выдавать себя" за другого пользователя. Это важно для отладки и для целей поддержки. request.args[0] это id выдаваемого пользователя. Это допускается только в случае входа в систему пользователя имеющего разрешение has_permission('impersonate', db.auth_user, user_id). Вы можете использовать auth.is_impersonating(), чтобы проверить, является выдает ли себя текущий пользователь за кого-то другого.
  • groups перечислены группы, членом которых является текущий вошедший в систему пользователь.
  • not_authorized отображает сообщение об ошибке, когда посетитель пытается сделать что-то, на что он/она не имеет права делать
  • navbar является помощником, который генерирует бар с login/register/и т.д. ссылками.

Logout, profile, change_password, impersonate и groups требуют логина.

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

Все методы выше, могут быть расширены или заменены через подклассы Auth.

Все вышеприведенные методы могут быть использованы в отдельных действиях. Например:

def mylogin(): return dict(form=auth.login())
def myregister(): return dict(form=auth.register())
def myprofile(): return dict(form=auth.profile())
...

Чтобы открыть доступ к функциям только для зарегистрированных посетителей, декорируйте функцию, как в следующем примере

@auth.requires_login()
def hello():
    return dict(message='hello %(first_name)s' % auth.user)

Любая функция может быть декорирована, а не только выставленные действия. Конечно, это все еще только очень простой пример контроля доступа. Более сложные примеры будут рассмотрены ниже.

auth.user
auth.user_id
auth.user_groups.

auth.user содержит копию db.auth_user записей для текущего вошедшего в систему пользователя или None в противном случае. Существует также auth.user_id который является таким же, как auth.user.id (т.е. идентификатор текущего пользователя в user) или None. Аналогичным образом, auth_user_groups содержит словарь, где каждый ключ является id группы, членом которой является вошедший в систему текущий пользователь, а значение словаря соответствует роли группы.

otherwise

Декоратор auth.requires_login(), а также другие auth.requires_* декораторы принимают необязательный otherwise аргумент. Ему можно задать строку, которая перенаправляет пользователя, если регистрация не удалась, или вызывает объект. Он вызывается, если регистрация не удалась.

Ограничения на регистрацию

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

auth.settings.registration_requires_approval = True

Вы можете одобрить регистрацию через интерфейс администратора приложения. Посмотрите в таблицу auth_user. Ожидающие регистрации имеют поле registration_key, установленное на "ожидание" ("pending"). Если регистрация будет одобрена, то это поле не заполняется.

Через интерфейс appadmin, вы также можете запретить пользователю вход в систему. Найдите пользователя в таблице auth_user и задайте registration_key значение "blocked". "Блокированные" пользователи не могут войти в систему. Обратите внимание на то, что это будет препятствовать посетителю войти в систему, но это не заставит посетителя, который уже вошел в систему, выйти из системы. Вместо "blocked" может быть использовано слово "disabled", что предпочтительнее, с точно таким же поведением.

Вы можете также блокировать доступ к странице "register" полностью с этим выражением:

auth.settings.actions_disabled.append('register')

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

auth.settings.registration_requires_verification = True
auth.settings.login_after_registration = True

Другие методы Auth могут быть ограничены таким же образом.

Интеграция с OpenID, Facebook и т.д.

Janrain
OpenID
Facebook
LinkedIn
Google
MySpace
Flickr

Вы можете использовать web2py систему контроля доступа основанной на ролях и аутентификацию с другими службами, такими как OpenID, Facebook, LinkedIn, Google, Dropbox, MySpace, Flickr и т.д. Самый простой способ заключается в использовании JanRain Engage (ранее RPX) (Janrain.com).

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

JanRain Engage это сервис, который обеспечивает проверку подлинности промежуточного программного обеспечения (мидлваре). Вы можете зарегистрироваться на Janrain.com, зарегистрировать домен (имя вашего приложения) и набор URL-адресов, которые вы будете использовать с помощью предоставленного вам API ключа.

Теперь отредактируйте модель вашего приложения web2py и поместите следующие строки где-то после определения auth объекта:

from gluon.contrib.login_methods.rpx_account import RPXAccount
auth.settings.actions_disabled=['register','change_password','request_reset_password']
auth.settings.login_form = RPXAccount(request,
    api_key='...',
    domain='...',
    url = "http://your-external-address/%s/default/user/login" % request.application)

Первая строка импортирует новый метод входа, вторая строка отключает местную регистрацию, а третья строка просит web2py использовать метод RPX входа в систему. Вы должны вставить свой собственный api_key, предоставленный Janrain.com, домен, который вы выбрали при регистрации, и внешний url вашей страницы входа в систему. Получите затем войдите в janrain.com, затем перейдите на [Развертывание] [Настройки приложения]. На правой стороне есть "Application Info", The api_key называется "Ключ API (секретно)".

Домен является "Application Domain", без "https://" и без ".rpxnow.com/" Например: если вы зарегистрировали сайт как "secure.mywebsite.org", JanRain превращает его в домен приложения "https://secure-mywebsite.rpxnow.com".

image

Когда новый пользователь входит в систему в первый раз, то web2py создает новую db.auth_user запись, связанную с пользователем. Она будет использовать registration_id поле для хранения уникального идентификатора для пользователя. Большинство методов аутентификации также обеспечивается по username, email, first_name и last_name но это не гарантируется. Какие поля предоставляются зависит от способа входа в систему, выбранного пользователем. Если один и тот же пользователь входит в систему два раза, используя различные механизмы аутентификации (например, один раз с помощью OpenID и один раз с Facebook), то JanRain может не распознать его/ее, как одного и того же пользователя и выдаст различный registration_id.

Вы можете настроить сопоставление между данными, предоставленными JanRain и данными, хранящимися в db.auth_user. Вот пример для Facebook:

auth.settings.login_form.mappings.Facebook = lambda profile:            dict(registration_id = profile["identifier"],
                 username = profile["preferredUsername"],
                 email = profile["email"],
                 first_name = profile["name"]["givenName"],
                 last_name = profile["name"]["familyName"])

Ключи в словаре являются полями в db.auth_user, а значения являются данными входа в объекте профиля, предоставляемого JanRain. Посмотрите подробнее о последнем в онлайн документации JanRain.

JanRain будет также ведет статистику о входе в систему ваших пользователей.

Эта форма входа в систему полностью интегрирована с web2py управления доступом на основе ролей и вы все еще можете создавать группы, сделать пользователей членами группы, назначать права доступа, заблокировать пользователей и т.д.

Базовая служба JanRain бесплатно позволяет до 2500 уникальных пользователей для входа в год. Размещение большего числа пользователей требует обновления до одного из своих ярусов платных услуг.

Если вы предпочитаете не использовать JanRain и хотите использовать другой метод входа в систему (LDAP, PAM, Google, OpenID, OAuth/Facebook, LinkedIn и т.д.), то вы можете сделать это. API, чтобы сделать это описано далее в этой главе.

CAPTCHA и reCAPTCHA

CAPTCHA
reCAPTCHA
PIL
Во избежание спамеров и ботов при регистрации на вашем сайте, вы можете потребовать регистрацию CAPTCHA. web2py поддерживает reCAPTCHA[recaptcha] из коробки. Это потому, что reCAPTCHA очень хорошо разработана, свободная, доступная (она может прочитать слова для посетителей), проста в настройке и не требует установки каких-либо сторонних библиотек.

Вот что вам нужно сделать, чтобы использовать reCAPTCHA:

  • Зарегистрироваться на reCAPTCHA[recaptcha] и получить (PUBLIC_KEY, PRIVATE_KEY) пару для вашего аккаунта. Это только две строки.
  • Добавьте следующий код к вашей модели после определения объекта auth:
from gluon.tools import Recaptcha
auth.settings.captcha = Recaptcha(request,
    'PUBLIC_KEY', 'PRIVATE_KEY')

reCAPTCHA может не работать, если вы получаете доступ к веб-сайту как 'localhost' или '127.0.0.1', потому что она зарегистрирован только для работы с общедоступными веб-сайтами.

Конструктор Recaptcha принимает некоторые дополнительные аргументы:

Recaptcha(..., use_ssl=False, error_message='invalid', label='Verify:', options='')

Существует экспериментальный аргумент, ajax=True, который использует ajax API для рекапчи. Он может быть использован с любой рекапчей, но он был специально добавлен, чтобы позволить полям рекапча работать в LOAD формах (смотрите в главе 12 более подробную информацию о LOAD, который позволяет web2py подключить компоненты страницы с помощью AJAX). Это экспериментальная возможность, так как он может быть заменен автоматическим распознаванием, когда требуется Ajax.

Заметьте, что use_ssl=False по умолчанию.

options может быть строкой конфигурации, например options="theme:'white', lang:'fr'"

Более подробно: reCAPTCHA[recaptchagoogle] и кастомизация.

Если вы не хотите использовать reCAPTCHA, смотрите в определение класса Recaptcha в "gluon/tools.py", так как можно с легкостью использовать другие системы CAPTCHA.

Обратите внимание на то, что Recaptcha просто помощник, который расширяет DIV. Он генерирует фиктивное поле, которое проверяется с помощью службы reCaptcha и, следовательно, оно может быть использован в любой форме, в том числе используемых определенных FORM:

form = FORM(INPUT(...),Recaptcha(...),INPUT(_type='submit'))

Вы можете использовать его во всех типах SQLFORM путем инъекций:

form = SQLFORM(...) or SQLFORM.factory(...)
form.element('table').insert(-1,TR('',Recaptcha(...),''))

Кастомизация Auth

Вызов

auth.define_tables()

определяет все Auth таблицы, которые еще не были определены. Это означает, что если вы желаете сделать, то вы можете определить вашу собственную auth_user таблицу.

Есть несколько способов настроить авторизацию. Самым простым способом является добавление дополнительных полей:

## После auth = Auth(db)
auth.settings.extra_fields['auth_user']= [
  Field('address'),
  Field('city'),
  Field('zip'),
  Field('phone')]
## Перед auth.define_tables(username=True)

Вы можете объявить дополнительные поля не только для таблицы "auth_user", но и для других таблиц "auth_". Использование extra_fields является рекомендуемым способом, поскольку это не нарушит какой-либо внутренний механизм.

Другой способ сделать это, хотя на самом деле не рекомендуется, заключается в определении ваших auth таблиц самостоятельно. Если таблица объявлена перед auth.define_tables () она используется вместо установленной по умолчанию. Вот как это сделать:

## Перед auth = Auth(db)
db.define_table(
    auth.settings.table_user_name,
    Field('first_name', length=128, default=''),
    Field('last_name', length=128, default=''),
    Field('email', length=128, default='', unique=True), # обязательное
    Field('password', 'password', length=512,            # обязательное
          readable=False, label='Password'),
    Field('address'),
    Field('city'),
    Field('zip'),
    Field('phone'),
    Field('registration_key', length=512,                # обязательное
          writable=False, readable=False, default=''),
    Field('reset_password_key', length=512,              # обязательное
          writable=False, readable=False, default=''),
    Field('registration_id', length=512,                 # обязательное
          writable=False, readable=False, default=''))

## не забудьте валидаторы
custom_auth_table = db[auth.settings.table_user_name] # получаем custom_auth_table
custom_auth_table.first_name.requires =   IS_NOT_EMPTY(error_message=auth.messages.is_empty)
custom_auth_table.last_name.requires =   IS_NOT_EMPTY(error_message=auth.messages.is_empty)
custom_auth_table.password.requires = [IS_STRONG(), CRYPT()]
custom_auth_table.email.requires = [
  IS_EMAIL(error_message=auth.messages.invalid_email),
  IS_NOT_IN_DB(db, custom_auth_table.email)]

auth.settings.table_user = custom_auth_table # сообщаем auth использовать custom_auth_table

## Перед auth.define_tables()

Вы можете добавить любое поле, которое вы желаете, и вы можете изменить валидаторы, но вы не можете удалить поля, отмеченные как "обязательные" в данном примере.

Очень важно задать readable=False и writable=False для полей "password", "registration_key", "reset_password_key" и "registration_id", так как посетитель не должен иметь возможность вмешиваться в них.

Если добавить поле, называемое "username", оно будет использоваться вместо "email" для входа в систему. Если вы это сделаете, то вам нужно добавить валидатор:

auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username)

Обратите внимание, что Auth кэширует в сессии вошедшего пользователя, и это то, что вы получаете в auth.user, так что вам нужно очистить сессии для изменений дополнительных полей, которые будут отражены в нем.

Переименование Auth таблиц

Фактические имена таблиц Auth хранятся в

auth.settings.table_user_name = 'auth_user'
auth.settings.table_group_name = 'auth_group'
auth.settings.table_membership_name = 'auth_membership'
auth.settings.table_permission_name = 'auth_permission'
auth.settings.table_event_name = 'auth_event'

Имена таблиц могут быть изменены с помощью переназначения указанных выше переменных после определения объекта auth и перед определением таблицы Auth. Например:

auth = Auth(db)
auth.settings.table_user_name = 'person'
#...
auth.define_tables()

Фактические таблицы также могут быть ссылаемыми, независимо от их фактических имен,через

auth.settings.table_user
auth.settings.table_group
auth.settings.table_membership
auth.settings.table_permission
auth.settings.table_event

Примечание: auth.signature определяется при инициализации Auth, которая выполняется перед заданием пользовательских имен таблиц. Чтобы избежать этого выполните:

auth = Auth(db, signature=False)

В этом случае, auth.signature будет определена при вызове auth.define_tables(), через которую будут заданы имена пользовательских таблиц.

Другие методы входа и формы входа в систему

LDAP
PAM

Auth предоставляет несколько методов регистрации и крючки (hooks) для создания новых методов регистрации. Каждый поддерживаемый логин метод соответствует файлу в папке

gluon/contrib/login_methods/

Обратитесь к документации в самих файлах для каждого метода входа в систему, вот несколько примеров.

Прежде всего, нам необходимо провести различие между двумя типами альтернативных методов входа в систему:

  • логин методы, которые используют web2py форму входа в систему (хотя учетные данные проверяются за пределами web2py). Примером может служить LDAP.
  • логин методы, которые требуют внешнего единого входа на форме (примером может служить Google и Facebook).

В последнем случае, web2py никогда не получает учетные данные для входа, только токен входа в систему, выданный поставщиком услуг. Токен хранится в db.auth_user.registration_id.

Давайте рассмотрим примеры первого случая:

Базовый

Предположим у вас есть сервис аутентификации, например, на URL

https://basic.example.com

который принимает базовую аутентификацию доступа. Это означает, что сервер принимает HTTP-запросы с заголовком формы:

GET /index.html HTTP/1.0
Host: basic.example.com
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

где последняя строка является закодированной в base64 строкой username:password. Сервис отвечает 200 OK, если пользователь авторизован и 400, 401, 402, 403 или 404 в противном случае.

Вы хотите вводить имя пользователя и пароль, используя стандартную Auth регистрационную форму и проверять полномочия в отношении такого сервиса. Все, что вам нужно сделать, это добавить следующий код в приложение

from gluon.contrib.login_methods.basic_auth import basic_auth
auth.settings.login_methods.append(
    basic_auth('https://basic.example.com'))

Обратите внимание на то, что auth.settings.login_methods является списком из методов аутентификации, которые выполняются последовательно. По умолчанию он установлен на

auth.settings.login_methods = [auth]

Когда альтернативный метод добавляется в конец, например basic_auth, Auth сначала пытается провести в систему посетителя на основе содержания auth_user, и когда это не удается, он пробует следующий метод в списке. Если метод успешно провел в систему посетителя, и если auth.settings.login_methods[0]==auth, Auth выполняет следующие действия:

  • если пользователь не существует в auth_user, то создается новый пользователь и сохраняются имя пользователя/адрес электронной почты и пароли.
  • если пользователь существует в auth_user, но новый принятый пароль не соответствует старому хранимому паролю, то старый пароль заменяется новым (обратите внимание, что пароли всегда хранятся хэшированными, если не указано иное).

Если вы не хотите сохранять новый пароль в auth_user, то достаточно изменить порядок методов входа в систему, или удалить auth из списка. Например:

from gluon.contrib.login_methods.basic_auth import basic_auth
auth.settings.login_methods =     [basic_auth('https://basic.example.com')]

То же самое относится и к любому другому методу входа в систему, описанному здесь.

SMTP и Gmail

SMTP
Gmail

Вы можете проверить учетные данные для входа с помощью удаленного сервера SMTP, например, Gmail; т.е. при входе пользователя в случае, если адрес электронной почты и пароль, которые они предоставляют являются действительными учетными данными для доступа к серверу Gmail SMTP (smtp.gmail.com:587). Все, что необходимо, так это следующий код:

from gluon.contrib.login_methods.email_auth import email_auth
auth.settings.login_methods.append(
    email_auth("smtp.gmail.com:587", "@gmail.com"))

Первым аргументом email_auth является address:port SMTP сервера. Вторым аргументом является домен электронной почты.

Это работает с любым сервером SMTP, который требует аутентификации TLS.

TLS

Подключаемые модули аутентификации PAM
PAM

Аутентификация с использованием Подключаемых Модулей Аутентификации (PAM) работает как и в предыдущих случаях. Это позволяет web2py аутентифицировать пользователей с помощью аккаунтов операционной системы:

from gluon.contrib.login_methods.pam_auth import pam_auth
auth.settings.login_methods.append(pam_auth())
Облегчённый протокол доступа к каталогам LDAP
LDAP

Аутентификация с помощью LDAP работает очень похоже, как и в предыдущих случаях.

Чтобы использовать LDAP логин с MS Active Directory:

Active Directory

from gluon.contrib.login_methods.ldap_auth import ldap_auth
auth.settings.login_methods.append(ldap_auth(mode='ad',
   server='my.domain.controller',
   base_dn='ou=Users,dc=domain,dc=com'))

Чтобы использовать LDAP логин с Lotus Notes и Domino:

Lotus Notes
Domino

auth.settings.login_methods.append(ldap_auth(mode='domino',
   server='my.domino.server'))

Чтобы использовать LDAP логин с OpenLDAP (с UID):

OpenLDAP

auth.settings.login_methods.append(ldap_auth(server='my.ldap.server',
   base_dn='ou=Users,dc=domain,dc=com'))

Чтобы использовать LDAP логин с OpenLDAP (с CN):

auth.settings.login_methods.append(ldap_auth(mode='cn',
   server='my.ldap.server', base_dn='ou=Users,dc=domain,dc=com'))

Есть дополнительные параметры для выдачи web2py

  • прочитать дополнительные данные, такие как имя пользователя из LDAP
  • осуществлять контроль группы
  • ограничить доступ входа в систему.

Смотри документацию ldap_auth в web2py/gluon/contrib/login_methods/ldap_auth.py.

Google App Engine
GAE login

Аутентификация с помощью Google при запуске на Google App Engine требует пропуска web2py формы входа, перенаправления на страницу входа в Google, и обратно в случае успеха. Поскольку поведение отличается, от предыдущих примеров, то API немного отличается.

from gluon.contrib.login_methods.gae_google_login import GaeGoogleAccount
auth.settings.login_form = GaeGoogleAccount()
OpenID
OpenID

Ранее мы уже обсуждали интеграцию с JanRain (который поддерживает OpenID), и что это самый простой способ использовать OpenID. Тем не менее, иногда вам не хочется полагаться на службу третьей стороны, и вы хотите получить доступ к поставщику OpenID непосредственно от потребителя (ваше приложение).

Вот пример:

from gluon.contrib.login_methods.openid_auth import OpenIDAuth
auth.settings.login_form = OpenIDAuth(auth)

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

db.define_table('alt_logins',
    Field('username', length=512, default=''),
    Field('type', length =128, default='openid', readable=False),
    Field('user', self.table_user, readable=False))

в которой хранятся имена пользователей OpenId для каждого пользователя. Если вы хотите отображать OpenID для текущего авторизованного пользователя:

{{=auth.settings.login_form.list_user_openids()}}
OAuth2.0

OAuth
Facebook
Google
Twitter

Ранее мы уже обсуждали интеграцию с JanRain, но иногда вам не хочеться полагаться на службу третьей стороны, и вы хотите получить доступ к поставщику OAuth2.0 напрямую; например, Facebook, Linkedin, Twitter, Google все они предоставляют услуги аутентификации OAuth2.0.

web2py обрабатывает поток OAuth2.0 прозрачно, так что пользователь может быть проверен в отношении любого настроенного поставщика OAuth2.0 при входе в систему.

Кроме аутентификации OAuth 2.0 провайдер придумал собственный API, который позволяет предоставить любому web2py приложению доступ к пользовательским ресурсам с ограниченным доступом. Google, Twitter, Facebook и так далее, все имеют API-интерфейсы, которые могут быть легко доступны с помощью приложения web2py.

Следует подчеркнуть, что OAuth 2.0 ограничивается лишь аутентификацию и авторизацию (например CAS имеет больше функциональных возможностей), это означает, что каждый провайдер OAuth 2.0 предлагает разные способы, чтобы получить уникальный идентификатор из своей базы данных пользователей с помощью одного из своих API.

Конкретные методы хорошо описаны в соответствующей документации поставщика, они, как правило, заключаются в очень простом REST вызове. Именно поэтому для каждого провайдера OAuth2.0 существует необходимость в написании нескольких строк кода.

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

Есть несколько вещей, которые нужно знать при возникновении необходимости добавления нового поставщика OAuth2.0 к приложению:

1. URI авторизации; 2. Токен запрос URI; 3. идентификационный токен приложения и секретный пароль, полученные при регистрации нового приложения; 4. разрешения, которые поставщик должен предоставить приложению web2py, то есть "область" (смотрите в документации провайдера); 5. вызов API для получения UID аутентификации пользователя, как описано в документации поставщиков.

Пункты с 1 до 4 используются для инициализации конечной точки авторизации, используемой web2py для связи с провайдером OAuth2.0. Уникальный идентификатор извлекается web2py с вызовом метода get_user() когда это необходимо во время потока входа в систему; для этого необходим вызов API в пункте 5.

Это существенное изменение, которое должно быть сделано в вашей модели: a. импортировать класс OAuthAccount; b. определить производную реализацию OAuthClass; c. переопределить метод __init__() этого класса; d. переопределить метод get_user() этого класса. e. создать экземпляр класса с данными из пунктов 1-4 приведенного выше списка;

После того, как экземпляры класса будут созданы, и пользователь пройдет аутентификацию, приложение web2py может получить доступ к API-интерфейсу от поставщика в любое время, используя токен доступа OAuth 2.0 с помощью вызова метода accessToken() этого класса.

Ниже приведен пример того, что может быть использовано с Facebook. Это базовый пример использования Facebook Graph API, напомним, что, написав собственный метод get_user(), можно сделать много разных вещей. Пример показывает, как токен доступа OAuth 2.0 может быть использован при вызове удаленного API поставщика.

В первую очередь необходимо установить Facebook Python SDK.

Во-вторых, вам нужен следующий код в вашей модели:

## Определяем OAuth идентификатор приложения и секрет.
FB_CLIENT_ID='xxx'
FB_CLIENT_SECRET="yyyy"

## импорт необходимых модулей
try:
    import json
except ImportError:
    from gluon.contrib import simplejson as json
from facebook import GraphAPI, GraphAPIError
from gluon.contrib.login_methods.oauth20_account import OAuthAccount


## расширить класс OAUthAccount
class FaceBookAccount(OAuthAccount):
    """OAuth impl for FaceBook"""
    AUTH_URL="https://graph.facebook.com/oauth/authorize"
    TOKEN_URL="https://graph.facebook.com/oauth/access_token"

    def __init__(self):
        OAuthAccount.__init__(self, None, FB_CLIENT_ID, FB_CLIENT_SECRET,
                              self.AUTH_URL, self.TOKEN_URL,
                              scope='email,user_about_me,user_activities, user_birthday, user_education_history, user_groups, user_hometown, user_interests, user_likes, user_location, user_relationships, user_relationship_details, user_religion_politics, user_subscriptions, user_work_history, user_photos, user_status, user_videos, publish_actions, friends_hometown, friends_location,friends_photos',
                              state="auth_provider=facebook",
                              display='popup')
        self.graph = None

    def get_user(self):
        '''Returns the user using the Graph API.
        '''
        if not self.accessToken():
            return None

        if not self.graph:
            self.graph = GraphAPI((self.accessToken()))

        user = None
        try:
            user = self.graph.get_object("me")
        except GraphAPIError, e:
            session.token = None
            self.graph = None

        if user:
            if not user.has_key('username'):
                username = user['id']
            else:
                username = user['username']
                
            if not user.has_key('email'):
                email = '%s.fakemail' %(user['id'])
            else:
                email = user['email']    

            return dict(first_name = user['first_name'],
                        last_name = user['last_name'],
                        username = username,
                        email = '%s' %(email) )

## используя вышеуказанный класс строим новую форму входа
auth.settings.login_form=FaceBookAccount()
LinkedIn
LinkedIn

Ранее мы уже обсуждали интеграцию с JanRain (который имеет поддержку LinkedIn), и что это самый простой способ использовать OAuth. Тем не менее, когда-то вы не хочеться полагаться на службу третьей стороны, или может потребоваться получить доступ к LinkedIn напрямую, чтобы получить больше информации, чем JanRain обеспечивает.

Вот пример:

from gluon.contrib.login_methods.linkedin_account import LinkedInAccount
auth.settings.login_form=LinkedInAccount(request,KEY,SECRET,RETURN_URL)

LinkedInAccount требует установленного отдельно модуля "python-linkedin".

X509

Вы также можете войти в систему путем перехода на страницу сертификата x509 и ваши учетные данные будут извлечены из сертификата. Это требует M2Crypto установленного из

http://chandlerproject.org/bin/view/Projects/MeTooCrypto

После того как вы установили M2Cryption вы можете сделать:

from gluon.contrib.login_methods.x509_auth import X509Account
auth.settings.actions_disabled=['register','change_password','request_reset_password']
auth.settings.login_form = X509Account()

Теперь вы можете проверить подлинность в web2py передавая ваш сертификат x509. Как сделать это является браузер-зависимо, но, вероятно, у вас больше шансов использовать сертификаты для веб-служб. В этом случае вы можете использовать, например, cURL, чтобы попробовать вашу аутентификацию:

curl -d "firstName=John&lastName=Smith" -G -v --key private.key      --cert  server.crt https://example/app/default/user/profile

Это работает из коробки с Rocket ( web2py встроенный веб-сервер), но вам может потребоваться некоторая дополнительная работа по конфигурации на стороне веб-сервера, если вы используете другой веб-сервер. В частности, вам нужно указать свой веб-сервер, где сертификаты расположены на локальном хосте, и что указать ему необходимость проверки сертификатов, поступающих от клиентов. Как сделать это зависит от веб-сервера, и поэтому здесь не приведено.

Множественные формы входа в систему

Некоторые методы входа изменяют login_form, а некоторые этого не делают. Когда они делают это, то они не смогут сосуществовать. Тем не менее, некоторые сосуществуют, предоставляя несколько форм входа в систему на одной и той же странице. web2py обеспечивает способ сделать это. Ниже приведен пример смешивания обычного входа (аутентификации) и RPX входа в систему (janrain.com):

from gluon.contrib.login_methods.extended_login_form import ExtendedLoginForm
other_form = RPXAccount(request, api_key='...', domain='...', url='...')
auth.settings.login_form = ExtendedLoginForm(auth, other_form, signals=['token'])

Если сигналы устанавливаются и параметр в запросе совпадает с каким-либо из сигналом, то он вместо этого вернет вызов other_form.login_form.

other_form может обрабатывать несколько конкретных ситуаций, например, несколько шагов от OpenID входа в систему внутри other_form.login_form.

В противном случае он будет предоставлять нормальную форму входа в систему вместе с other_form.

Версионность записи

Вы можете использовать Auth для обеспечения полной версионности записи:

auth.enable_record_versioning(db,
    archive_db=None,
    archive_names='%(tablename)s_archive',
    current_record='current_record'):

Это говорит web2py создать архивную таблицу для каждой из таблиц в db и хранить копию каждой записи при модификации. Старая копия сохраняется. Новая копия не сохраняется.

Последние три параметра являются необязательными:

  • archive_db позволяет указать другую базу данных, где архивные таблицы должны быть сохранены. Установка в None означает тоже самое, что и установка в db.
  • archive_names предоставляет шаблон для именования каждой таблицы архива.
  • current_record указывает имя ссылочного поля для использования в таблице архива, чтобы обратиться к оригинальной, неизмененной, записи. Обратите внимание на то, что archive_db!=db когда ссылка на поле просто целочисленное поле, так как перекрестные ссылки базы данных не возможны.

Архивируются только таблицы с modified_by и modified_on полями (созданные, например, с помощью auth.signature).

Когда вы включите enable_record_versioning, если записи имеют is_active поле (также созданное auth.signature), то записи никогда не будут удалены, но они будут помечены is_active=False.

На самом деле, enable_record_versioning добавляет common_filter, чтобы каждая версионируемая таблица, отфильтровывала записи с is_active = False так что они по существу становятся невидимыми.

Если вы включаете enable_record_versioning, то вы не должны использовать auth.archive или crud.archive иначе вы будете в конечном итоге иметь дело с дублированием записей.

Эти функции делают явно то, что enable_record_versioning делает автоматически, и они считаются устаревшими.

Mail и Auth

Вы можете прочитать больше о web2py API для электронной почты и конфигурации электронной почты в Главе 8. Здесь мы ограничимся рассмотрением взаимодействия Mail и Auth.

Определим мэйлер с

from gluon.tools import Mail
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'

или просто используем мэйлер, представленный auth:

mail = auth.settings.mailer
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'

Вам нужно заменить mail.settings на правильные параметры для вашего SMTP-сервера. Задайте mail.settings.login = None если сервер SMTP не требует аутентификации. Если вы не хотите использовать TLS, то задайте mail.settings.tls = False

В Auth, по умолчанию, проверка по электронной почте отключена. Чтобы включить электронную почту, добавьте следующие строки в модели, где имеется определение auth:

auth.settings.registration_requires_verification = True
auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True
auth.messages.verify_email = 'Click on the link %(link)s to verify your email'
auth.messages.reset_password = 'Click on the link %(link)s to reset your password'

В двух auth.messages выше, вам возможно потребуется заменить часть URL строки на соответствующий полный URL-адрес действия. Это необходимо потому, что web2py может быть установлен за прокси-сервером, и он не сможет определить свои собственные общедоступные URL-адреса с абсолютной уверенностью. Приведенные выше примеры (которые являются значениями по умолчанию), однако, должны работать в большинстве случаев.

Двухшаговая верификация

Двухшаговая верификация (или двухфакторная аутентификация) является способом повышения безопасности аутентификации. Настройками добавляется дополнительный шаг в процессе входа в систему. На первом шаге, пользователям показывается стандартная форма ввода имени пользователя/пароля. Если они успешно прошли эту задачу, представив правильное имя пользователя и пароль, а также если двухфакторная аутентификация включена для пользователя, то сервер представит вторую форму перед входом их в систему.

image

Эта функция может быть включена отдельно для каждого пользователя:

Этот случай является хорошим примером для приложений, где пользователи могут включить/отключить двухфакторную аутентификацию самостоятельно.

Эта форма будет запрашивать у пользователя шестизначный код, который был отправлен на его аккаунт (сервер электронной почты, если имя пользователя и пароль верны). По умолчанию пользователь имеет 3 попытки ввода поверочного кода. Если код неверен после 3-х попыток, то второй шаг проверки рассматривается как несостоявшийся и пользователь должен завершить первую задачу (имя пользователя/пароль) снова.

  • Создайте группу (также известную как роль) для двухэтапной проверки. В этом примере она будет называться auth2step и описание может быть Двухшаговая верификация.
  • Включите пользователя в группу для этой роли.
  • Добавьте следующую настройку в модели, где вы создали и настроили свой объект аутентификации (вероятно, в модели db.py):
auth.settings.two_factor_authentication_group = "auth2step"
  • Не забудьте настроить сервер электронной почты в db.py
Эта функция может быть включена для всего приложения:

Эта форма будет запрашивать у пользователя шестизначный код, который был отправлен на его аккаунт (код на сервер электронной почты, если имя пользователя и пароль был правильным). По умолчанию пользователь будет иметь 3 попытки ввести код. Если код неверен после 3-х попыток, то второй шаг проверки рассматривается как несостоявшийся и пользователь должен завершить первую задачу (имя пользователя/пароль) снова.

auth.settings.auth_two_factor_enabled = True

Этот случай будет влиять по всех пользователей в приложении. Например, если IP вашего офиса 93.56.854.54 и вы не хотите двухфакторную аутентификацию из IP вашего офиса, то в своих моделях выполните следующее:

if request.env.remote_addr != '93.56.854.54':
    auth.settings.auth_two_factor_enabled = True
Другие варианты, которые могут быть применены для вышеприведенных примеров:
Пример 1: Если вы хотите отправить код по SMS вместо электронной почты. В вашей модели напишите:
def _sendsms(user, auth_two_factor):
    #написать процесс отправки кода auth_two_factor по SMS
    return  auth_two_factor

auth.settings.auth_two_factor_enabled = True
auth.messages.two_factor_comment = "Your code have been sent by SMS"
auth.settings.two_factor_methods = [lambda user, auth_two_factor: _sendsms(user, auth_two_factor)]

Для def _sendsms(...) получите два значения: user и auth_two_factor:

  • user: это строка со всеми параметрами пользователя. Вы можете получить доступ к его: user.email, user.first_name и т.д.
  • auth_two_factor: Строка, которая содержит код аутентификации.

Обратите внимание, что в случае, если вы хотите отправить SMS, вам нужно будет добавить дополнительное поле, например, phone в вашей таблице пользователей. В этом случае вы можете получить доступ к полю телефона, как user.phone. Более подробную информацию, как отправить SMS с web2py Emails-and-SMS

Пример 2: Если вы хотите отправить код с помощью SMS и/или создать собственный код:
def _sendsms(user, auth_two_factor):
    auth_two_factor = #write your own  algorithm to generate the code.
    #написать процесс отправки кода auth_two_factor по SMS
    return  auth_two_factor

auth.settings.two_factor_methods = [lambda user, auth_two_factor: _sendsms(user, auth_two_factor)]
Пример 3: Код генерируется внешним клиентом. Например, Mobile OTP Client:

MOTP (Мобильный одноразовый пароль) позволяет Вам войти с одноразовым паролем (OTP), который генерируется на клиенте motp, motp клиенты доступны практически для всех платформ. Чтобы узнать больше о OTP посетите вики Одноразовый пароль чтобы узнать больше посетите MOTP

В следующем примере мы будем использовать DroidOTP. Это бесплатное приложение, и его можно найти в Play Store для Android. После того, как вы установили:

  • Создайте новый профиль, например test
  • Инициализируйте секретный ключ встряхивания телефона.

В ваши модели скопируйте и вставьте:

#Перед определением таблиц, мы добавим некоторое дополнительное поле для AUTH_USER
auth.settings.extra_fields['auth_user'] = [
    Field('motp_secret', 'password', length=512, default='', label='MOTP Secret'),
    Field('motp_pin', 'string', length=128, default='', label='MOTP PIN')]

OFFSET = 60 #Be sure is the same in your OTP Client

#Установим session.auth_two_factor на None Поскольку код генерируется внешним приложением. 
# Это позволит избежать использования настроек по умолчанию и отправки кода по электронной почте.
def _set_two_factor(user, auth_two_factor):
    return None

def verify_otp(user, otp):
  import time
  from hashlib import md5
  epoch_time = int(time.time())
  time_start = int(str(epoch_time - OFFSET)[:-1])
  time_end = int(str(epoch_time + OFFSET)[:-1])
  for t in range(time_start - 1, time_end + 1):
     to_hash = str(t) + user.motp_secret + user.motp_pin
     hash = md5(to_hash).hexdigest()[:6]
     if otp == hash:
       return hash

auth.settings.auth_two_factor_enabled = True
auth.messages.two_factor_comment = "Verify your OTP Client for the code."
auth.settings.two_factor_methods = [lambda user, auth_two_factor: _set_two_factor(user, auth_two_factor)]
auth.settings.two_factor_onvalidation = [lambda user, otp: verify_otp(user, otp)]

Секретный ключ генерируется перед возникновением необходимости ввода его поле motp_secret вашего телефона. Секретный ключ не должен использоваться повторно, по соображениям безопасности. Выберите один PIN-код. Это может быть цифры, буквы или сочетание. Перейдите на ваш телефон, выберите свой профиль и введите PIN-код, который вы ввели до этого в форме. Вы получили код аутентификации для использования в вашем приложении!!

image

Обратите внимание, что для данного способа двухфакторной аутентификации телефон и сервер (где web2py приложение располагается) должны быть синхронизированы (по времени). Они могут быть в различных часовых поясах. Это происходит потому, что ОТР использует метку времени Unix. Он отслеживает время, как общую сумма секунд.

Некоторые дополнительные параметры конфигурации:

Установите свое собственное число попыток входа:

auth.setting.auth_two_factor_tries_left = 3

Сообщение для возврата в случае, если код неверен:

auth.messages.invalid_two_factor_code = 'Incorrect code. {0} more attempt(s) remaining.'

Чтобы настроить шаблон электронной почты:

auth.messages.retrieve_two_factor_code='Your temporary login code is {0}'
auth.messages.retrieve_two_factor_code_subject='Your temporary login code is {0}'

Для того, чтобы настроить двухфакторную форму:

auth.messages.label_two_factor = 'Authentication code'
auth.messages.two_factor_comment = 'The code was emailed to you and is required for login.'

Авторизация

После того, как новый пользователь зарегистрирован, создается новая группа, содержащая пользователя. Роль нового пользователя условно "user_[id]" где [id] это id вновь созданного пользователя. Создание группы может быть отключено с помощью

auth.settings.create_user_groups = None

хотя мы не рекомендуем делать это. Обратите внимание на то , что create_user_groups не является логическим значением (хотя он может быть False) но он по умолчанию:

auth.settings.create_user_groups="user_%(id)s"

Он хранит шаблон для имени группы, созданной для пользователя id.

Пользователи имеют членство в группах. Каждая группа идентифицируется по имени/роли. Группы имеют разрешения. Пользователи имеют разрешения благодаря группам, которым они принадлежат. По умолчанию, каждый пользователь становится членом своей собственной группы.

Вы можете также сделать

auth.settings.everybody_group_id = 5

чтобы любой новый пользователь автоматически становился членом группы № 5. Здесь 5 используется в качестве примера, и мы предполагаем, что группа уже была создана.

Вы можете создавать группы, давать членство и разрешения через appadmin или программно с помощью следующих методов:

auth.add_group('role', 'description')

возвращает идентификатор вновь созданной группы.

auth.del_group(group_id)

удаляет группу с group_id.

auth.del_group(auth.id_group('user_7'))

удаляет группу с ролью "user_7", т.е. группа однозначно связанную с пользователя по номером 7.

auth.user_group(user_id)

возвращает id группы, однозначно связанной с пользователем, идентифицированного через user_id.

auth.add_membership(group_id, user_id)

дает user_id членство в группе group_id. Если user_id не задан, то web2py предполагает текущего вошедшего в систему пользователя.

auth.del_membership(group_id, user_id)

аннулирует user_id членов группы group_id.. Если user_id не задан, то web2py предполагает текущего вошедшего в систему пользователя.

auth.has_membership(group_id, user_id, role)

проверяет, имеет ли user_id членство в группе group_id или группе с указанной ролью. Только group_id или role должны быть переданы функции, а не оба. Если user_id не задан, то web2py предполагает текущего вошедшего в систему пользователя.

auth.add_permission(group_id, 'name', 'object', record_id)

дает разрешение "name" (определяется пользователем) на объект "object" (также определяется пользователем) членам группы group_id. Если "object" является tablename, то разрешение может ссылаться на всю таблицу, посредством установки record_id в нулевое значение, либо разрешение может относиться к конкретной записи, указав record_id значение больше нуля. При передаче разрешений на таблицы, обычно используют имя разрешения в наборе ('create', 'read', 'update', 'delete', 'select') так как эти права понятные и могут быть реализованы с помощью API-интерфейсов CRUD.

Если group_id равен нулю, то web2py использует группу, однозначно ассоциированную с текущим вошедшим в систему пользователем.

Вы можете также использовать auth.id_group(role="...") чтобы получить id группы по переданному названию группы.

id_group

auth.del_permission(group_id, 'name', 'object', record_id)

аннулирует разрешение.

auth.has_permission('name', 'object', record_id, user_id)

проверяет, имеет ли пользователь идентифицированный по user_id членство в группе с запрашиваемым разрешением.

rows = db(auth.accessible_query('read', db.mytable, user_id))    .select(db.mytable.ALL)

возвращает все строки таблицы "mytable" с пользователем user_id, имеющим разрешение на чтение "read". Если user_id не задан, то web2py предполагает текущего вошедшего в систему пользователя. Запросы accessible_query(...) можно комбинировать с другими запросами, чтобы сделать более сложные запросы. accessible_query(...) является единственным Auth методом, который требует JOIN, поэтому он не работает на Google App Engine.

Предполагаются, следующие определения:

>>> from gluon.tools import Auth
>>> auth = Auth(db)
>>> auth.define_tables()
>>> secrets = db.define_table('secret_document', Field('body'))
>>> james_bond = db.auth_user.insert(first_name='James',
                                     last_name='Bond')

Вот пример:

>>> doc_id = db.secret_document.insert(body = 'top secret')
>>> agents = auth.add_group(role = 'Secret Agent')
>>> auth.add_membership(agents, james_bond)
>>> auth.add_permission(agents, 'read', secrets)
>>> print auth.has_permission('read', secrets, doc_id, james_bond)
True
>>> print auth.has_permission('update', secrets, doc_id, james_bond)
False

Декораторы

Наиболее распространенным способом проверки разрешения является использование декорирования функций вместо явного вызова вышеупомянутых методов, таким образом, что разрешения проверяются по отношению к вошедшему в систему посетителю. Вот некоторые примеры:

def function_one():
    return 'this is a public function'

@auth.requires_login()
def function_two():
    return 'this requires login'

@auth.requires_membership('agents')
def function_three():
    return 'you are a secret agent'

@auth.requires_permission('read', secrets)
def function_four():
    return 'you can read secret documents'

@auth.requires_permission('delete', 'any file')
def function_five():
    import os
    for file in os.listdir('./'):
        os.unlink(file)
    return 'all files deleted'

@auth.requires(auth.user_id==1 or request.client=='127.0.0.1', requires_login=True)
def function_six():
    return 'you can read secret documents'

@auth.requires_permission('add', 'number')
def add(a, b):
    return a + b

def function_seven():
    return add(3, 4)

Аргумент condition @auth.requires(condition) может быть вызываемым и если условие не является простым, то его лучше передать как вызов, чем как условие поскольку это будет быстрее, так как условие будет оцениваться только в случае необходимости. Например

@auth.requires(lambda: check_condition())
def action():
    ....

@auth.requires также принимает необязательный аргумент requires_login который по умолчанию True. Если задано значение False, то он не требует логина перед оценкой условия как истина/ложь. Условие может быть логическим значением или функцией, вычисляющей логическое значение.

Обратите внимание, что доступ ко всем функциям, за исключением первой ограничивается на основе разрешений, которые посетитель может иметь или не иметь.

Если посетитель не вошел в систему, то разрешение не может быть проверено; посетитель перенаправляется на страницу входа в систему, а затем обратно на страницу, которая требует разрешения.

Комбинирование требований

Время от времени необходимо комбинировать требования. Это может быть сделано с помощью общего requires декоратора, который принимает один аргумент, истинное или ложное условие. Например, чтобы дать доступ агентам, но только по вторникам:

@auth.requires(auth.has_membership(group_id='agents')               and request.now.weekday()==1)
def function_seven():
    return 'Hello agent, it must be Tuesday!'

или, что эквивалентно:

@auth.requires(auth.has_membership(role='Secret Agent')                and request.now.weekday()==1)
def function_seven():
    return 'Hello agent, it must be Tuesday!'

Авторизация и CRUD

Использование декораторов и/или явных проверок обеспечивает один из способов осуществления контроля доступа.

Другим способом осуществления контроля доступа является всегда использовать CRUD (в отличие от SQLFORM) для доступа к базе данных и поручить CRUD обеспечивать контроль доступа для таблиц базы данных и записей. Это делается путем связывания Auth и CRUD со следующим утверждением:

crud.settings.auth = auth

Это позволит оградит посетителя от доступа к любой из функций CRUD, если посетитель не вошел в систему и не имеет явный доступ. Например, чтобы разрешить пользователю возможность отправлять комментарии, но только обновлять свои собственные комментарии (при условии, что crud, Auth и db.comment определены):

def give_create_permission(form):
    group_id = auth.id_group('user_%s' % auth.user.id)
    auth.add_permission(group_id, 'read', db.comment)
    auth.add_permission(group_id, 'create', db.comment)
    auth.add_permission(group_id, 'select', db.comment)

def give_update_permission(form):
    comment_id = form.vars.id
    group_id = auth.id_group('user_%s' % auth.user.id)
    auth.add_permission(group_id, 'update', db.comment, comment_id)
    auth.add_permission(group_id, 'delete', db.comment, comment_id)

auth.settings.register_onaccept = give_create_permission
crud.settings.auth = auth

def post_comment():
   form = crud.create(db.comment, onaccept=give_update_permission)
   comments = db(db.comment).select()
   return dict(form=form, comments=comments)

def update_comment():
   form = crud.update(db.comment, request.args(0))
   return dict(form=form)

Вы также можете выбрать определенные записи (те, для которых вас есть "read" доступ):

def post_comment():
   form = crud.create(db.comment, onaccept=give_update_permission)
   query = auth.accessible_query('read', db.comment, auth.user.id)
   comments = db(query).select(db.comment.ALL)
   return dict(form=form, comments=comments)

Именами разрешений, навязываемыми через:

crud.settings.auth = auth

являются "read", "create", "update", "delete", "select", "impersonate".

Авторизация и скачивание

Использование декораторов и использование crud.settings.auth не привязывают авторизацию к файлам, загруженным с помощью обычной функции загрузки

def download(): return response.download(request, db)

Если кто-то пожелает сделать это, то необходимо явно объявить какие "upload" поля содержат файлы, нуждающиеся в контроле доступа при скачивании. Например:

db.define_table('dog',
   Field('small_image', 'upload'),
   Field('large_image', 'upload'))

db.dog.large_image.authorize = lambda record:    auth.is_logged_in() and    auth.has_permission('read', db.dog, record.id, auth.user.id)

Атрибут authorize поля upload может быть None (по умолчанию) или функцией, которая решает, может ли пользователь войти в систему и имеет разрешение на "read" текущей записи. В этом примере, не существует никаких ограничений на скачивание изображений, связанных по полю "small_image", но мы требуем контроля доступа на изображения, связанные по полю "large_image".

Контроль доступа и базовая аутентификация

Иногда может возникнуть необходимость выставить действия, имеющие декораторы контроля доступа, в качестве сервисов; т.е. вызывать их из программы или скрипта и еще иметь возможность использовать аутентификацию для проверки авторизации.

Auth включает логин с помощью базовой аутентификации:

auth.settings.allow_basic_login = True

С помощью этой установки, такое действие, как

@auth.requires_login()
def give_me_time():
    import time
    return time.ctime()

может быть вызвано, например, из оболочки командой:

wget --user=[username] --password=[password] --auth-no-challenge
    http://.../[app]/[controller]/give_me_time

Кроме того, можно войти в систему с помощью вызова auth.basic() вместо использования @auth декоратора:

def give_me_time():
    import time
    auth.basic()
    if auth.user:
        return time.ctime()
    else:
        return 'Not authorized'

Базовый логин часто является единственным вариантом для сервисов (описываемых в следующей главе), но он отключен по умолчанию.

Управление приложениями с помощью привилегированных пользователей (экспериментальный)

Обычно функции администратора, такие как определение пользователей и групп управляются администратором сервера. Тем не менее, может потребоваться группа привилегированных пользователей, чтобы иметь права администратора для конкретного приложения.

Это возможно с версиями web2py, идущими после v2.5.1 (Обновление существующего приложения требует нового контроллера appadmin и нового представления appadmin.html, скопированных из welcome приложения. Кроме того, приложениям, созданным до web2py v2.6 нужен новый javascript файл в welcome/static/js/web2py.js)

Концепция допускает различные параметры управления, каждый из которых позволяет группе пользователей редактировать определенный набор таблиц в этом приложении.

Например: Во-первых, создайте группу (также известную как роль) для ваших привилегированных пользователей. В данном примере она будет называться admin. Дайте пользователю членство в этой роли.

Во-вторых, придумайте название, чтобы описать эту настройку управления, такое как db_admin.

Добавьте следующую настройку в модель, в которой вы создали и настроили ваш объект аутентификации (возможно, в модели db):

auth.settings.manager_actions = dict(db_admin=dict(role='admin',heading='Manage Database',tables = db.tables))

Пункт меню имеет URL-адрес, как показано ниже, которому передано имя настройки управления в качестве аргумента:

URL('appadmin','manage',args=['db_admin'])

Этот URL отображается как /appadmin/manage/auth.

Продвинутое использование

Этот механизм позволяет использовать несколько параметров управления; каждый дополнительный параметр управления является еще одним ключом, определенным в auth.settings.manager_actions.

Например, вы можете захотеть, чтобы группа пользователей (например, 'Super') имела доступ к каждой таблице в настройке управления под названием "db_admin", а другая группа (например, "Content Manager '), чтобы имела административный доступ к таблицам, касающимся содержания в параметре управления под названием "content_admin".

Это может быть настроен, как это:

auth.settings.manager_actions = dict(
    db_admin=dict(role='Super', heading='Manage Database', tables=db.tables),
    content_admin=dict(role='Content Manager', tables=[content_db.articles, content_db.recipes, content_db.comments])
    content_mgr_group_v2 = dict(role='Content Manager v2', db=content_db,
        tables=['articles','recipes','comments'],
        smartgrid_args=dict(
           DEFAULT=dict(maxtextlength=50,paginate=30), 
           comments=dict(maxtextlength=100,editable=False)
        )
     )

(Ключ заголовка не является обязательным. Если отсутствует, то будет использоваться смарт-умолчание)

Затем можно сделать два новых пункта меню с помощью этих URL-адресов:

URL('appadmin','manage',args=['db_admin'])
URL('appadmin','manage',args=['content_admin'])

Настройка управления под названием "content_mgr_group_v2" демонстрирует некоторые более продвинутые возможности. Ключ smartgrid_args передается SmartGrid, используемой для редактирования или просмотра таблиц. Помимо специального ключа ПО УМОЛЧАНИЮ, имена таблиц передаются в качестве ключей (например, таблицы под названием "comments"). Синтаксис в этом примере такой, имена таблиц передаются в виде списка строк, с помощью ключа db= content_db чтобы указать базу данных.

Ручная аутентификация

Иногда вам хочется реализовать свою собственную логику и сделать "вручную" вход пользователя. Это также может быть сделано путем вызова функции:

user = auth.login_bare(username,password)

login_bare возвращает пользователя, если пользователь существует и пароль действителен, в противном случае он возвращает значение False. username является электронной почтой, если таблица "auth_user" не имеет поле "username".

Настройки аутентификации и сообщения

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

Следующее необходимо указать на объект gluon.tools.Mail, чтобы разрешить auth отправлять электронную почту:

auth.settings.mailer = None

Подробнее о настройке почты здесь: Mail и Auth

Следующим должно быть имя контроллера, который определяет user действие:

auth.settings.controller = 'default'

Следующее было весьма важным параметром в более старых версиях web2py:

auth.settings.hmac_key = None

Когда он был установлен в что-то вроде "sha512:a-pass-phrase" и передается в CRYPT валидатор для поля "password" из auth_user таблицы, обеспечивая алгоритм и передавая фразу, используемую для хэширования паролей. Однако, web2py больше не нуждается в этом параметре, так как он обрабатывает это автоматически.

По умолчанию, Auth также требует минимальную длину пароля 4. Это может быть изменено:

auth.settings.password_min_length = 4

Чтобы отключить действие добавьте в конец свое имя к этому списку:

auth.settings.actions_disabled = []

Например:

auth.settings.actions_disabled.append('register')

отключит регистрацию.

Если вы хотите получать по электронной почте для проверки регистрации, то задайте парамтру True:

auth.settings.registration_requires_verification = False

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

auth.settings.login_after_registration = False

Если новички должны ждать одобрения прежде чем они смогут войти в систему, то установите для этого параметра True:

auth.settings.registration_requires_approval = False

Утверждение заключается в установке registration_key=='' с помощью appadmin или программно.

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

auth.settings.create_user_groups = True

Следующие параметры определяют альтернативные методы входа и формы входа, как обсуждалось ранее:

auth.settings.login_methods = [auth]
auth.settings.login_form = auth

Вы хотите разрешить базовый вход?

auth.settings.allows_basic_login = False

Ниже приведен URL в login действии:

auth.settings.login_url = URL('user', args='login')

Если пользователь попытается получить доступ к странице регистрации, но уже вошел в систему, то он будет перенаправлен на этот URL:

auth.settings.logged_url = URL('user', args='profile')

Это должно указывать на URL действие загрузки, в случае, если профиль содержит изображения:

auth.settings.download_url = URL('download')

Этим необходимо указывать на URL, куда вы хотите перенаправить пользователей после различных возможных auth действий (в случае, если нет отсылающего):

Примечание: Если ваше приложение базируется на стандартном скаффолдинг приложении Welcome, то вы используете auth.navbar. Чтобы полученные указанные ниже параметры вступили в силу, вам нужно отредактировать layout.html и установить аргументу referrer_actions=None. auth.navbar(mode='dropdown',referrer_actions=None)

Кроме того, можно сохранить referrer_actions для некоторых auth событий. Например

auth.navbar(referrer_actions=['login', 'profile'])

Если поведение по умолчанию остается без изменений, то auth.navbar использует URL параметр _next, и использует это, чтобы отправить пользователя назад к отсылаемой странице. Однако, если авто-отсылаемое поведение navbar по умолчанию изменено, то настройки ниже вступят в силу.

auth.settings.login_next = URL('index')
auth.settings.logout_next = URL('index')
auth.settings.profile_next = URL('index')
auth.settings.register_next = URL('user', args='login')
auth.settings.retrieve_username_next = URL('index')
auth.settings.retrieve_password_next = URL('index')
auth.settings.change_password_next = URL('index')
auth.settings.request_reset_password_next = URL('user', args='login')
auth.settings.reset_password_next = URL('user', args='login')
auth.settings.verify_email_next = URL('user', args='login')

Если посетитель не вошел в систему, и вызывает функцию, которая требует аутентификации, то пользователь перенаправляется на auth.settings.login_url который по умолчанию URL('default','user/login'). Можно заменить это поведение, путем переопределения:

on_failed_authentication
auth.settings.on_failed_authentication = lambda url: redirect(url)

Это функция вызывается для перенаправления. Аргумент url`, который передается этой функции, является URL для страницы входа.

Если посетитель не имеет разрешения на доступ к данной функции, то посетитель перенаправляется на URL, определенный через

on_failed_authorization
auth.settings.on_failed_authorization =     URL('user',args='on_failed_authorization')

Вы можете изменить эту переменную и перенаправить пользователя в другое место.

Часто on_failed_authorization является URL, но это может быть функция, которая возвращает URL и она будет вызываться на ошибки авторизации.

Это списки обратных вызовов, которые должны быть выполнены вслед за формой проверки для каждого из соответствующих действий перед любым вводом-выводом базы данных:

auth.settings.login_onvalidation = []
auth.settings.register_onvalidation = []
auth.settings.profile_onvalidation = []
auth.settings.retrieve_password_onvalidation = []
auth.settings.reset_password_onvalidation = []

Каждый обратный вызов должен быть функцией, которая принимает form объект, и она может изменить атрибуты объекта формы перед выполнением IO базы данных.

Это списки обратных вызовов, которые должны быть выполнены после IO базы данных, выполняется также и перед перенаправлением:

auth.settings.login_onaccept = []
auth.settings.register_onaccept = []
auth.settings.profile_onaccept = []
auth.settings.verify_email_onaccept = []

Вот пример:

auth.settings.register_onaccept.append(lambda form:   mail.send(to='you@example.com',subject='new user',
             message='new user email is %s'%form.vars.email))

Вы можете включить CAPTCHA  для любого из auth действий:

auth.settings.captcha = None
auth.settings.login_captcha = None
auth.settings.register_captcha = None
auth.settings.retrieve_username_captcha = None
auth.settings.retrieve_password_captcha = None

Если .captcha настройки указывает на gluon.tools.Recaptcha, то все формы, для которых соответствующий параметр (например, .login_captcha) установлен в None будут иметь капчу, в то время как те формы, для которых соответствующий параметр установлен в значение False, не будут иметь капчу. Если вместо этого, .captcha установлен в None, то только те формы, которые имеют соответствующий параметр установленный на объект gluon.tools.Recaptcha будут иметь CAPTCHA, а другие не будут.

Это время истечения сессии входа в систему:

auth.settings.expiration = 3600  # секунд

Вы можете изменить имя поля ввода пароля (в Firebird, например, "password" является ключевым словом и не может быть использован для имени поля):

auth.settings.password_field = 'password'

Обычно форма входа в систему пытается проверить электронную почту. Это может быть отключено путем изменения этого параметра:

auth.settings.login_email_validate = True

Вы хотите показывать идентификатор записи в профиле редактирования?

auth.settings.showid = False

Для пользовательских форм вы можете отключить автоматическое оповещение об ошибках в формах:

auth.settings.hideerror = False

Кроме того, для пользовательских форм вы можете изменить стиль:

auth.settings.formstyle = 'table3cols'

(он может быть "bootstrap3_inline", "table3cols", "table2cols", "divs" и "ul"; для всех вариантов, смотрите gluon/sqlhtml.py)

И вы можете установить разделитель для генерируемых auth форм:

auth.settings.label_separator =':'

По умолчанию форма входа в систему дает возможность продлить нахождение в системе через "запомнить меня" вариант. Время окончания срока может быть изменено или опция отключена с помощью этих параметров:

auth.settings.long_expiration = 3600*24*30 # один месяц
auth.settings.remember_me_form = True

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

auth.messages.submit_button = 'Submit'
auth.messages.verify_password = 'Verify Password'
auth.messages.delete_label = 'Check to delete:'
auth.messages.function_disabled = 'Function disabled'
auth.messages.access_denied = 'Insufficient privileges'
auth.messages.registration_verifying = 'Registration needs verification'
auth.messages.registration_pending = 'Registration is pending approval'
auth.messages.login_disabled = 'Login disabled by administrator'
auth.messages.logged_in = 'Logged in'
auth.messages.email_sent = 'Email sent'
auth.messages.unable_to_send_email = 'Unable to send email'
auth.messages.email_verified = 'Email verified'
auth.messages.logged_out = 'Logged out'
auth.messages.registration_successful = 'Registration successful'
auth.messages.invalid_email = 'Invalid email'
auth.messages.unable_send_email = 'Unable to send email'
auth.messages.invalid_login = 'Invalid login'
auth.messages.invalid_user = 'Invalid user'
auth.messages.is_empty = "Cannot be empty"
auth.messages.mismatched_password = "Password fields don't match"
auth.messages.verify_email = ...
auth.messages.verify_email_subject = 'Password verify'
auth.messages.username_sent = 'Your username was emailed to you'
auth.messages.new_password_sent = 'A new password was emailed to you'
auth.messages.password_changed = 'Password changed'
auth.messages.retrieve_username = 'Your username is: %(username)s'
auth.messages.retrieve_username_subject = 'Username retrieve'
auth.messages.retrieve_password = 'Your password is: %(password)s'
auth.messages.retrieve_password_subject = 'Password retrieve'
auth.messages.reset_password = ...
auth.messages.reset_password_subject = 'Password reset'
auth.messages.invalid_reset_password = 'Invalid reset password'
auth.messages.profile_updated = 'Profile updated'
auth.messages.new_password = 'New password'
auth.messages.old_password = 'Old password'
auth.messages.group_description =     'Group uniquely assigned to user %(id)s'
auth.messages.register_log = 'User %(id)s Registered'
auth.messages.login_log = 'User %(id)s Logged-in'
auth.messages.logout_log = 'User %(id)s Logged-out'
auth.messages.profile_log = 'User %(id)s Profile updated'
auth.messages.verify_email_log = 'User %(id)s Verification email sent'
auth.messages.retrieve_username_log = 'User %(id)s Username retrieved'
auth.messages.retrieve_password_log = 'User %(id)s Password retrieved'
auth.messages.reset_password_log = 'User %(id)s Password reset'
auth.messages.change_password_log = 'User %(id)s Password changed'
auth.messages.add_group_log = 'Group %(group_id)s created'
auth.messages.del_group_log = 'Group %(group_id)s deleted'
auth.messages.add_membership_log = None
auth.messages.del_membership_log = None
auth.messages.has_membership_log = None
auth.messages.add_permission_log = None
auth.messages.del_permission_log = None
auth.messages.has_permission_log = None
auth.messages.label_first_name = 'First name'
auth.messages.label_last_name = 'Last name'
auth.messages.label_username = 'Username'
auth.messages.label_email = 'E-mail'
auth.messages.label_password = 'Password'
auth.messages.label_registration_key = 'Registration key'
auth.messages.label_reset_password_key = 'Reset Password key'
auth.messages.label_registration_id = 'Registration identifier'
auth.messages.label_role = 'Role'
auth.messages.label_description = 'Description'
auth.messages.label_user_id = 'User ID'
auth.messages.label_group_id = 'Group ID'
auth.messages.label_name = 'Name'
auth.messages.label_table_name = 'Table name'
auth.messages.label_record_id = 'Record ID'
auth.messages.label_time_stamp = 'Timestamp'
auth.messages.label_client_ip = 'Client IP'
auth.messages.label_origin = 'Origin'
auth.messages.label_remember_me = "Remember me (for 30 days)"

add|del|has логи членов позволяют использовать "%(user_id)s" и "%(group_id)s". add|del|has логи разрешений позволяют использовать "%(user_id)s", "%(name)s", "%(table_name)s", и "%(record_id)s".

Центральный сервис аутентификации

CAS
authentication

web2py обеспечивает поддержку аутентификацию сторонних производителей и единого входа в систему. Здесь мы обсуждаем центральный сервис аутентификации (CAS), который является отраслевым стандартом, и оба, клиент и сервер, встроены в web2py.

CAS является открытым протоколом распределенной аутентификации и он работает следующим образом: Когда посетитель приходит на наш веб-сайт, то наше приложение проверяет в сессии, если уже аутентифицированный пользователь (например, с помощью объекта session.token). Если пользователь не аутентифицирован, контроллер перенаправляет посетителя от оснастки CAS, где пользователь может входить в систему регистрироваться и управлять своими учетными данными (имя, адрес электронной почты и пароль). Если пользователь регистрируется, то он получает электронную почту, и регистрация считается не завершенной, пока он не ответит на электронную почту. После того как пользователь успешно зарегистрирован и вошел в систему, оснастка CAS перенаправляет пользователя к нашему приложению вместе с ключом. Наше приложение использует ключ, чтобы получить учетные данные пользователя с помощью запроса HTTP в фоновом режиме на сервере CAS.

Используя этот механизм, несколько приложений могут использовать единый вход в систему с помощью одного сервера CAS. Сервер, предоставляющий аутентификацию, будем называть провайдером услуг. Приложения, стремящиеся аутентифицировать посетителей, называются потребителями услуг.

CAS похож на OpenID, с одним основным отличием. В случае с OpenID, посетитель выбирает провайдера услуг. В случае с CAS, наше приложение делает этот выбор самостоятельно, тем самым делая CAS более безопасным.

Запуск поставщика web2py CAS является столь же легким, как и копирование скаффолдинг-приложения. Фактически любое web2py приложение выставляет действие

## в приложении поставщика
def user(): return dict(form=auth())

является провайдером CAS 2.0 и его услуги могут быть доступны по URL

http://.../provider/default/user/cas/login
http://.../provider/default/user/cas/validate
http://.../provider/default/user/cas/logout

(будем считать, что это приложение будет называться "провайдер").

Вы можете получить доступ этому сервису любого другого веб-приложения (потребитель) путем простого делегирования аутентификации провайдеру:

## в потребительском приложении
auth = Auth(db,cas_provider = 'http://127.0.0.1:8000/provider/default/user/cas')

При посещении URL-адреса входа в систему приложения потребителя, оно будет перенаправлять вас к приложению провайдера, которое будет выполнять аутентификации и перенаправит обратно к потребителю. Все процессы регистрации, выхода из системы, изменение пароля, восстановление пароля, должны быть завершены в приложении поставщика. Запись о вошедшем в систему пользователе будет создан на стороне потребителя, так что вы добавляете дополнительные поля и имеете локальный профиль. Благодаря CAS 2.0 все поля, которые являются читаемыми на провайдере и имеют соответствующее поле в auth_user таблице потребителя, будут автоматически скопированы.

Auth(...,cas_provider='...') работает со сторонними провайдерами и поддерживает CAS 1.0 и 2.0. Версия определяется автоматически. По умолчанию он строит URL-адреса провайдера из базы (cas_provider URL-адрес выше) путем добавления

/login
/validate
/logout

Они могут быть изменены в потребителе и в поставщике

## в потребительском или провайдерском приложении (должны совпадать)
auth.settings.cas_actions['login']='login'
auth.settings.cas_actions['validate']='validate'
auth.settings.cas_actions['logout']='logout'

Если вы хотите подключиться к провайдеру web2py CAS из другого домена, то вам необходимо включить его путем добавления в список разрешенных доменов:

## в провайдерском приложении
auth.settings.cas_domains.append('example.com')

Использование web2py для авторизации не web2py приложений

Это возможно, но зависит от веб-сервера. Здесь будем предполагать два приложения, работающие под одним тем же веб-сервером: Apache с mod_wsgi. Одним из приложений является web2py с приложением, предоставляющим контроль доступа через Auth. Другим может быть CGI скрипт, программа PHP или что-нибудь еще. Мы хотим поручить веб-серверу запрашивать разрешения первого приложения, когда клиент запрашивает доступ к последнему.

В первую очередь нам нужно изменить приложение web2py и добавить следующий контроллер:

def check_access():
    return 'true' if auth.is_logged_in() else 'false'

которая возвращает true, если пользователь вошел в систему и false в противном случае. Теперь запустите процесс web2py в фоновом режиме:

nohup python web2py.py -a '' -p 8002

Порт 8002 является обязательным, и нет необходимости во включении администратора, так как нет пароля администратора.

Затем нам нужно отредактировать конфигурационный файл Apache (например, "/etc/apache2/sites-available/default") и проинструктировать Apache так, что когда не-web2py программа вызывается, то он должен вызвать вышеупомянутое check действие вместо этого и только тогда, когда оно возвращает true оно должно продолжаться и отвечать на запрос, в противном случае следует отказать в доступе.

Поскольку web2py и не-web2py приложения запускаются под одним и тем же доменом, если пользователь вошел в приложение web2py, то web2py куки сессии будут переданы в Apache даже при запросе другого приложения и разрешают верификацию учетных данных.

Для достижения этого нам нужен скрипт, "web2py/scripts/access.wsgi", который может сыграть этот трюк. Скрипт поставляется с web2py. Все, что нам нужно сделать, это сказать, Apache вызвать этот скрипт, URL-адрес приложения нуждающегося в контроле доступа, а также расположение скрипта:

<VirtualHost *:80>
   WSGIDaemonProcess web2py user=www-data group=www-data
   WSGIProcessGroup web2py
   WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py

   AliasMatch ^myapp/path/needing/authentication/myfile /path/to/myfile
   <Directory /path/to/>
     WSGIAccessScript /path/to/web2py/scripts/access.wsgi
   </Directory>
</VirtualHost>

здесь "^myapp/path/needing/authentication/myfile" это регулярное выражение, которое должно соответствовать входящему запросу и "/path/to/" это абсолютное расположение папки web2py.

Скрипт "access.wsgi" содержит следующую строку:

URL_CHECK_ACCESS = 'http://127.0.0.1:8002/%(app)s/default/check_access'

которая указывает на приложение web2py, которое мы запрашиваем, но вы можете изменить его, чтобы указать на конкретное приложение, работающее на другом порту, кроме 8002.

Вы также можете изменить check_access() действие и сделать его логику более сложной. Это действие может получить URL, который был первоначально запрошен, используя переменную окружения

request.env.request_uri

и вы можете реализовать более сложные правила:

def check_access():
    if not auth.is_logged_in():
       return 'false'
    elif not user_has_access(request.env.request_uri):
       return 'false'
    else:
       return 'true'
 top