diff --git a/README.rst b/README.rst index f2b051689..7640c140d 100644 --- a/README.rst +++ b/README.rst @@ -85,6 +85,7 @@ or current ones extended): * Linkedin_ OAuth1 * Live_ OAuth2 * Livejournal_ OpenId + * Lyft_ OAuth2 * LoginRadius_ OAuth2 and Application Auth * Mailru_ OAuth2 * MapMyFitness_ OAuth2 @@ -267,6 +268,7 @@ check `django-social-auth LICENSE`_ for details: .. _Linkedin: https://www.linkedin.com .. _Live: https://live.com .. _Livejournal: http://livejournal.com +.. _Lyft: http://lyft.com .. _Khan Academy: https://github.com/Khan/khan-api/wiki/Khan-Academy-API-Authentication .. _Mailru: https://mail.ru .. _MapMyFitness: http://www.mapmyfitness.com/ diff --git a/docs/backends/lyft.rst b/docs/backends/lyft.rst new file mode 100644 index 000000000..ff2e11d4b --- /dev/null +++ b/docs/backends/lyft.rst @@ -0,0 +1,28 @@ +Lyft +========= + +Lyft implements OAuth2 as its authorization service. To setup a Lyft backend: + +1. Register a new application via the `Lyft Developer Portal`_. + +2. Add the Lyft OAuth2 backend as an option in your settings:: + + SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( + ... + 'social.backends.lyft.LyftOAuth2', + ... + ) + +3. Use the ``Client Id`` and ``Client Secret`` from the Developer Portal into your settings:: + + SOCIAL_AUTH_LYFT_KEY = '' + SOCIAL_AUTH_LYFT_SECRET = '' + +4. Specify the scope that your app should have access to:: + + SOCIAL_AUTH_LYFT_SCOPE = ['public', 'profile', 'rides.read', 'rides.request'] + +To learn more about the API and the calls that are available, read the `Lyft API Documentation`_. + +.. _Lyft Developer Portal: https://developer.lyft.com/ +.. _Lyft API Documentation: https://developer.lyft.com/docs diff --git a/social/backends/lyft.py b/social/backends/lyft.py new file mode 100644 index 000000000..9a6a565ff --- /dev/null +++ b/social/backends/lyft.py @@ -0,0 +1,67 @@ +""" +Lyft OAuth2 backend. Read more about the + API at https://developer.lyft.com/docs +""" +from social.backends.oauth import BaseOAuth2 + +class LyftOAuth2(BaseOAuth2): + + name = 'lyft' + ID_KEY = 'id' + + SCOPE_SEPARATOR = ' ' + AUTHORIZATION_URL = 'https://api.lyft.com/oauth/authorize' + ACCESS_TOKEN_URL = 'https://api.lyft.com/oauth/token' + ACCESS_TOKEN_METHOD = 'POST' + REFRESH_TOKEN_URL = 'https://api.lyft.com/oauth/token' + USER_DATA_URL = 'https://api.lyft.com/v1/profile' + + DEFAULT_SCOPE = ['public', 'profile', 'rides.read', 'rides.request'] + + RESPONSE_TYPE = 'code' + STATE_PARAMETER = 'asdf' + + EXTRA_DATA = [ + ('id', 'id'), + ('username', 'username'), + ('access_token', 'access_token'), + ('refresh_token', 'refresh_token'), + ('token_type', 'token_type'), + ('expires_in', 'expires_in'), + ('scope', 'scope'), + ] + + def get_user_details(self, response): + """Return user details from Lyft account""" + + return { + 'id': response['id'], + 'username': response['id'] + } + + def user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + response = kwargs.pop('response') + + return self.get_json(self.USER_DATA_URL, headers={ + 'Authorization': 'Bearer {0}'.format( + access_token + ) + } + ) + + def auth_complete_params(self, state=None): + client_id, client_secret = self.get_key_and_secret() + return { + 'grant_type': 'authorization_code', + 'code': self.data['code'] + } + + def auth_complete_credentials(self): + return self.get_key_and_secret() + + def refresh_token_params(self, refresh_token, *args, **kwargs): + return {'refresh_token': refresh_token, + 'grant_type': 'refresh_token'} + + diff --git a/social/tests/backends/test_lyft.py b/social/tests/backends/test_lyft.py new file mode 100644 index 000000000..646e0deb1 --- /dev/null +++ b/social/tests/backends/test_lyft.py @@ -0,0 +1,31 @@ +import json + +from social.tests.backends.oauth import OAuth2Test + + +class LyftOAuth2Test(OAuth2Test): + + backend_path = 'social.backends.lyft.LyftOAuth2' + + user_data_url = \ + 'https://api.lyft.com/v1/profile' + + access_token_body = json.dumps({ + 'access_token': 'atoken_foo', + 'refresh_token': 'rtoken_bar', + 'token_type': 'bearer', + 'expires_in': 3600, + 'scope': 'public profile rides.read rides.request', + 'id': 'user_foobar' + }) + + user_data_body = json.dumps({ + 'id': 'user_foobar' + }) + expected_username = 'user_foobar' + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline()