Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contextual endpoint #51

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions docs/providers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ Authentication Providers supply varying levels of information when
authentication has occurred. Some of them can also provide API access
tokens in addition to authenticating a user for sign-on.

Common POST parameters
=======================

* end_point
A common and not mandatory parameter 'end_point' can be provided in the POST parameters
to allow velruse to redirect to this endpoint callback instead of the one given in the configuration.

.. code-block:: html

<form action="/velruse/yahoo/auth" method="POST">
<input type="hidden" name="xend_point" value="http://myapp/signed_in" />
</form>

Facebook
========

Expand Down Expand Up @@ -87,7 +100,21 @@ Settings
velruse.openid.realm
Domain for your website, e.g. `http://yourdomain.com/`
velruse.openid.store
A class from which the OpenID store will be instantiated.
A class or callable factory from which the OpenID store will be instantiated.
Tbis class or callable will be given the registry settings as the first constructor/method parameter.
We provide a stock sqlstore for you to use: velruse.providers.openidconsumer.get_openid_sqlstore

Example:

[app:velruse]
use=egg:velruse
# velruse (§OPENID services authentication configuration)
velruse.store=velruse.store.sqlstore
velruse.store.url = postgresql+psycopg2://something:secret@localhost:5438/somebase
velruse.store.echo = true
velruse.store.echo_pool = true
velruse.store.pool_recycle = 10
velruse.openid.store = velruse.providers.openidconsumer.get_openid_sqlstore

.. note::

Expand Down Expand Up @@ -332,7 +359,7 @@ Policy URL
Site's Privacy Policy URL, overrides the url specified during registration
of your application with Live Services.
Return URL
Site's Return URL, overrides the url specified during registration of
Site's Return URL, overrides the url specified during registration of
your application with Live Services. This is not *YOUR* applicaton's end
point! This should only be overriden if your registration url is not
the velruse url. For example http://YOURDOMAIN.COM/velruse/live/process.
Expand All @@ -347,7 +374,7 @@ POST Parameters
Complete Example:

.. code-block:: html
<form action="/velruse/live/login" method="post">

<form action="/velruse/live/auth" method="post">
<input type="submit" value="Login with Windows Live" />
</form>
19 changes: 15 additions & 4 deletions velruse/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

@view_config(context='velruse.api.AuthenticationComplete')
def auth_complete_view(context, request):
endpoint = request.registry.settings.get('velruse.endpoint')
# if we have a thirdparty software behind velruse, it can also process in turn
# the authentication request and so have specified a callback to return to
# We may have this information in the context profile.
endpoint = context.profile.get('end_point',
request.registry.settings.get('velruse.endpoint'))
token = generate_token()
storage = request.registry.velruse_store
if 'birthday' in context.profile:
Expand All @@ -33,12 +37,19 @@ def auth_complete_view(context, request):

