Пример #1
0
class PayPal(OAuth2):
    """
    PayPal |oauth2| provider.

    * Dashboard: https://developer.paypal.com/webapps/developer/applications
    * Docs: https://developer.paypal.com/webapps/developer/docs/integration/direct/make-your-first-call/
    * API reference: https://developer.paypal.com/webapps/developer/docs/api/

    .. note::

        Paypal doesn't redirect the **user** to authorize your app!
        It grants you an **access token** based on your **app's** key and
        secret instead.

    """

    _x_use_authorization_header = True

    supported_user_attributes = core.SupportedUserAttributes()

    @classmethod
    def _x_request_elements_filter(cls, request_type, request_elements,
                                   credentials):

        if request_type == cls.ACCESS_TOKEN_REQUEST_TYPE:
            url, method, params, headers, body = request_elements
            params['grant_type'] = 'client_credentials'
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)

        return request_elements

    user_authorization_url = ''
    access_token_url = 'https://api.sandbox.paypal.com/v1/oauth2/token'
    user_info_url = ''
Пример #2
0
class Bitbucket(OAuth1):
    """
    Bitbucket |oauth1| provider.
    
    * Dashboard: https://bitbucket.org/account/user/peterhudec/api
    * Docs: https://confluence.atlassian.com/display/BITBUCKET/oauth+Endpoint
    * API reference: https://confluence.atlassian.com/display/BITBUCKET/Using+the+Bitbucket+REST+APIs

    Supported :class:`.User` properties:

    * first_name
    * id
    * last_name
    * link
    * name
    * picture
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * gender
    * locale
    * location
    * nickname
    * phone
    * postal_code
    * timezone

    """

    supported_user_attributes = core.SupportedUserAttributes(
        first_name=True,
        id=True,
        last_name=True,
        link=True,
        name=True,
        picture=True,
        username=True
    )
    
    request_token_url = 'https://bitbucket.org/!api/1.0/oauth/request_token'
    user_authorization_url = 'https://bitbucket.org/!api/1.0/oauth/authenticate'
    access_token_url = 'https://bitbucket.org/!api/1.0/oauth/access_token'
    user_info_url = 'https://api.bitbucket.org/1.0/user'
    
    @staticmethod
    def _x_user_parser(user, data):
        _user = data.get('user', {})
        user.username = user.id = _user.get('username')
        user.name = _user.get('display_name')
        user.first_name = _user.get('first_name')
        user.last_name = _user.get('last_name')
        user.picture = _user.get('avatar')
        user.link = 'https://bitbucket.org/api{0}'\
            .format(_user.get('resource_uri'))
        return user
Пример #3
0
class Twitter(OAuth1):
    """
    Twitter |oauth1| provider.
    
    * Dashboard: https://dev.twitter.com/apps
    * Docs: https://dev.twitter.com/docs
    * API reference: https://dev.twitter.com/docs/api

    Supported :class:`.User` properties:

    * id
    * link
    * locale
    * location
    * name
    * picture
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * gender
    * first_name
    * last_name
    * nickname
    * phone
    * postal_code
    * timezone

    """

    supported_user_attributes = core.SupportedUserAttributes(
        id=True,
        link=True,
        locale=True,
        location=True,
        name=True,
        picture=True,
        username=True
    )
    
    request_token_url = 'https://api.twitter.com/oauth/request_token'
    user_authorization_url = 'https://api.twitter.com/oauth/authenticate'
    access_token_url = 'https://api.twitter.com/oauth/access_token'
    user_info_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
    
    supports_jsonp = True
     
    @staticmethod
    def _x_user_parser(user, data):
        user.username = data.get('screen_name')
        user.id = data.get('id') or data.get('user_id')
        user.picture = data.get('profile_image_url')
        user.locale = data.get('lang')
        user.link = data.get('url')
        return user
Пример #4
0
class Relayr(OAuth2):
    """
    Relayr |oauth2| provider.
    
    .. warning::
        |no-csrf|

    * Dashboard: https://developer.relayr.io
    * Docs: https://developer.relayr.io/documents/Welcome/Introduction
    * Public API reference: https://developer.relayr.io/documents/relayrAPI/Introduction

    Supported :class:`.User` properties:

    * name
    * id
    * email
    """

    # https://developer.relayr.io/documents/Welcome/OAuthReference

    supported_user_attributes = core.SupportedUserAttributes(name=True,
                                                             id=True,
                                                             email=True)

    supports_csrf_protection = False
    _x_use_authorization_header = False

    user_authorization_url = 'https://api.relayr.io/oauth2/auth'
    access_token_url = 'https://api.relayr.io/oauth2/token'
    user_info_url = 'https://api.relayr.io/oauth2/user-info'
    user_info_scope = ['access-own-user-info']

    def __init__(self, *args, **kwargs):
        super(Relayr, self).__init__(*args, **kwargs)

        if self.offline:
            if not 'grant_type' in self.access_token_params:
                self.access_token_params['grant_type'] = 'refresh_token'

    def _x_scope_parser(self, scope):
        # relayr has space-separated scopes
        return ' '.join(scope)

    @staticmethod
    def _x_user_parser(user, data):
        client = Client(token=user.credentials.token)
        usr = client.get_user()
        user.name = usr.name
        user.id = usr.id
        user.email = usr.email
        user.transmitters = list(usr.get_transmitters())
        user.transmitters_devices = {}
        for t in user.transmitters:
            user.transmitters_devices[
                t.name] = client.api.get_transmitter_devices(t.id)
        return user
Пример #5
0
class Flickr(OAuth1):
    """
    Flickr |oauth1| provider.
    
    * Dashboard: https://www.flickr.com/services/apps/
    * Docs: https://www.flickr.com/services/api/auth.oauth.html
    * API reference: https://www.flickr.com/services/api/

    Supported :class:`.User` properties:

    * id
    * name
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * first_name
    * gender
    * last_name
    * link
    * locale
    * location
    * nickname
    * phone
    * picture
    * postal_code
    * timezone

    """

    supported_user_attributes = core.SupportedUserAttributes(
        id=True,
        name=True,
        username=True
    )
    
    request_token_url = 'http://www.flickr.com/services/oauth/request_token'
    user_authorization_url = 'http://www.flickr.com/services/oauth/authorize'
    access_token_url = 'http://www.flickr.com/services/oauth/access_token'
    user_info_url = None

    supports_jsonp = True
    
    @staticmethod
    def _x_user_parser(user, data):
        _user = data.get('user', {})
        
        user.name = data.get('fullname') or _user.get('username', {}).get('_content')
        user.id = data.get('user_nsid') or _user.get('id')
        
        return user
