Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F82610027
handlers.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Thu, Sep 12, 10:08
Size
13 KB
Mime Type
text/x-python
Expires
Sat, Sep 14, 10:08 (2 d)
Engine
blob
Format
Raw Data
Handle
20722031
Attached To
R3600 invenio-infoscience
handlers.py
View Options
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2014, 2015 CERN.
#
# Invenio is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
"""Handlers for customizing oauthclient endpoints."""
import
warnings
from
functools
import
partial
,
wraps
from
flask
import
current_app
,
flash
,
redirect
,
render_template
,
\
request
,
session
,
url_for
from
flask.ext.login
import
current_user
import
six
from
werkzeug.utils
import
import_string
from
invenio.base.globals
import
cfg
from
.client
import
oauth
,
signup_handlers
from
.errors
import
OAuthClientError
,
OAuthError
,
\
OAuthRejectedRequestError
,
OAuthResponseError
from
.forms
import
EmailSignUpForm
from
.models
import
RemoteAccount
,
RemoteToken
from
.utils
import
oauth_authenticate
,
oauth_get_user
,
oauth_register
#
# Token handling
#
def
get_session_next_url
(
remote_app
):
"""Return redirect url stored in session."""
return
session
.
get
(
"
%s
_
%s
"
%
(
token_session_key
(
remote_app
),
"next_url"
)
)
def
set_session_next_url
(
remote_app
,
url
):
"""Store redirect url in session for security reasons."""
session
[
"
%s
_
%s
"
%
(
token_session_key
(
remote_app
),
"next_url"
)]
=
\
url
def
token_session_key
(
remote_app
):
"""Generate a session key used to store the token for a remote app."""
return
'
%s
_
%s
'
%
(
cfg
[
'OAUTHCLIENT_SESSION_KEY_PREFIX'
],
remote_app
)
def
response_token_setter
(
remote
,
resp
):
"""Extract token from response and set it for the user."""
if
resp
is
None
:
raise
OAuthRejectedRequestError
(
"User rejected request."
,
remote
,
resp
)
else
:
if
'access_token'
in
resp
:
return
oauth2_token_setter
(
remote
,
resp
)
elif
'oauth_token'
in
resp
and
'oauth_token_secret'
in
resp
:
return
oauth1_token_setter
(
remote
,
resp
)
elif
'error'
in
resp
:
# Only OAuth2 specifies how to send error messages
raise
OAuthClientError
(
'Authorization with remote service failed.'
,
remote
,
resp
,
)
raise
OAuthResponseError
(
"Bad OAuth authorized request"
,
remote
,
resp
)
def
oauth1_token_setter
(
remote
,
resp
,
token_type
=
''
,
extra_data
=
None
):
"""Set an OAuth1 token."""
return
token_setter
(
remote
,
resp
[
'oauth_token'
],
secret
=
resp
[
'oauth_token_secret'
],
extra_data
=
extra_data
,
token_type
=
token_type
,
)
def
oauth2_token_setter
(
remote
,
resp
,
token_type
=
''
,
extra_data
=
None
):
"""Set an OAuth2 token.
The refresh_token can be used to obtain a new access_token after
the old one is expired. It is saved in the database for long term use.
A refresh_token will be present only if `access_type=offline` is included
in the authorization code request.
"""
return
token_setter
(
remote
,
resp
[
'access_token'
],
secret
=
''
,
token_type
=
token_type
,
extra_data
=
extra_data
,
)
def
token_setter
(
remote
,
token
,
secret
=
''
,
token_type
=
''
,
extra_data
=
None
):
"""Set token for user."""
session
[
token_session_key
(
remote
.
name
)]
=
(
token
,
secret
)
# Save token if used is authenticated
if
current_user
.
is_authenticated
():
uid
=
current_user
.
get_id
()
cid
=
remote
.
consumer_key
# Check for already existing token
t
=
RemoteToken
.
get
(
uid
,
cid
,
token_type
=
token_type
)
if
t
:
t
.
update_token
(
token
,
secret
)
else
:
t
=
RemoteToken
.
create
(
uid
,
cid
,
token
,
secret
,
token_type
=
token_type
,
extra_data
=
extra_data
)
return
t
return
None
def
token_getter
(
remote
,
token
=
''
):
"""Retrieve OAuth access token.
Used by flask-oauthlib to get the access token when making requests.
:param token: Type of token to get. Data passed from ``oauth.request()`` to
identify which token to retrieve.
"""
session_key
=
token_session_key
(
remote
.
name
)
if
session_key
not
in
session
and
current_user
.
is_authenticated
():
# Fetch key from token store if user is authenticated, and the key
# isn't already cached in the session.
remote_token
=
RemoteToken
.
get
(
current_user
.
get_id
(),
remote
.
consumer_key
,
token_type
=
token
,
)
if
remote_token
is
None
:
return
None
# Store token and secret in session
session
[
session_key
]
=
remote_token
.
token
()
return
session
.
get
(
session_key
,
None
)
def
token_delete
(
remote
,
token
=
''
):
"""Remove OAuth access tokens from session."""
session_key
=
token_session_key
(
remote
.
name
)
return
session
.
pop
(
session_key
,
None
)
#
# Error handling decorators
#
def
oauth_error_handler
(
f
):
"""Decorator to handle exceptions."""
@wraps
(
f
)
def
inner
(
*
args
,
**
kwargs
):
# OAuthErrors should not happen, so they are not caught here. Hence
# they will result in a 500 Internal Server Error which is what we
# are interested in.
try
:
return
f
(
*
args
,
**
kwargs
)
except
OAuthClientError
as
e
:
current_app
.
logger
.
warning
(
e
.
message
,
exc_info
=
True
)
return
oauth2_handle_error
(
e
.
remote
,
e
.
response
,
e
.
code
,
e
.
uri
,
e
.
description
)
except
OAuthRejectedRequestError
:
flash
(
"You rejected the authentication request."
,
category
=
'info'
)
return
redirect
(
'/'
)
return
inner
#
# Handlers
#
@oauth_error_handler
def
authorized_default_handler
(
resp
,
remote
,
*
args
,
**
kwargs
):
"""Store access token in session.
Default authorized handler.
"""
response_token_setter
(
remote
,
resp
)
return
redirect
(
'/'
)
@oauth_error_handler
def
authorized_signup_handler
(
resp
,
remote
,
*
args
,
**
kwargs
):
"""Handle sign-in/up functionality."""
# Remove any previously stored auto register session key
session
.
pop
(
token_session_key
(
remote
.
name
)
+
'_autoregister'
,
None
)
# Store token in session
# ----------------------
# Set token in session - token object only returned if
# current_user.is_autenticated().
token
=
response_token_setter
(
remote
,
resp
)
handlers
=
signup_handlers
[
remote
.
name
]
# Sign-in/up user
# ---------------
if
not
current_user
.
is_authenticated
():
account_info
=
handlers
[
'info'
](
resp
)
user
=
oauth_get_user
(
remote
.
consumer_key
,
account_info
=
account_info
,
access_token
=
token_getter
(
remote
)[
0
],
)
if
user
is
None
:
# Auto sign-up if user not found
user
=
oauth_register
(
account_info
)
if
user
is
None
:
# Auto sign-up requires extra information
session
[
token_session_key
(
remote
.
name
)
+
'_autoregister'
]
=
True
session
[
token_session_key
(
remote
.
name
)
+
"_account_info"
]
=
account_info
session
[
token_session_key
(
remote
.
name
)
+
"_response"
]
=
resp
return
redirect
(
url_for
(
".signup"
,
remote_app
=
remote
.
name
,
))
# Authenticate user
if
not
oauth_authenticate
(
remote
.
consumer_key
,
user
,
require_existing_link
=
False
,
remember
=
cfg
[
'OAUTHCLIENT_REMOTE_APPS'
]
[
remote
.
name
]
.
get
(
'remember'
,
False
)):
return
current_app
.
login_manager
.
unauthorized
()
# Link account
# ------------
# Need to store token in database instead of only the session when
# called first time.
token
=
response_token_setter
(
remote
,
resp
)
# Setup account
# -------------
if
not
token
.
remote_account
.
extra_data
:
try
:
handlers
[
'setup'
](
token
,
resp
)
except
TypeError
:
warnings
.
warn
(
'Method signature of setup signup handler is '
'deprecated. It must take three arguments (remote,'
' token, response).'
,
DeprecationWarning
)
handlers
[
'setup'
](
token
)
# Redirect to next
next_url
=
get_session_next_url
(
remote
.
name
)
if
next_url
:
return
redirect
(
next_url
)
else
:
return
redirect
(
'/'
)
def
disconnect_handler
(
remote
,
*
args
,
**
kwargs
):
"""Handle unlinking of remote account.
This default handler will just delete the remote account link. You may
wish to extend this module to perform clean-up in the remote service
before removing the link (e.g. removing install webhooks).
"""
if
not
current_user
.
is_authenticated
():
return
current_app
.
login_manager
.
unauthorized
()
account
=
RemoteAccount
.
get
(
user_id
=
current_user
.
get_id
(),
client_id
=
remote
.
consumer_key
)
if
account
:
account
.
delete
()
return
redirect
(
url_for
(
'oauthclient_settings.index'
))
def
signup_handler
(
remote
,
*
args
,
**
kwargs
):
"""Handle extra signup information."""
# User already authenticated so move on
if
current_user
.
is_authenticated
():
return
redirect
(
"/"
)
# Retrieve token from session
oauth_token
=
token_getter
(
remote
)
if
not
oauth_token
:
return
redirect
(
"/"
)
session_prefix
=
token_session_key
(
remote
.
name
)
# Test to see if this is coming from on authorized request
if
not
session
.
get
(
session_prefix
+
'_autoregister'
,
False
):
return
redirect
(
url_for
(
".login"
,
remote_app
=
remote
.
name
))
form
=
EmailSignUpForm
(
request
.
form
)
if
form
.
validate_on_submit
():
account_info
=
session
.
get
(
session_prefix
+
"_account_info"
)
response
=
session
.
get
(
session_prefix
+
"_response"
)
# Register user
user
=
oauth_register
(
account_info
,
form
.
data
)
if
user
is
None
:
raise
OAuthError
(
"Could not create user."
,
remote
)
# Remove session key
session
.
pop
(
session_prefix
+
'_autoregister'
,
None
)
# Authenticate the user
if
not
oauth_authenticate
(
remote
.
consumer_key
,
user
,
require_existing_link
=
False
,
remember
=
cfg
[
'OAUTHCLIENT_REMOTE_APPS'
]
[
remote
.
name
]
.
get
(
'remember'
,
False
)):
return
current_app
.
login_manager
.
unauthorized
()
# Link account and set session data
token
=
token_setter
(
remote
,
oauth_token
[
0
],
secret
=
oauth_token
[
1
])
handlers
=
signup_handlers
[
remote
.
name
]
if
token
is
None
:
raise
OAuthError
(
"Could not create token for user."
,
remote
)
if
not
token
.
remote_account
.
extra_data
:
try
:
handlers
[
'setup'
](
token
,
response
)
except
TypeError
:
warnings
.
warn
(
'Method signature of setup signup handler is '
'deprecated. It must take three arguments'
' (remote, token, response).'
,
DeprecationWarning
)
handlers
[
'setup'
](
token
)
# Remove account info from session
session
.
pop
(
session_prefix
+
'_account_info'
,
None
)
session
.
pop
(
session_prefix
+
'_response'
,
None
)
# Redirect to next
next_url
=
get_session_next_url
(
remote
.
name
)
if
next_url
:
return
redirect
(
next_url
)
else
:
return
redirect
(
'/'
)
return
render_template
(
"oauthclient/signup.html"
,
form
=
form
,
remote
=
remote
,
app_title
=
cfg
[
'OAUTHCLIENT_REMOTE_APPS'
][
remote
.
name
]
.
get
(
'title'
,
''
),
app_description
=
cfg
[
'OAUTHCLIENT_REMOTE_APPS'
][
remote
.
name
]
.
get
(
'description'
,
''
),
app_icon
=
cfg
[
'OAUTHCLIENT_REMOTE_APPS'
][
remote
.
name
]
.
get
(
'icon'
,
None
),
)
def
oauth_logout_handler
(
sender_app
,
user
=
None
):
"""Remove all access tokens from session on logout."""
for
remote
in
oauth
.
remote_apps
.
values
():
token_delete
(
remote
)
#
# Helpers
#
def
make_handler
(
f
,
remote
,
with_response
=
True
):
"""Make a handler for authorized and disconnect callbacks.
:param f: Callable or an import path to a callable
"""
if
isinstance
(
f
,
six
.
string_types
):
f
=
import_string
(
f
)
@wraps
(
f
)
def
inner
(
*
args
,
**
kwargs
):
if
with_response
:
return
f
(
args
[
0
],
remote
,
*
args
[
1
:],
**
kwargs
)
else
:
return
f
(
remote
,
*
args
,
**
kwargs
)
return
inner
def
make_token_getter
(
remote
):
"""Make a token getter for a remote application."""
return
partial
(
token_getter
,
remote
)
def
oauth2_handle_error
(
remote
,
resp
,
error_code
,
error_uri
,
error_description
):
"""Handle errors during exchange of one-time code for an access tokens."""
flash
(
"Authorization with remote service failed."
)
return
redirect
(
'/'
)
Event Timeline
Log In to Comment