@view_config(context='velruse.exceptions.AuthenticationDenied')
def auth_denied_view(context, request):
endpoint = request.registry.settings.get('velruse.endpoint')
# if we have a thirdparty software behind velruse, it can also process in turn
# the authentication request and so have specified a callback to return to
# We may have this information in the request parameters from POST to GET.
endpoint = request.POST.get('end_point',
request.GET.get('end_point',
request.GET.get('return_to',
request.registry.settings.get('velruse.end_point'))))
token = generate_token()
storage = request.registry.velruse_store
error_dict = {
'code': getattr(context, 'code', None),
'description': context.message,
'code': getattr(context, 'code', None),
'description': getattr(context, 'description',
getattr(context, 'message', '')),
}
storage.store(token, error_dict, expires=300)
form = redirect_form(endpoint, token)
Expand Down
12 changes: 11 additions & 1 deletion velruse/providers/bitbucket.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Bitbucket Authentication Views"""
import json
from urllib import urlencode
from urlparse import parse_qs

import oauth2 as oauth
Expand All @@ -11,6 +12,7 @@
from velruse.api import AuthenticationComplete
from velruse.exceptions import AuthenticationDenied
from velruse.exceptions import ThirdPartyFailure
from velruse.utils import flat_url, get_came_from


REQUEST_URL = 'https://bitbucket.org/api/1.0/oauth/request_token/'
Expand All @@ -37,9 +39,16 @@ def bitbucket_login(request):
config = request.registry.settings

# Create the consumer and client, make the request
redirect_uri = request.route_url('bitbucket_process')
came_from = get_came_from(request)
if came_from:
qs = urlencode({'end_point':came_from})
if not '?' in redirect_uri:
redirect_uri += '?'
redirect_uri += qs
consumer = oauth.Consumer(config['velruse.bitbucket.consumer_key'],
config['velruse.bitbucket.consumer_secret'])
params = {'oauth_callback': request.route_url('bitbucket_process')}
params = {'oauth_callback': redirect_uri}

# We go through some shennanigans here to specify a callback url
oauth_request = oauth.Request.from_consumer_and_token(consumer,
Expand Down Expand Up @@ -110,5 +119,6 @@ def bitbucket_process(request):
'familyName': data['last_name']
}
profile['displayName'] = profile['name']['formatted']
profile['end_point'] = get_came_from(request)
return BitbucketAuthenticationComplete(profile=profile,
credentials=cred)
22 changes: 19 additions & 3 deletions velruse/providers/facebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import uuid
from json import loads
from urlparse import parse_qs
from urllib import urlencode

import requests

Expand All @@ -12,7 +13,7 @@
from velruse.exceptions import AuthenticationDenied
from velruse.exceptions import CSRFError
from velruse.exceptions import ThirdPartyFailure
from velruse.utils import flat_url
from velruse.utils import flat_url, get_came_from


class FacebookAuthenticationComplete(AuthenticationComplete):
Expand All @@ -33,9 +34,16 @@ def facebook_login(request):
scope = config.get('velruse.facebook.scope',
request.POST.get('scope', ''))
request.session['state'] = state = uuid.uuid4().hex
redirect_uri = request.route_url('facebook_process')
came_from = get_came_from(request)
if came_from:
qs = urlencode({'end_point':came_from })
if not '?' in redirect_uri:
redirect_uri += '?'
redirect_uri += qs
fb_url = flat_url('https://www.facebook.com/dialog/oauth/', scope=scope,
client_id=config['velruse.facebook.app_id'],
redirect_uri=request.route_url('facebook_process'),
redirect_uri=redirect_uri,
state=state)
return HTTPFound(location=fb_url)

Expand All @@ -54,10 +62,17 @@ def facebook_process(request):
return AuthenticationDenied(reason)

# Now retrieve the access token with the code
redirect_uri = request.route_url('facebook_process')
came_from = get_came_from(request)
if came_from:
qs = urlencode({'end_point':came_from })
if not '?' in redirect_uri:
redirect_uri += '?'
redirect_uri += qs
access_url = flat_url('https://graph.facebook.com/oauth/access_token',
client_id=config['velruse.facebook.app_id'],
client_secret=config['velruse.facebook.app_secret'],
redirect_uri=request.route_url('facebook_process'),
redirect_uri=redirect_uri,
code=code)
r = requests.get(access_url)
if r.status_code != 200:
Expand All @@ -74,6 +89,7 @@ def facebook_process(request):
profile = extract_fb_data(fb_profile)

cred = {'oauthAccessToken': access_token}
profile['end_point'] = get_came_from(request)
return FacebookAuthenticationComplete(profile=profile,
credentials=cred)

Expand Down
20 changes: 15 additions & 5 deletions velruse/providers/github.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Github Authentication Views"""
from json import loads
from urllib import urlencode
from urlparse import parse_qs

import requests
Expand All @@ -9,7 +10,7 @@
from velruse.api import AuthenticationComplete
from velruse.exceptions import AuthenticationDenied
from velruse.exceptions import ThirdPartyFailure
from velruse.utils import flat_url
from velruse.utils import flat_url, get_came_from


class GithubAuthenticationComplete(AuthenticationComplete):
Expand All @@ -27,11 +28,19 @@ def includeme(config):
def github_login(request):
"""Initiate a github login"""
config = request.registry.settings
scope = config.get('velruse.github.scope',
request.POST.get('scope', ''))
gh_url = flat_url('https://github.com/login/oauth/authorize', scope=scope,
redirect_uri = request.route_url('github_process')
came_from = get_came_from(request)
if came_from:
qs = urlencode({'end_point':came_from })
if not '?' in redirect_uri:
redirect_uri += '?'
redirect_uri += qs
scope = config.get('velruse.github.authorize',
request.POST.get('scope', ''))
gh_url = flat_url('https://github.com/login/oauth/authorize',
scope=scope,
client_id=config['velruse.github.app_id'],
redirect_uri=request.route_url('github_process'))
redirect_uri=redirect_uri)
return HTTPFound(location=gh_url)


Expand Down Expand Up @@ -70,6 +79,7 @@ def github_process(request):
}]
profile['displayName'] = data['name']
profile['preferredUsername'] = data['login']
profile['end_point'] = get_came_from(request)