Пример #6
0
class DeviantART(OAuth2):
    """
    DeviantART |oauth2| provider.
    
    * Dashboard: https://www.deviantart.com/settings/myapps
    * Docs: https://www.deviantart.com/developers/authentication
    * API reference: http://www.deviantart.com/developers/oauth2

    Supported :class:`.User` properties:

    * name
    * picture
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * first_name
    * gender
    * id
    * last_name
    * link
    * locale
    * nickname
    * phone
    * postal_code
    * timezone

    """

    user_authorization_url = 'https://www.deviantart.com/oauth2/draft15/authorize'
    access_token_url = 'https://www.deviantart.com/oauth2/draft15/token'
    user_info_url = 'https://www.deviantart.com/api/oauth2/user/whoami'

    user_info_scope = ['basic']

    supported_user_attributes = core.SupportedUserAttributes(name=True,
                                                             picture=True,
                                                             username=True)

    def __init__(self, *args, **kwargs):
        super(DeviantART, self).__init__(*args, **kwargs)

        if self.offline:
            if not 'grant_type' in self.access_token_params:
                self.access_token_params['grant_type'] = 'refresh_token'

    @staticmethod
    def _x_user_parser(user, data):
        user.picture = data.get('usericonurl')
        return user
Пример #7
0
class Tumblr(OAuth1):
    """
    Tumblr |oauth1| provider.
    
    * Dashboard: http://www.tumblr.com/oauth/apps
    * Docs: http://www.tumblr.com/docs/en/api/v2#auth
    * API reference: http://www.tumblr.com/docs/en/api/v2

    Supported :class:`.User` properties:

    * id
    * name
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * gender
    * first_name
    * last_name
    * link
    * locale
    * location
    * nickname
    * phone
    * picture
    * postal_code
    * timezone

    """

    supported_user_attributes = core.SupportedUserAttributes(
        id=True,
        name=True,
        username=True
    )
    
    request_token_url = 'http://www.tumblr.com/oauth/request_token'
    user_authorization_url = 'http://www.tumblr.com/oauth/authorize'
    access_token_url = 'http://www.tumblr.com/oauth/access_token'
    user_info_url = 'http://api.tumblr.com/v2/user/info'
    
    supports_jsonp = True
    
    @staticmethod
    def _x_user_parser(user, data):
        _user = data.get('response', {}).get('user', {})
        user.username = user.id = _user.get('name')
        return user
Пример #8
0
class SAEON(OAuth2):
    """
    SAEON |oauth2| provider.
    
    * Dashboard: https://github.com/settings/developers
    * Docs: http://developer.github.com/v3/#authentication
    * API reference: http://developer.github.com/v3/
    
    .. note::
        
        GitHub API `documentation <http://developer.github.com/v3/#user-agent-required>`_ sais:
        
            all API requests MUST include a valid ``User-Agent`` header.
        
        You can apply a default ``User-Agent`` header for all API calls in the config like this:
        
        .. code-block:: python
            :emphasize-lines: 6
        
            CONFIG = {
                'github': {
                    'class_': oauth2.GitHub,
                    'consumer_key': '#####',
                    'consumer_secret': '#####',
                    'access_headers': {'User-Agent': 'Awesome-Octocat-App'},
                }
            }

    Supported :class:`.User` properties:

    * email
    * id
    * link
    * location
    * name
    * picture
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * first_name
    * gender
    * last_name
    * locale
    * nickname
    * phone
    * postal_code
    * timezone
    
    """

    user_authorization_url = "https://identity.saeon.nimbusservices.co.za/oauth2/connect/authorize"
    access_token_url = "https://identity.saeon.nimbusservices.co.za/oauth2/connect/token"
    user_info_url = "https://identity.saeon.nimbusservices.co.za/oauth2/connect/userinfo"

    same_origin = False

    supported_user_attributes = core.SupportedUserAttributes(
        email=True,
        id=True,
        username=True,
        name=True,
        link=True,
        location=False,
        picture=False,
    )

    @staticmethod
    def _x_user_parser(user, data):
        logging.debug('_x_user_parser: data = %s' % data)
        user.username = data.get('preferred_username')
        user.email = data.get('email')
        user.id = data.get('email')
        user.fullname = "%s %s" % (data.get('given_name'),
                                   data.get('family_name'))
        user.name = user.fullname
        user.link = 'http://www.saeon.ac.za'  #data.get('html_url')
        #user.picture = data.get('avatar_url')
        logging.debug('_x_user_parser: user = %s' % user)
        return user

    @classmethod
    def _x_credentials_parser(cls, credentials, data):
        if data.get('token_type') == 'Bearer':
            credentials.token_type = cls.BEARER
        return credentials
Пример #9
0
class GitHub(OAuth2):
    """
    GitHub |oauth2| provider.
    
    * Dashboard: https://github.com/settings/applications/
    * Docs: http://developer.github.com/v3/#authentication
    * API reference: http://developer.github.com/v3/
    
    .. note::
        
        GitHub API `documentation <http://developer.github.com/v3/#user-agent-required>`_ sais:
        
            all API requests MUST include a valid ``User-Agent`` header.
        
        You can apply a default ``User-Agent`` header for all API calls in the config like this:
        
        .. code-block:: python
            :emphasize-lines: 6
        
            CONFIG = {
                'github': {
                    'class_': oauth2.GitHub,
                    'consumer_key': '#####',
                    'consumer_secret': '#####',
                    'access_headers': {'User-Agent': 'Awesome-Octocat-App'},
                }
            }

    * city
    * country
    * email
    * id
    * link
    * name
    * picture
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * first_name
    * gender
    * last_name
    * locale
    * nickname
    * phone
    * postal_code
    * timezone
    
    """

    user_authorization_url = 'https://github.com/login/oauth/authorize'
    access_token_url = 'https://github.com/login/oauth/access_token'
    user_info_url = 'https://api.github.com/user'

    same_origin = False

    supported_user_attributes = core.SupportedUserAttributes(city=True,
                                                             country=True,
                                                             email=True,
                                                             id=True,
                                                             link=True,
                                                             name=True,
                                                             picture=True,
                                                             username=True)

    @staticmethod
    def _x_user_parser(user, data):
        user.username = data.get('login')
        user.picture = data.get('avatar_url')
        user.link = data.get('html_url')

        location = data.get('location', '')
        if location:
            split_location = location.split(',')
            user.city = split_location[0].strip()
            if len(split_location) > 1:
                user.country = split_location[1].strip()

        return user

    @classmethod
    def _x_credentials_parser(cls, credentials, data):
        if data.get('token_type') == 'bearer':
            credentials.token_type = cls.BEARER
        return credentials
