Skip to content

Commit

Permalink
Release v1.1.2
Browse files Browse the repository at this point in the history
    - Login and Logout actions are performed via POST now with
      CSRF protection activated
  • Loading branch information
kipanshi committed Nov 23, 2013
1 parent 78f2a94 commit 39093fe
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 88 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
@@ -1,3 +1,8 @@
1.1.2
-----
- Login and Logout actions are performed via POST and has protection
against CSRF attacks

1.1.1
-----
- Fix ``BaseHandler`` obscuring ``AttributeError`` during dispatch
Expand Down
4 changes: 4 additions & 0 deletions README.rst
Expand Up @@ -127,7 +127,11 @@ You can provide custom ``forbidden.jinja2`` template by overriding asset in your

See template example in `pyramid_odesk/templates/forbidden.jinja2`_.

The "Logout" action is done also via POST request with CSRF protection,
see example of "Logout" buttion in `pyramid_odesk_example/templates/layout.jinja2`_.

.. _`pyramid_odesk/templates/forbidden.jinja2`: https://github.com/kipanshi/pyramid_odesk/tree/master/pyramid_odesk/templates/forbidden.jinja2
.. _`pyramid_odesk_example/templates/layout.jinja2`: https://github.com/kipanshi/pyramid_odesk_example/blob/master/pyramid_odesk_example/templates/layout.jinja2


Contacts
Expand Down
12 changes: 6 additions & 6 deletions pyramid_odesk/__init__.py
Expand Up @@ -4,7 +4,7 @@
from pyramid.authorization import ACLAuthorizationPolicy


from .views import login, logout, oauth_callback, forbidden
from .views import Login, Logout, OauthCallback, forbidden


def includeme(config):
Expand Down Expand Up @@ -48,22 +48,22 @@ def includeme(config):
config.registry['odesk.login_route'] = login_route
login_path = settings.get('odesk.login_path', '/odesk-auth/login')
config.add_route(login_route, login_path)
config.add_view(login, route_name=login_route,
permission='login')
config.add_view(Login, route_name=login_route,
permission='login', check_csrf=True)

logout_route = settings.get('odesk.logout_route', 'logout')
config.registry['odesk.logout_route'] = logout_route
logout_path = settings.get('odesk.logout_path', '/odesk-auth/logout')
config.add_route(logout_route, logout_path)
config.add_view(logout, route_name=logout_route,
permission=NO_PERMISSION_REQUIRED)
config.add_view(Logout, route_name=logout_route,
permission=NO_PERMISSION_REQUIRED, check_csrf=True)

callback_route = settings.get('odesk.callback_route', 'oauth_callback')
config.registry['odesk.logout_route'] = callback_route
callback_path = settings.get('odesk.callback_path',
'/odesk-auth/callback')
config.add_route(callback_route, callback_path)
config.add_view(oauth_callback, route_name=callback_route,
config.add_view(OauthCallback, route_name=callback_route,
permission='login')

# A simple 403 view, with a login button.
Expand Down
10 changes: 8 additions & 2 deletions pyramid_odesk/templates/forbidden.jinja2
Expand Up @@ -2,9 +2,15 @@
<body>
<p>You are not authorized to access this page.</p>
{% if authenticated %}
<p><a href="{{ request.route_url('logout') }}">Log out</a></p>
<form method="post" action="{{ request.route_url('logout') }}">
<input type="hidden" name="csrf_token" value="{{ request.session.get_csrf_token() }}">
<input type="submit" value="Logout">
</form>
{% else %}
<p>Login with oDesk account <a href="{{ request.route_url('login', _query={'next': request.path}) }}">here</a></p>
<form method="post" action="{{ request.route_url('login') }}">
<input type="hidden" name="csrf_token" value="{{ request.session.get_csrf_token() }}">
<input type="submit" value="Login with oDesk">
</form>
{% endif %}
</body>
</html>
26 changes: 26 additions & 0 deletions pyramid_odesk/utils.py
@@ -0,0 +1,26 @@
import odesk


def get_odesk_client(request, **attrs):
"""Construct an oDesk client.
*Parameters:*
:attrs: keyword arguments that will be
attached to the ``client.auth`` as attributes
(``request_token``, etc.)
"""
client_kwargs = {
'oauth_access_token': attrs.pop('oauth_access_token', None),
'oauth_access_token_secret': attrs.pop(
'oauth_access_token_secret', None)

}

settings = request.registry.settings
client = odesk.Client(settings['odesk.api.key'],
settings['odesk.api.secret'], **client_kwargs)

for key, value in attrs.items():
setattr(client.auth, key, value)

return client
133 changes: 54 additions & 79 deletions pyramid_odesk/views.py
@@ -1,7 +1,7 @@
from pyramid.security import remember, forget, unauthenticated_userid
from pyramid.httpexceptions import HTTPMethodNotAllowed, HTTPFound

import odesk
from .utils import get_odesk_client


class BaseHandler(object):
Expand Down Expand Up @@ -36,91 +36,66 @@ def delete(self):
raise NotImplementedError


def _get_odesk_client(request, **attrs):
"""Construct an oDesk client.
*Parameters:*
:attrs: keyword arguments that will be
attached to the ``client.auth`` as attributes
(``request_token``, etc.)
"""
client_kwargs = {
'oauth_access_token': attrs.pop('oauth_access_token', None),
'oauth_access_token_secret': attrs.pop(
'oauth_access_token_secret', None)

}
class Login(BaseHandler):
def post(self):
"""The login view performs following actions:
settings = request.registry.settings
client = odesk.Client(settings['odesk.api.key'],
settings['odesk.api.secret'], **client_kwargs)
- Redirects user to oDesk. If user is logged in, callback url
is invoked, otherwise user is asked to login to oDesk.
for key, value in attrs.items():
setattr(client.auth, key, value)
"""
client = get_odesk_client(self.request)
authorize_url = client.auth.get_authorize_url()
# Save request tokens in the session
self.request.session['odesk_request_token'] = client.auth.request_token
self.request.session['odesk_request_token_secret'] = \
client.auth.request_token_secret

return client
return HTTPFound(location=authorize_url)


def login(request):
"""The login view performs following actions:
class Logout(BaseHandler):
def post(self):
# Forget user
forget(self.request)
self.request.session.invalidate()
return HTTPFound('/')

- Redirects user to oDesk. If user is logged in, callback url
is invoked, otherwise user is asked to login to oDesk.

"""
client = _get_odesk_client(request)
authorize_url = client.auth.get_authorize_url()
# Save request tokens in the session
request.session['odesk_request_token'] = client.auth.request_token
request.session['odesk_request_token_secret'] = \
client.auth.request_token_secret

redirect_url = '{0}&callback_url={1}'.format(
authorize_url,
request.route_url('oauth_callback',
_query={'next': request.GET.get('next', '/')})
)
return HTTPFound(location=redirect_url)


def logout(request):
# Forget user
forget(request)
request.session.invalidate()
return HTTPFound('/')


def oauth_callback(request):
verifier = request.GET.get('oauth_verifier')
next_url = request.GET.get('next', '/')

request_token = request.session.pop('odesk_request_token', None)
request_token_secret = request.session.pop(
'odesk_request_token_secret', None)

if verifier:
client = _get_odesk_client(request, request_token=request_token,
request_token_secret=request_token_secret)
oauth_access_token, oauth_access_token_secret = \
client.auth.get_access_token(verifier)

client = _get_odesk_client(
request,
oauth_access_token=oauth_access_token,
oauth_access_token_secret=oauth_access_token_secret)

# Get user info
user_info = client.auth.get_info()
user_uid = user_info['auth_user']['uid']

# Store the user in session
remember(request, user_uid)
# Store oauth access token in session
request.session['auth.access_token'] = oauth_access_token
request.session['auth.access_token_secret'] = oauth_access_token_secret

# Redirect to ``next`` url
return HTTPFound(location=next_url)
class OauthCallback(BaseHandler):
def get(self):
request = self.request
verifier = request.GET.get('oauth_verifier')

request_token = request.session.pop('odesk_request_token', None)
request_token_secret = request.session.pop(
'odesk_request_token_secret', None)

if verifier:
client = get_odesk_client(
request, request_token=request_token,
request_token_secret=request_token_secret)
oauth_access_token, oauth_access_token_secret = \
client.auth.get_access_token(verifier)

client = get_odesk_client(
request,
oauth_access_token=oauth_access_token,
oauth_access_token_secret=oauth_access_token_secret)

# Get user info
user_info = client.auth.get_info()
user_uid = user_info['auth_user']['uid']

# Store the user in session
remember(request, user_uid)
# Store oauth access token in session
request.session['auth.access_token'] = oauth_access_token
request.session['auth.access_token_secret'] = \
oauth_access_token_secret

# Redirect to ``next`` url
return HTTPFound(location='/')


def forbidden(request):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -13,7 +13,7 @@
]

setup(name='pyramid_odesk',
version='1.1.1',
version='1.1.2',
description='pyramid_odesk',
long_description=README + '\n\n' + CHANGES,
classifiers=[
Expand Down

0 comments on commit 39093fe

Please sign in to comment.