# We don't add this to verifiedEmail because ppl can change email addresses
# without verifying them
Expand Down
13 changes: 9 additions & 4 deletions velruse/providers/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ class GoogleAuthenticationComplete(OpenIDAuthenticationComplete):

def includeme(config):
settings = config.registry.settings
store, realm = setup_openid(config)
store, _ = setup_openid(config)
# Here realm must be None not to be too restrictive on
# the return_to callback domain.
consumer = GoogleConsumer(
storage=store,
realm=realm,
realm=None,
process_url='google_process',
oauth_key=settings.get('velruse.google.consumer_key'),
oauth_secret=settings.get('velruse.google.consumer_secret'),
Expand Down Expand Up @@ -86,8 +88,11 @@ def _update_authrequest(self, request, authrequest):
oauth_scope = None
if 'oauth_scope' in request.POST:
oauth_scope = request.POST['oauth_scope']
elif 'velruse.google.oauth_scope' in settings:
oauth_scope = settings['velruse.google.oauth_scope']
# let scope being permissive to allow thirdparty softwares to log on velruse
else:
oauth_scope = settings.get(
'velruse.google.authorize',
settings.get('velruse.google.oauth_scope', ''))
if oauth_scope:
oauth_request = OAuthRequest(consumer=self.oauth_key,
scope=oauth_scope)
Expand Down
14 changes: 11 additions & 3 deletions velruse/providers/lastfm.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Last.fm Authentication Views"""
from hashlib import md5
from urllib import urlencode
from json import loads

import requests
Expand All @@ -9,7 +10,7 @@
from velruse.api import AuthenticationComplete
from velruse.exceptions import AuthenticationDenied
from velruse.exceptions import ThirdPartyFailure
from velruse.utils import flat_url
from velruse.utils import flat_url, get_came_from

API_BASE = 'https://ws.audioscrobbler.com/2.0/'

Expand Down Expand Up @@ -37,8 +38,14 @@ def sign_call(params, secret):
def lastfm_login(request):
"""Initiate a LastFM login"""
config = request.registry.settings
fb_url = flat_url('https://www.last.fm/api/auth/',
api_key=config['velruse.lastfm.api_key'])
redirect_uri = request.route_url('lastfm_process')
came_from = get_came_from(request)
if came_from:
qs = urlencode({'end_point':came_from })
if not '?' in redirect_uri:
redirect_uri += '?'
redirect_uri += qs
fb_url = flat_url('https://www.last.fm/api/auth/', api_key=config['velruse.lastfm.api_key'], cb=redirect_uri)
return HTTPFound(location=fb_url)


Expand Down Expand Up @@ -105,5 +112,6 @@ def lastfm_process(request):
larger = images.get('extralarge', images.get('large'))
if larger:
profile['photos'].append({'type': '', 'value': larger})
profile['end_point'] = get_came_from(request)
return LastFMAuthenticationComplete(profile=profile,
credentials=cred)
12 changes: 11 additions & 1 deletion velruse/providers/linkedin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""LinkedIn Authentication Views"""
from json import loads
from urllib import urlencode
from urlparse import parse_qs

import oauth2 as oauth
Expand All @@ -11,6 +12,7 @@
from velruse.api import AuthenticationComplete
from velruse.exceptions import AuthenticationDenied
from velruse.exceptions import ThirdPartyFailure
from velruse.utils import get_came_from


REQUEST_URL = 'https://api.linkedin.com/uas/oauth/requestToken'
Expand All @@ -37,7 +39,14 @@ def linkedin_login(request):
consumer = oauth.Consumer(config['velruse.linkedin.consumer_key'],
config['velruse.linkedin.consumer_secret'])
sigmethod = oauth.SignatureMethod_HMAC_SHA1()
params = {'oauth_callback': request.route_url('linkedin_process')}
redirect_uri = request.route_url('linkedin_process')
came_from = get_came_from(request)
if came_from:
qs = urlencode({'end_point':came_from })
if not '?' in redirect_uri:
redirect_uri += '?'
redirect_uri += qs
params = {'oauth_callback': redirect_uri}

# We go through some shennanigans here to specify a callback url
oauth_request = oauth.Request.from_consumer_and_token(consumer,
Expand Down Expand Up @@ -111,5 +120,6 @@ def linkedin_process(request):
'domain':'linkedin.com',
'userid':data['id']
}]
profile['end_point'] = get_came_from(request)
return LinkedInAuthenticationComplete(profile=profile,
credentials=cred)
Loading