Пример #10
0
class Meetup(OAuth1):
    """
    Meetup |oauth1| provider.
    
    .. note::
        
        Meetup also supports |oauth2| but you need the **user ID** to update the **user** info,
        which they don't provide in the |oauth2| access token response.
    
    * Dashboard: http://www.meetup.com/meetup_api/oauth_consumers/
    * Docs: http://www.meetup.com/meetup_api/auth/#oauth
    * API: http://www.meetup.com/meetup_api/docs/

    Supported :class:`.User` properties:

    * city
    * country
    * id
    * link
    * locale
    * location
    * name
    * picture

    Unsupported :class:`.User` properties:

    * birth_date
    * email
    * first_name
    * gender
    * last_name
    * nickname
    * phone
    * postal_code
    * timezone
    * username

    """

    supported_user_attributes = core.SupportedUserAttributes(
        city=True,
        country=True,
        id=True,
        link=True,
        locale=True,
        location=True,
        name=True,
        picture=True
    )
    
    request_token_url = 'https://api.meetup.com/oauth/request/'
    user_authorization_url = 'http://www.meetup.com/authorize/'
    access_token_url = 'https://api.meetup.com/oauth/access/'
    user_info_url = 'https://api.meetup.com/2/member/{id}'
    
    
    @staticmethod
    def _x_user_parser(user, data):
        
        user.id = data.get('id') or data.get('member_id')
        user.locale = data.get('lang')
        user.picture = data.get('photo', {}).get('photo_link')
        
        return user
Пример #11
0
class Plurk(OAuth1):
    """
    Plurk |oauth1| provider.
    
    * Dashboard: http://www.plurk.com/PlurkApp/
    * Docs: 
    * API: http://www.plurk.com/API
    * API explorer: http://www.plurk.com/OAuth/test/

    Supported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * gender
    * id
    * link
    * locale
    * location
    * name
    * nickname
    * picture
    * timezone
    * username

    Unsupported :class:`.User` properties:

    * first_name
    * last_name
    * phone
    * postal_code

    """

    supported_user_attributes = core.SupportedUserAttributes(
        birth_date=True,
        city=True,
        country=True,
        email=True,
        gender=True,
        id=True,
        link=True,
        locale=True,
        location=True,
        name=True,
        nickname=True,
        picture=True,
        timezone=True,
        username=True
    )
    
    
    request_token_url = 'http://www.plurk.com/OAuth/request_token'
    user_authorization_url = 'http://www.plurk.com/OAuth/authorize'
    access_token_url = 'http://www.plurk.com/OAuth/access_token'
    user_info_url = 'http://www.plurk.com/APP/Profile/getOwnProfile'
    
    
    @staticmethod
    def _x_user_parser(user, data):
        
        _user = data.get('user_info', {})

        user.email = _user.get('email')
        user.gender = _user.get('gender')
        user.id = _user.get('id') or _user.get('uid')
        user.locale = _user.get('default_lang')
        user.name = _user.get('full_name')
        user.nickname = _user.get('nick_name')
        user.picture = 'http://avatars.plurk.com/{0}-big2.jpg'.format(user.id)
        user.timezone = _user.get('timezone')
        user.username = _user.get('display_name')

        user.link = 'http://www.plurk.com/{0}/'.format(user.username)
        
        user.city, user.country = _user.get('location', ',').split(',')
        user.city = user.city.strip()
        user.country = user.country.strip()

        _bd = _user.get('date_of_birth')
        if _bd:
            try:
                user.birth_date = datetime.datetime.strptime(
                    _bd,
                    "%a, %d %b %Y %H:%M:%S %Z"
                )
            except ValueError:
                pass

        return user
Пример #12
0
class Xing(OAuth1):
    """
    Xing |oauth1| provider.

    * Dashboard: https://dev.xing.com/applications
    * Docs: https://dev.xing.com/docs/authentication
    * API reference: https://dev.xing.com/docs/resources

    Supported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * first_name
    * gender
    * id
    * last_name
    * link
    * locale
    * location
    * name
    * phone
    * picture
    * postal_code
    * timezone
    * username

    Unsupported :class:`.User` properties:

    * nickname

    """

    request_token_url = 'https://api.xing.com/v1/request_token'
    user_authorization_url = 'https://api.xing.com/v1/authorize'
    access_token_url = 'https://api.xing.com/v1/access_token'
    user_info_url = 'https://api.xing.com/v1/users/me'

    supported_user_attributes = core.SupportedUserAttributes(
        birth_date=True,
        city=True,
        country=True,
        email=True,
        first_name=True,
        gender=True,
        id=True,
        last_name=True,
        link=True,
        locale=True,
        location=True,
        name=True,
        phone=True,
        picture=True,
        postal_code=True,
        timezone=True,
        username=True,
    )

    @staticmethod
    def _x_user_parser(user, data):
        _users = data.get('users', [])
        if _users and _users[0]:
            _user = _users[0]
            user.id = _user.get('id')
            user.name = _user.get('display_name')
            user.first_name = _user.get('first_name')
            user.last_name = _user.get('last_name')
            user.gender = _user.get('gender')
            user.timezone = _user.get('time_zone', {}).get('name')
            user.email = _user.get('active_email')
            user.link = _user.get('permalink')
            user.username = _user.get('page_name')
            user.picture = _user.get('photo_urls', {}).get('large')

            _address = _user.get('business_address', {})
            if _address:
                user.city = _address.get('city')
                user.country = _address.get('country')
                user.postal_code = _address.get('zip_code')
                user.phone = (_address.get('phone', '') or
                             _address.get('mobile_phone', '')).replace('|', '')

            _languages = list(_user.get('languages', {}).keys())
            if _languages and _languages[0]:
                user.locale = _languages[0]

            _birth_date = _user.get('birth_date', {})
            _year = _birth_date.get('year')
            _month = _birth_date.get('month')
            _day = _birth_date.get('day')
            if _year and _month and _day:
                user.birth_date = datetime.datetime(_year, _month, _day)

        return user
