Добавление нового бэкенда

Adding new backends is quite easy. Usually just all that’s required is to add a class with a couple settings and method overrides to retrieve user data from a services API. Follow the details below:

Общие атрибуты

First, let’s check the common attributes for all backend types.

name = ''

Any backend needs a name, usually the popular name of the service is used, like facebook, twitter, etc. It must be unique, otherwise another backend can take precedence if it’s listed before in the AUTHENTICATION_BACKENDS setting.

ID_KEY = None

Defines the attribute in the service response that identifies the user as unique to the service, the value is later stored in the uid attribute in the UserSocialAuth instance.

REQUIRES_EMAIL_VALIDATION = False

Флаг бэкенда для принудительной проверки электронной почты во время конвейера (если соответствующий конвейер social_core.pipeline.mail.mail_validation был включен).

EXTRA_DATA = None

During the auth process some basic user data is returned by the provider or retrieved by the user_data() method which usually is used to call some API on the provider to retrieve it. This data will be stored in the UserSocialAuth.extra_data attribute, but to make it accessible under some common names on different providers, this attribute defines a list of tuples in the form (name, alias) where name is the key in the user data (which should be a dict instance) and alias is the name to store it on extra_data.

ACCESS_TOKEN_METHOD = 'GET'

Specifying the method type required to retrieve your access token if it’s not the default GET request.

OAuth

OAuth1 and OAuth2 provide some common definitions based on the shared behavior during the auth process. For example, a successful API response from AUTHORIZATION_URL usually returns some basic user data like a user Id.

Общие атрибуты

name

Определяет имя бэкенда и идентифицирует его в процессе аутентификации. Имя используется в URL /login/<backend name> и /complete/<backend name>.

ID_KEY = 'id'

The default key name where the user identification field is defined, it’s used in the auth process when some basic user data is returned. This Id is stored in the UserSocialAuth.uid field and this, together with the UserSocialAuth.provider field, is used to uniquely identify a user association.

SCOPE_PARAMETER_NAME = 'scope'

The scope argument is used to tell the provider the API endpoints you want to call later, it’s a permissions request granted over the access_token later retrieved. The default value is scope since that’s usually the name used in the URL parameter, but can be overridden if needed.

DEFAULT_SCOPE = None

Some providers give nothing about the user but some basic data like the user Id or an email address. The default scope attribute is used to specify a default value for the scope argument to request those extra bits.

SCOPE_SEPARATOR = ' '

The scope argument is usually a list of permissions to request, the list is joined with a separator, usually just a blank space, but this can differ from provider to provider. Override the default value with this attribute if it differs.

OAuth2

Бэкенды OAuth2 довольно просты в реализации: всего несколько настроек, переопределение метода, и все готово к работе.

Ключевыми моментами этого бэкенда являются:

AUTHORIZATION_URL

T

ACCESS_TOKEN_URL

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

REFRESH_TOKEN_URL

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

RESPONSE_TYPE

Тип ответа, ожидаемый в процессе auth, значение по умолчанию code, как диктует определение OAuth2. Переопределите его, если значение по умолчанию не подходит для реализации провайдера.

STATE_PARAMETER

OAuth2 определяет, что параметр state может быть передан для подтверждения процесса, это своего рода проверка CSRF, чтобы избежать атак типа «человек посередине». Некоторые не распознают его или не возвращают, что сделает процесс auth недействительным. В этом случае установите этот атрибут в False.

REDIRECT_STATE

Для тех провайдеров, которые не распознают параметр state, приложение может добавить аргумент redirect_state к redirect_uri, чтобы имитировать его. Установите значение False, если провайдеру нравится проверять значение redirect_uri и этот параметр делает эту проверку недействительной.

Пример кода:

from social_core.backends.oauth import BaseOAuth2

class GitHubOAuth2(BaseOAuth2):
    """GitHub OAuth authentication backend"""
    name = 'github'
    AUTHORIZATION_URL = 'https://github.com/login/oauth/authorize'
    ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token'
    ACCESS_TOKEN_METHOD = 'POST'
    SCOPE_SEPARATOR = ','
    EXTRA_DATA = [
        ('id', 'id'),
        ('expires', 'expires')
    ]

    def get_user_details(self, response):
        """Return user details from GitHub account"""
        return {'username': response.get('login'),
                'email': response.get('email') or '',
                'first_name': response.get('name')}

    def user_data(self, access_token, *args, **kwargs):
        """Loads user data from service"""
        url = 'https://api.github.com/user?' + urlencode({
            'access_token': access_token
        })
        return self.get_json(url)