Пример #13
0
class Flickr(OAuth1):
    """
    Flickr |oauth1| provider.
    
    * Dashboard: https://www.flickr.com/services/apps/
    * Docs: https://www.flickr.com/services/api/auth.oauth.html
    * API reference: https://www.flickr.com/services/api/

    Supported :class:`.User` properties:

    * id
    * name
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * first_name
    * gender
    * last_name
    * link
    * locale
    * location
    * nickname
    * phone
    * picture
    * postal_code
    * timezone

    .. note::

        If you encounter the "Oops! Flickr doesn't recognise the
        permission set." message, you need to add the ``perms=read`` or
        ``perms=write`` parameter to the *user authorization request*.
        You can do it by adding the ``user_authorization_params``
        key to the :doc:`config`:

        .. code-block:: python
            :emphasize-lines: 6

            CONFIG = {
                'flickr': {
                    'class_': oauth1.Flickr,
                    'consumer_key': '##########',
                    'consumer_secret': '##########',
                    'user_authorization_params': dict(perms='read'),
                },
            }

    """

    supported_user_attributes = core.SupportedUserAttributes(
        id=True,
        name=True,
        username=True
    )
    
    request_token_url = 'http://www.flickr.com/services/oauth/request_token'
    user_authorization_url = 'http://www.flickr.com/services/oauth/authorize'
    access_token_url = 'http://www.flickr.com/services/oauth/access_token'
    user_info_url = None

    supports_jsonp = True
    
    @staticmethod
    def _x_user_parser(user, data):
        _user = data.get('user', {})
        
        user.name = data.get('fullname') or _user.get('username', {}).get('_content')
        user.id = data.get('user_nsid') or _user.get('id')
        
        return user
Пример #14
0
class Twitter(OAuth1):
    """
    Twitter |oauth1| provider.

    * Dashboard: https://dev.twitter.com/apps
    * Docs: https://dev.twitter.com/docs
    * API reference: https://dev.twitter.com/docs/api

    .. note:: To prevent multiple authorization attempts, you should enable
      the option:
      ``Allow this application to be used to Sign in with Twitter``
      in the Twitter 'Application Management' page. (http://apps.twitter.com)

    Supported :class:`.User` properties:

    * email
    * city
    * country
    * id
    * link
    * locale
    * location
    * name
    * picture
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * email
    * gender
    * first_name
    * last_name
    * locale
    * nickname
    * phone
    * postal_code
    * timezone

    """

    supported_user_attributes = core.SupportedUserAttributes(city=True,
                                                             country=True,
                                                             id=True,
                                                             email=False,
                                                             link=True,
                                                             locale=False,
                                                             location=True,
                                                             name=True,
                                                             picture=True,
                                                             username=True)

    request_token_url = 'https://api.twitter.com/oauth/request_token'
    user_authorization_url = 'https://api.twitter.com/oauth/authenticate'
    access_token_url = 'https://api.twitter.com/oauth/access_token'
    user_info_url = (
        'https://api.twitter.com/1.1/account/verify_credentials.json?'
        'include_entities=true&include_email=true')
    supports_jsonp = True

    @staticmethod
    def _x_user_parser(user, data):
        user.username = data.get('screen_name')
        user.id = data.get('id') or data.get('user_id')
        user.picture = data.get('profile_image_url')
        user.locale = data.get('lang')
        user.link = data.get('url')
        _location = data.get('location', '')
        if _location:
            user.location = _location.strip()
            _split_location = _location.split(',')
            if len(_split_location) > 1:
                _city, _country = _split_location
                user.country = _country.strip()
            else:
                _city = _split_location[0]
            user.city = _city.strip()
        return user
Пример #15
0
class Yahoo(OAuth1):
    """
    Yahoo |oauth1| provider.
    
    * Dashboard: https://developer.apps.yahoo.com/dashboard/
    * Docs: http://developer.yahoo.com/oauth/guide/oauth-auth-flow.html
    * API: http://developer.yahoo.com/everything.html
    * API explorer: http://developer.yahoo.com/yql/console/

    Supported :class:`.User` properties:

    * birth_date
    * city
    * country
    * gender
    * id
    * link
    * location
    * name
    * nickname
    * picture

    Unsupported :class:`.User` properties:

    * locale
    * phone
    * postal_code
    * timezone
    * username

    """

    supported_user_attributes = core.SupportedUserAttributes(
        birth_date=True,
        city=True,
        country=True,
        gender=True,
        id=True,
        link=True,
        location=True,
        name=True,
        nickname=True,
        picture=True
    )
    
    request_token_url = 'https://api.login.yahoo.com/oauth/v2/get_request_token'
    user_authorization_url = 'https://api.login.yahoo.com/oauth/v2/request_auth'
    access_token_url = 'https://api.login.yahoo.com/oauth/v2/get_token'
    user_info_url = ('https://query.yahooapis.com/v1/yql?q=select%20*%20from%20'
                     'social.profile%20where%20guid%3Dme%3B&format=json')
    
    same_origin = False
    supports_jsonp = True
    
    @staticmethod
    def _x_user_parser(user, data):
        
        _user = data.get('query', {}).get('results', {}).get('profile', {})
        
        user.id = _user.get('guid')
        user.gender = _user.get('gender')
        user.nickname = _user.get('nickname')
        user.link = _user.get('profileUrl')
        
        emails = _user.get('emails')
        if isinstance(emails, list):
            for email in emails:
                if 'primary' in list(email.keys()):
                    user.email = email.get('handle')
        elif isinstance(emails, dict):
            user.email = emails.get('handle')
        
        user.picture = _user.get('image', {}).get('imageUrl')
        
        user.city, user.country = _user.get('location', ',').split(',')
        user.city = user.city.strip()
        user.country = user.country.strip()
        
        _date = _user.get('birthdate')
        _year = _user.get('birthYear')
        
        if _date and _year:
            _full = _date + '/' + _year
            try:
                user.birth_date = datetime.datetime.strptime(_full, "%m/%d/%Y")
            except:
                user.birth_date = _full
        
        return user
Пример #16
0
class Reddit(OAuth2):
    """
    Reddit |oauth2| provider.
    
    .. note::
        
        Currently credentials refreshment returns ``{"error": "invalid_request"}``.
    
    * Dashboard: https://ssl.reddit.com/prefs/apps
    * Docs: https://github.com/reddit/reddit/wiki/OAuth2
    * API reference: http://www.reddit.com/dev/api

    Supported :class:`.User` properties:

    * id
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * country
    * city
    * email
    * first_name
    * gender
    * last_name
    * link
    * locale
    * name
    * nickname
    * phone
    * picture
    * postal_code
    * timezone

    """

    user_authorization_url = 'https://ssl.reddit.com/api/v1/authorize'
    access_token_url = 'https://ssl.reddit.com/api/v1/access_token'
    user_info_url = 'https://oauth.reddit.com/api/v1/me.json'

    user_info_scope = ['identity']

    supported_user_attributes = core.SupportedUserAttributes(id=True,
                                                             name=True,
                                                             username=True)

    def __init__(self, *args, **kwargs):
        super(Reddit, self).__init__(*args, **kwargs)

        if self.offline:
            if not 'duration' in self.user_authorization_params:
                # http://www.reddit.com/r/changelog/comments/11jab9/reddit_change_permanent_oauth_grants_using/
                self.user_authorization_params['duration'] = 'permanent'

    @classmethod
    def _x_credentials_parser(cls, credentials, data):
        if data.get('token_type') == 'bearer':
            credentials.token_type = cls.BEARER
        return credentials

    @staticmethod
    def _x_user_parser(user, data):
        user.username = data.get('name')
        return user
Пример #17
0
class LinkedIn(OAuth2):
    """
    Linked In |oauth2| provider.
    
    .. note::
        
        Doesn't support access token refreshment.
    
    * Dashboard: https://www.linkedin.com/secure/developer
    * Docs: http://developer.linkedin.com/documents/authentication
    * API reference: http://developer.linkedin.com/rest

    Supported :class:`.User` properties:

    * birth_date
    * country
    * email
    * first_name
    * id
    * last_name
    * link
    * name
    * phone
    * picture

    Unsupported :class:`.User` properties:

    * city
    * gender
    * locale
    * nickname
    * postal_code
    * timezone
    * username
    """

    user_authorization_url = 'https://www.linkedin.com/uas/oauth2/authorization'
    access_token_url = 'https://www.linkedin.com/uas/oauth2/accessToken'
    user_info_url = 'https://api.linkedin.com/v1/people/~:' + \
                    '(id,first-name,last-name,formatted-name,location,picture-url,public-profile-url,email-address,date-of-birth,phone-numbers)?format=json'

    user_info_scope = ['r_fullprofile', 'r_emailaddress', 'r_contactinfo']

    token_request_method = 'GET'  # To avoid a bug with OAuth2.0 on Linkedin
    # http://developer.linkedin.com/forum/unauthorized-invalid-or-expired-token-immediately-after-receiving-oauth2-token

    supported_user_attributes = core.SupportedUserAttributes(birth_date=True,
                                                             country=True,
                                                             email=True,
                                                             first_name=True,
                                                             id=True,
                                                             last_name=True,
                                                             link=True,
                                                             name=True,
                                                             phone=True,
                                                             picture=True)

    @classmethod
    def _x_request_elements_filter(cls, request_type, request_elements,
                                   credentials):

        if request_type == cls.PROTECTED_RESOURCE_REQUEST_TYPE:
            # LinkedIn too has it's own terminology!
            url, method, params, headers, body = request_elements
            params['oauth2_access_token'] = params.pop('access_token')
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)

        return request_elements

    @staticmethod
    def _x_user_parser(user, data):

        user.first_name = data.get('firstName')
        user.last_name = data.get('lastName')
        user.email = data.get('emailAddress')
        user.name = data.get('formattedName')
        user.country = data.get('location', {}).get('name')
        user.phone = data.get('phoneNumbers',
                              {}).get('values', [{}])[0].get('phoneNumber')
        user.picture = data.get('pictureUrl')
        user.link = data.get('publicProfileUrl')

        _birthdate = data.get('dateOfBirth', {})
        if _birthdate:
            _day = _birthdate.get('day')
            _month = _birthdate.get('month')
            _year = _birthdate.get('year')
            if _day and _month and _year:
                user.birth_date = datetime.datetime(_year, _month, _day)

        return user
Пример #18
0
class Vimeo(OAuth1):
    """
    Vimeo |oauth1| provider.
    
    .. warning::

        Vimeo needs one more fetch to get rich user info!

    * Dashboard: https://developer.vimeo.com/apps
    * Docs: https://developer.vimeo.com/apis/advanced#oauth-endpoints
    * API reference: https://developer.vimeo.com/apis

    Supported :class:`.User` properties:

    * id
    * link
    * location
    * name
    * picture

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * gender
    * first_name
    * last_name
    * locale
    * nickname
    * phone
    * postal_code
    * timezone
    * username

    """

    supported_user_attributes = core.SupportedUserAttributes(
        id=True,
        link=True,
        location=True,
        name=True,
        picture=True
    )
    
    request_token_url = 'https://vimeo.com/oauth/request_token'
    user_authorization_url = 'https://vimeo.com/oauth/authorize'
    access_token_url = 'https://vimeo.com/oauth/access_token'
    user_info_url = ('http://vimeo.com/api/rest/v2?'
                     'format=json&method=vimeo.oauth.checkAccessToken')

    def _access_user_info(self):
        """
        Vimeo requires the user ID to access the user info endpoint,
        so we need to make two requests: one to get user ID and
        second to get user info.
        """
        response = super(Vimeo, self)._access_user_info()
        uid = response.data.get('oauth', {}).get('user', {}).get('id')
        if uid:
            return self.access('http://vimeo.com/api/v2/{0}/info.json'
                               .format(uid))
        return response
    
    @staticmethod
    def _x_user_parser(user, data):
        user.name = data.get('display_name')
        user.link = data.get('profile_url')
        user.picture = data.get('portrait_huge')
        return user