OAuth1

Процесс OAuth1 немного сложнее, Twitter Docs объясняет его достаточно хорошо. Помимо атрибутов AUTHORIZATION_URL и ACCESS_TOKEN_URL, необходим третий, используемый при запуске процесса.

REQUEST_TOKEN_URL = ''

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

Пример кода:

from xml.dom import minidom

from social_core.backends.oauth import ConsumerBasedOAuth


class TripItOAuth(ConsumerBasedOAuth):
    """TripIt OAuth authentication backend"""
    name = 'tripit'
    AUTHORIZATION_URL = 'https://www.tripit.com/oauth/authorize'
    REQUEST_TOKEN_URL = 'https://api.tripit.com/oauth/request_token'
    ACCESS_TOKEN_URL = 'https://api.tripit.com/oauth/access_token'
    EXTRA_DATA = [('screen_name', 'screen_name')]

    def get_user_details(self, response):
        """Return user details from TripIt account"""
        try:
            first_name, last_name = response['name'].split(' ', 1)
        except ValueError:
            first_name = response['name']
            last_name = ''
        return {'username': response['screen_name'],
                'email': response['email'],
                'fullname': response['name'],
                'first_name': first_name,
                'last_name': last_name}

    def user_data(self, access_token, *args, **kwargs):
        """Return user data provided"""
        url = 'https://api.tripit.com/v1/get/profile'
        request = self.oauth_request(access_token, url)
        content = self.fetch_response(request)
        try:
            dom = minidom.parseString(content)
        except ValueError:
            return None

        return {
            'id': dom.getElementsByTagName('Profile')[0].getAttribute('ref'),
            'name': dom.getElementsByTagName(
                'public_display_name')[0].childNodes[0].data,
            'screen_name': dom.getElementsByTagName(
                'screen_name')[0].childNodes[0].data,
            'email': dom.getElementsByTagName(
                'is_primary')[0].parentNode.getElementsByTagName(
                'address')[0].childNodes[0].data,
        }

OpenID

OpenID is far simpler that OAuth since it’s used for authentication rather than authorization (regardless it’s used for authorization too).

Обычно требуется один атрибут - конечная точка URL-адреса аутентификации.

URL = ''

OpenID endpoint where to redirect the user.

Sometimes the URL is user dependant, like in myOpenID where the URL is https://<user handler>.myopenid.com. For those cases where the user must input it’s handle (or full URL). The backend must override the openid_url() method to retrieve it and return a full URL to where the user will be redirected.

Пример кода:

from social_core.backends.open_id import OpenIdAuth
from social_core.exceptions import AuthMissingParameter


class LiveJournalOpenId(OpenIdAuth):
    """LiveJournal OpenID authentication backend"""
    name = 'livejournal'

    def get_user_details(self, response):
        """Generate username from identity url"""
        values = super(LiveJournalOpenId, self).get_user_details(response)
        values['username'] = values.get('username') or \
                             urlparse.urlsplit(response.identity_url)\
                                        .netloc.split('.', 1)[0]
        return values

    def openid_url(self):
        """Returns LiveJournal authentication URL"""
        if not self.data.get('openid_lj_user'):
            raise AuthMissingParameter(self, 'openid_lj_user')
        return 'http://%s.livejournal.com' % self.data['openid_lj_user']

Auth APIs

Для других типов аутентификации определен класс BaseAuth. Эти пользовательские методы аутентификации должны переопределять методы auth_url() и << 2 >>>.

Пример кода:

from google.appengine.api import users

from social_core.backends.base import BaseAuth
from social_core.exceptions import AuthException


class GoogleAppEngineAuth(BaseAuth):
    """GoogleAppengine authentication backend"""
    name = 'google-appengine'

    def get_user_id(self, details, response):
        """Return current user id."""
        user = users.get_current_user()
        if user:
            return user.user_id()

    def get_user_details(self, response):
        """Return user basic information (id and email only)."""
        user = users.get_current_user()
        return {'username': user.user_id(),
                'email': user.email(),
                'fullname': '',
                'first_name': '',
                'last_name': ''}

    def auth_url(self):
        """Build and return complete URL."""
        return users.create_login_url(self.redirect_uri)

    def auth_complete(self, *args, **kwargs):
        """Completes login process, must return user instance."""
        if not users.get_current_user():
            raise AuthException('Authentication error')
        kwargs.update({'response': '', 'backend': self})
        return self.strategy.authenticate(*args, **kwargs)
Вернуться на верх