Пример #19
0
class Foursquare(OAuth2):
    """
    Foursquare |oauth2| provider.
    
    * Dashboard: https://foursquare.com/developers/apps
    * Docs: https://developer.foursquare.com/overview/auth.html
    * API reference: https://developer.foursquare.com/docs/

    .. note::

        Foursquare requires a *version* parameter in each request.
        The default value is ``v=20140501``. You can override the version in
        the ``params`` parameter of the :meth:`.Authomatic.access` method.
        See https://developer.foursquare.com/overview/versioning

    Supported :class:`.User` properties:

    * city
    * country
    * email
    * first_name
    * gender
    * id
    * last_name
    * name
    * phone
    * picture

    Unsupported :class:`.User` properties:

    * birth_date
    * link
    * locale
    * nickname
    * postal_code
    * timezone
    * username

    """

    user_authorization_url = 'https://foursquare.com/oauth2/authenticate'
    access_token_url = 'https://foursquare.com/oauth2/access_token'
    user_info_url = 'https://api.foursquare.com/v2/users/self'

    same_origin = False

    supported_user_attributes = core.SupportedUserAttributes(city=True,
                                                             country=True,
                                                             email=True,
                                                             first_name=True,
                                                             gender=True,
                                                             id=True,
                                                             last_name=True,
                                                             name=True,
                                                             phone=True,
                                                             picture=True)

    @classmethod
    def _x_request_elements_filter(cls, request_type, request_elements,
                                   credentials):

        if request_type == cls.PROTECTED_RESOURCE_REQUEST_TYPE:
            # Foursquare uses OAuth 1.0 "oauth_token" for what should be
            # "access_token" in OAuth 2.0!
            url, method, params, headers, body = request_elements
            params['oauth_token'] = params.pop('access_token')

            # Foursquare needs the version "v" parameter in every request.
            # https://developer.foursquare.com/overview/versioning
            if not params.get('v'):
                params['v'] = '20140501'

            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)

        return request_elements

    @staticmethod
    def _x_user_parser(user, data):

        _resp = data.get('response', {})
        _user = _resp.get('user', {})

        user.id = _user.get('id')
        user.first_name = _user.get('firstName')
        user.last_name = _user.get('lastName')
        user.gender = _user.get('gender')

        _photo = _user.get('photo', {})
        if isinstance(_photo, dict):
            _photo_prefix = _photo.get('prefix', '').strip('/')
            _photo_suffix = _photo.get('suffix', '').strip('/')
            user.picture = '/'.join([_photo_prefix, _photo_suffix])

        if isinstance(_photo, basestring):
            user.picture = _photo

        user.city, user.country = _user.get('homeCity', ', ').split(', ')

        _contact = _user.get('contact', {})
        user.email = _contact.get('email')
        user.phone = _contact.get('phone')

        return user
Пример #20
0
class Facebook(OAuth2):
    """
    Facebook |oauth2| provider.
    
    * Dashboard: https://developers.facebook.com/apps
    * Docs: http://developers.facebook.com/docs/howtos/login/server-side-login/
    * API reference: http://developers.facebook.com/docs/reference/api/
    * API explorer: http://developers.facebook.com/tools/explorer

    Supported :class:`.User` properties:

    * city
    * country
    * email
    * first_name
    * gender
    * id
    * last_name
    * link
    * locale
    * name
    * picture
    * timezone
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * nickname
    * phone
    * postal_code

    """

    user_authorization_url = 'https://www.facebook.com/dialog/oauth'
    access_token_url = 'https://graph.facebook.com/oauth/access_token'
    user_info_url = 'https://graph.facebook.com/me'
    user_info_scope = ['user_about_me', 'email']
    same_origin = False

    supported_user_attributes = core.SupportedUserAttributes(id=True,
                                                             email=True,
                                                             username=True,
                                                             name=True,
                                                             first_name=True,
                                                             last_name=True,
                                                             city=True,
                                                             country=True,
                                                             gender=True,
                                                             link=True,
                                                             locale=True,
                                                             picture=True,
                                                             timezone=True)

    @classmethod
    def _x_request_elements_filter(cls, request_type, request_elements,
                                   credentials):

        if request_type == cls.REFRESH_TOKEN_REQUEST_TYPE:
            # As always, Facebook has it's original name for "refresh_token"!
            url, method, params, headers, body = request_elements
            params['fb_exchange_token'] = params.pop('refresh_token')
            params['grant_type'] = 'fb_exchange_token'
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)

        return request_elements

    def __init__(self, *args, **kwargs):
        super(Facebook, self).__init__(*args, **kwargs)

        # Handle special Facebook requirements to be able to refresh the access token.
        if self.offline:
            # Facebook needs an offline_access scope.
            if not 'offline_access' in self.scope:
                self.scope.append('offline_access')

        if self.popup:
            self.user_authorization_url += '?display=popup'

    @staticmethod
    def _x_user_parser(user, data):
        user.picture = 'http://graph.facebook.com/{0}/picture?type=large'.format(
            data.get('username'))

        location = data.get('location', {}).get('name')
        if location and location.split:
            split_location = location.split(', ')
            user.city = split_location[0].strip()
            if len(split_location) > 1:
                user.country = split_location[1].strip()

        return user

    @staticmethod
    def _x_credentials_parser(credentials, data):
        """
        We need to override this method to fix Facebooks naming deviation.
        """

        # Facebook returns "expires" instead of "expires_in".
        credentials.expire_in = data.get('expires')

        return credentials

    @staticmethod
    def _x_refresh_credentials_if(credentials):
        # Always refresh.
        return True
Пример #21
0
class VK(OAuth2):
    """
    VK.com |oauth2| provider.
    
    * Dashboard: Could not find any. You must do it like this: http://vk.com/editapp?id={consumer_key}
    * Docs: http://vk.com/developers.php?oid=-17680044&p=Authorizing_Sites
    * API reference: http://vk.com/developers.php?oid=-17680044&p=API_Method_Description
    
    .. note::
        
        VK uses a `bitmask scope <http://vk.com/developers.php?oid=-17680044&p=Application_Rights>`_!
        Use it like this:
        
    .. code-block:: python
        :emphasize-lines: 7
        
        CONFIG = {
            'vk': {
                'class_': oauth2.VK,
                'consumer_key': '#####',
                'consumer_secret': '#####',
                'id': authomatic.provider_id(),
                'scope': ['1024'] # Always a single item.
            }
        }

    Supported :class:`.User` properties:

    * birth_date
    * city
    * country
    * first_name
    * gender
    * id
    * last_name
    * name
    * picture
    * timezone

    Unsupported :class:`.User` properties:

    * email
    * link
    * locale
    * nickname
    * phone
    * postal_code
    * username

    """

    user_authorization_url = 'http://api.vkontakte.ru/oauth/authorize'
    access_token_url = 'https://api.vkontakte.ru/oauth/access_token'
    user_info_url = 'https://api.vk.com/method/getProfiles?' + \
                    'fields=uid,first_name,last_name,nickname,sex,bdate,city,country,timezone,photo_big'

    supported_user_attributes = core.SupportedUserAttributes(
        birth_date=True,
        city=True,
        country=True,
        first_name=True,
        gender=True,
        id=True,
        last_name=True,
        name=True,
        picture=True,
        timezone=True,
    )

    def __init__(self, *args, **kwargs):
        super(VK, self).__init__(*args, **kwargs)

        if self.offline:
            if not 'offline' in self.scope:
                self.scope.append('offline')

    @staticmethod
    def _x_user_parser(user, data):
        _resp = data.get('response', [{}])[0]

        _birth_date = _resp.get('bdate')
        if _birth_date:
            user.birth_date = datetime.datetime.strptime(
                _birth_date, '%d.%m.%Y')
        user.id = _resp.get('uid')
        user.first_name = _resp.get('first_name')
        user.gender = _resp.get('sex')
        user.last_name = _resp.get('last_name')
        user.nickname = _resp.get('nickname')
        user.city = _resp.get('city')
        user.country = _resp.get('country')
        user.timezone = _resp.get('timezone')
        user.picture = _resp.get('photo_big')

        return user
Пример #22
0
class Bitly(OAuth2):
    """
    Bitly |oauth2| provider.
    
    .. warning::

        |no-csrf|

    * Dashboard: https://bitly.com/a/oauth_apps
    * Docs: http://dev.bitly.com/authentication.html
    * API reference: http://dev.bitly.com/api.html

    Supported :class:`.User` properties:

    * id
    * link
    * name
    * picture
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * email
    * first_name
    * gender
    * last_name
    * locale
    * nickname
    * phone
    * postal_code
    * timezone

    """

    supported_user_attributes = core.SupportedUserAttributes(id=True,
                                                             link=True,
                                                             name=True,
                                                             picture=True,
                                                             username=True)

    supports_csrf_protection = False
    _x_use_authorization_header = False

    user_authorization_url = 'https://bitly.com/oauth/authorize'
    access_token_url = 'https://api-ssl.bitly.com/oauth/access_token'
    user_info_url = 'https://api-ssl.bitly.com/v3/user/info'

    def __init__(self, *args, **kwargs):
        super(Bitly, self).__init__(*args, **kwargs)

        if self.offline:
            if not 'grant_type' in self.access_token_params:
                self.access_token_params['grant_type'] = 'refresh_token'

    @staticmethod
    def _x_user_parser(user, data):
        info = data.get('data', {})

        user.id = info.get('login')
        user.name = info.get('full_name')
        user.username = info.get('display_name')
        user.picture = info.get('profile_image')
        user.link = info.get('profile_url')

        return user
Пример #23
0
class Xero(OAuth1):
    """
    Xero |oauth1| provider.
    
    .. note::
        
        API returns XML!
    
    * Dashboard: https://api.xero.com/Application
    * Docs: http://blog.xero.com/developer/api-overview/public-applications/
    * API reference: http://blog.xero.com/developer/api/

    Supported :class:`.User` properties:

    * email
    * first_name
    * id
    * last_name
    * name

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * gender
    * link
    * locale
    * location
    * nickname
    * phone
    * picture
    * postal_code
    * timezone
    * username

    """

    supported_user_attributes = core.SupportedUserAttributes(
        email=True,
        first_name=True,
        id=True,
        last_name=True,
        name=True
    )
    
    request_token_url = 'https://api.xero.com/oauth/RequestToken'
    user_authorization_url = 'https://api.xero.com/oauth/Authorize'
    access_token_url = 'https://api.xero.com/oauth/AccessToken'
    user_info_url = 'https://api.xero.com/api.xro/2.0/Users'
    
    
    @staticmethod
    def _x_user_parser(user, data):
        # Data is xml.etree.ElementTree.Element object.
        if type(data) is not dict:
            # But only on user.update()
            _user = data.find('Users/User')
            user.id = _user.find('UserID').text
            user.first_name = _user.find('FirstName').text
            user.last_name = _user.find('LastName').text
            user.email = _user.find('EmailAddress').text
        
        return user
Пример #24
0
class Google(OAuth2):
    """
    Google |oauth2| provider.
    
    * Dashboard: https://console.developers.google.com/project
    * Docs: https://developers.google.com/accounts/docs/OAuth2
    * API reference: https://developers.google.com/gdata/docs/directory
    * API explorer: https://developers.google.com/oauthplayground/

    Supported :class:`.User` properties:

    * email
    * first_name
    * gender
    * id
    * last_name
    * link
    * locale
    * name
    * picture

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * nickname
    * phone
    * postal_code
    * timezone
    * username

    """

    user_authorization_url = 'https://accounts.google.com/o/oauth2/auth'
    access_token_url = 'https://accounts.google.com/o/oauth2/token'
    user_info_url = 'https://www.googleapis.com/plus/v1/people/me'

    user_info_scope = ['profile', 'email']

    supported_user_attributes = core.SupportedUserAttributes(id=True,
                                                             email=True,
                                                             name=True,
                                                             first_name=True,
                                                             last_name=True,
                                                             gender=True,
                                                             locale=True,
                                                             link=True,
                                                             picture=True)

    def __init__(self, *args, **kwargs):
        super(Google, self).__init__(*args, **kwargs)

        # Handle special Google requirements to be able to refresh the access token.
        if self.offline:
            if not 'access_type' in self.user_authorization_params:
                # Google needs access_type=offline param in the user authorization request.
                self.user_authorization_params['access_type'] = 'offline'
            if not 'approval_prompt' in self.user_authorization_params:
                # And also approval_prompt=force.
                self.user_authorization_params['approval_prompt'] = 'force'

    @staticmethod
    def _x_user_parser(user, data):
        emails = data.get('emails', [])
        if emails:
            user.email = emails[0].get('value')
            for email in emails:
                if email.get('type') == 'account':
                    user.email = email.get('value')
                    break

        user.id = data.get('sub') or data.get('id')
        user.name = data.get('displayName')
        user.first_name = data.get('name', {}).get('givenName')
        user.last_name = data.get('name', {}).get('familyName')
        user.locale = data.get('language')
        user.link = data.get('url')
        user.picture = data.get('image', {}).get('url')
        try:
            user.birth_date = datetime.datetime.strptime(
                data.get('birthdate'), "%Y-%m-%d")
        except:
            user.birth_date = data.get('birthdate')
        return user

    def _x_scope_parser(self, scope):
        """
        Google has space-separated scopes
        """
        return ' '.join(scope)
Пример #25
0
class Google(OAuth2):
    """
    Google |oauth2| provider.

    * Dashboard: https://console.developers.google.com/project
    * Docs: https://developers.google.com/accounts/docs/OAuth2
    * API reference: https://developers.google.com/gdata/docs/directory
    * API explorer: https://developers.google.com/oauthplayground/

    Supported :class:`.User` properties:

    * email
    * first_name
    * gender
    * id
    * last_name
    * link
    * locale
    * name
    * picture

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * nickname
    * phone
    * postal_code
    * timezone
    * username

    .. note::

        To get the user info, you need to activate the **Google+ API**
        in the **APIs & auth >> APIs** section of the`Google Developers Console
        <https://console.developers.google.com/project>`__.

    """

    user_authorization_url = 'https://accounts.google.com/o/oauth2/auth'
    access_token_url = 'https://accounts.google.com/o/oauth2/token'
    user_info_url = 'https://www.googleapis.com/oauth2/v3/userinfo?alt=json'

    user_info_scope = ['profile', 'email']

    supported_user_attributes = core.SupportedUserAttributes(
        id=False,
        email=True,
        name=True,
        first_name=True,
        last_name=True,
        locale=True,
        picture=True,

        # No longer supported as of 2016-08
        link=False,
        gender=False,
    )

    def __init__(self, *args, **kwargs):
        super(Google, self).__init__(*args, **kwargs)

        # Handle special Google requirements to be able to refresh the access token.
        if self.offline:
            if not 'access_type' in self.user_authorization_params:
                # Google needs access_type=offline param in the user authorization request.
                self.user_authorization_params['access_type'] = 'offline'
            if not 'approval_prompt' in self.user_authorization_params:
                # And also approval_prompt=force.
                self.user_authorization_params['approval_prompt'] = 'force'

    @classmethod
    def _x_request_elements_filter(cls, request_type, request_elements,
                                   credentials):
        """
        Google doesn't accept client ID and secret to be at the same time in
        request parameters and in the basic authorization header in the
        access token request.
        """
        if request_type is cls.ACCESS_TOKEN_REQUEST_TYPE:
            params = request_elements[2]
            del params['client_id']
            del params['client_secret']
        return request_elements

    @staticmethod
    def _x_user_parser(user, data):

        user.email = data.get('email')
        user.name = data.get('name')
        user.first_name = data.get('given_name', '')
        user.last_name = data.get('family_name', '')
        user.locale = data.get('locale', '')
        user.picture = data.get('picture', '')

        # Special attribute if email has been verified
        user.email_verified = data.get("email_verified")
        user.hosted_domain = data.get("hd")
        return user

    def _x_scope_parser(self, scope):
        """
        Google has space-separated scopes
        """
        return ' '.join(scope)
Пример #26
0
class Bitbucket(OAuth1):
    """
    Bitbucket |oauth1| provider.

    * Dashboard: https://bitbucket.org/account/user/peterhudec/api
    * Docs: https://confluence.atlassian.com/display/BITBUCKET/oauth+Endpoint
    * API reference:
      https://confluence.atlassian.com/display/BITBUCKET/Using+the+Bitbucket+REST+APIs

    Supported :class:`.User` properties:

    * first_name
    * id
    * last_name
    * link
    * name
    * picture
    * username
    * email

    Unsupported :class:`.User` properties:

    * birth_date
    * city
    * country
    * gender
    * locale
    * location
    * nickname
    * phone
    * postal_code
    * timezone

    .. note::

        To get the full user info, you need to select both the *Account Read*
        and the *Repositories Read* permission in the Bitbucket application
        edit form.

    """

    supported_user_attributes = core.SupportedUserAttributes(first_name=True,
                                                             id=True,
                                                             last_name=True,
                                                             link=True,
                                                             name=True,
                                                             picture=True,
                                                             username=True,
                                                             email=True)

    request_token_url = 'https://bitbucket.org/!api/1.0/oauth/request_token'
    user_authorization_url = 'https://bitbucket.org/!api/1.0/oauth/' + \
                             'authenticate'
    access_token_url = 'https://bitbucket.org/!api/1.0/oauth/access_token'
    user_info_url = 'https://api.bitbucket.org/1.0/user'
    user_email_url = 'https://api.bitbucket.org/1.0/emails'

    @staticmethod
    def _x_user_parser(user, data):
        _user = data.get('user', {})
        user.username = user.id = _user.get('username')
        user.name = _user.get('display_name')
        user.first_name = _user.get('first_name')
        user.last_name = _user.get('last_name')
        user.picture = _user.get('avatar')
        user.link = 'https://bitbucket.org/api{0}'\
            .format(_user.get('resource_uri'))
        return user

    def _access_user_info(self):
        """
        Email is available in separate method so second request is needed.
        """
        response = super(Bitbucket, self)._access_user_info()

        response.data.setdefault("email", None)

        email_response = self.access(self.user_email_url)
        if email_response.data:
            for item in email_response.data:
                if item.get("primary", False):
                    response.data.update(email=item.get("email", None))

        return response