Skip to content

Commit

Permalink
Merge 15211de into a8946bf
Browse files Browse the repository at this point in the history
  • Loading branch information
wasade committed Apr 5, 2017
2 parents a8946bf + 15211de commit c1cf276
Show file tree
Hide file tree
Showing 19 changed files with 640 additions and 226 deletions.
10 changes: 5 additions & 5 deletions qiita_db/handlers/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from json import loads

import qiita_db as qdb
from .oauth2 import OauthBaseHandler, authenticate_oauth
from .oauth2 import OauthBaseHandler, authenticate_oauth2


def _get_artifact(a_id):
Expand Down Expand Up @@ -46,7 +46,7 @@ def _get_artifact(a_id):


class ArtifactHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def get(self, artifact_id):
"""Retrieves the artifact information
Expand Down Expand Up @@ -109,7 +109,7 @@ def get(self, artifact_id):

self.write(response)

@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def patch(self, artifact_id):
"""Patches the artifact information
Expand Down Expand Up @@ -140,7 +140,7 @@ def patch(self, artifact_id):


class ArtifactAPItestHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self):
"""Creates a new artifact
Expand Down Expand Up @@ -180,7 +180,7 @@ def post(self):


class ArtifactTypeHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self):
"""Creates a new artifact type
Expand Down
4 changes: 2 additions & 2 deletions qiita_db/handlers/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------

from .oauth2 import OauthBaseHandler, authenticate_oauth
from .oauth2 import OauthBaseHandler, authenticate_oauth2
import qiita_db as qdb


class ResetAPItestHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self):
qdb.environment_manager.drop_and_rebuild_tst_database()
160 changes: 116 additions & 44 deletions qiita_db/handlers/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,66 +47,138 @@ def _oauth_error(handler, error_msg, error):
handler.finish()


def authenticate_oauth(f):
def _check_oauth2_header(handler):
"""Check if the oauth2 header is valid
Parameters
----------
handler : tornado.web.RequestHandler instance
The handler instance being requested
Returns
-------
errtype
The type of error, None if no error was observed
errdesc
A description of the error, None if no error was observed.
client_id
The observed client ID. This field is None if any error was observed.
"""
header = handler.request.headers.get('Authorization', None)

if header is None:
return ('invalid_request', 'Oauth2 error: invalid access token', None)

token_info = header.split()
# Based on RFC6750 if reply is not 2 elements in the format of:
# ['Bearer', token] we assume a wrong reply
if len(token_info) != 2 or token_info[0] != 'Bearer':
return ('invalid_grant', 'Oauth2 error: invalid access token', None)

token = token_info[1]
db_token = r_client.hgetall(token)
if not db_token:
# token has timed out or never existed
return ('invalid_grant', 'Oauth2 error: token has timed out', None)

# Check daily rate limit for key if password style key
if db_token['grant_type'] == 'password':
limit_key = '%s_%s_daily_limit' % (db_token['client_id'],
db_token['user'])
limiter = r_client.get(limit_key)
if limiter is None:
# Set limit to 5,000 requests per day
r_client.setex(limit_key, 5000, 86400)
else:
r_client.decr(limit_key)
if int(r_client.get(limit_key)) <= 0:
return ('invalid_grant',
'Oauth2 error: daily request limit reached', None)

return (None, None, db_token['client_id'])


class authenticate_oauth2:
"""Decorate methods to require valid Oauth2 Authorization header[1]
If a valid header is given, the handoff is done and the page is rendered.
If an invalid header is given, a 400 error code is returned and the json
error message is automatically sent.
Returns
-------
Sends oauth2 formatted error JSON if authorizaton fails
Notes
-----
Expects handler to be a tornado RequestHandler or subclass
Attributes
----------
default_public : bool
If True, execute the handler if a) the oauth2 token is acceptable or
b) if the Authorization header is not present. If False, the handler
will only be executed if the oauth2 token is acceptable.
inject_user : bool
If True, monkey patch the handler's get_current_user method to return
the instance of the User associated with the token's client ID. If
False, get_current_user is not monkey patched. If default_public is
also True, the default User returned is "demo@microbio.me"
References
----------
[1] The OAuth 2.0 Authorization Framework.
http://tools.ietf.org/html/rfc6749
"""
@functools.wraps(f)
def wrapper(handler, *args, **kwargs):
header = handler.request.headers.get('Authorization', None)
if header is None:
_oauth_error(handler, 'Oauth2 error: invalid access token',
'invalid_request')
return
token_info = header.split()
# Based on RFC6750 if reply is not 2 elements in the format of:
# ['Bearer', token] we assume a wrong reply
if len(token_info) != 2 or token_info[0] != 'Bearer':
_oauth_error(handler, 'Oauth2 error: invalid access token',
'invalid_grant')
return
def __init__(self, default_public=False, inject_user=False):
self.default_public = default_public
self.inject_user = inject_user

def get_user_maker(self, cid):
"""Produce a function which acts like get_current_user"""
def f():
if cid is None:
return qdb.user.User("demo@microbio.me")
else:
return qdb.user.User.from_client_id(cid)
return f

token = token_info[1]
db_token = r_client.hgetall(token)
if not db_token:
# token has timed out or never existed
_oauth_error(handler, 'Oauth2 error: token has timed out',
'invalid_grant')
return
# Check daily rate limit for key if password style key
if db_token['grant_type'] == 'password':
limit_key = '%s_%s_daily_limit' % (db_token['client_id'],
db_token['user'])
limiter = r_client.get(limit_key)
if limiter is None:
# Set limit to 5,000 requests per day
r_client.setex(limit_key, 5000, 86400)
def __call__(self, f):
"""Handle oauth, and execute the handler's method if appropriate
Parameters
----------
f : function
The function decorated is expected to be a member method of a
subclass of `Tornado.web.RequestHandler`
Notes
-----
If an error with oauth2 occurs, a status code of 400 is set, a message
about the error is sent out over `write` and the response is ended
with `finish`. This happens without control being passed to the
handler, and in this situation, the handler is not executed.
"""
@functools.wraps(f)
def wrapper(handler, *args, **kwargs):
errtype, errdesc, cid = _check_oauth2_header(handler)

if self.default_public:
# no error, or no authorization header. We should error if
# oauth is actually attempted but there was an auth issue
# (e.g., rate limit hit)
if errtype not in (None, 'invalid_request'):
_oauth_error(handler, errdesc, errtype)
return

if self.inject_user:
handler.get_current_user = self.get_user_maker(cid)
else:
r_client.decr(limit_key)
if int(r_client.get(limit_key)) <= 0:
_oauth_error(
handler, 'Oauth2 error: daily request limit reached',
'invalid_grant')
if errtype is not None:
_oauth_error(handler, errdesc, errtype)
return
if self.inject_user:
if cid is None:
raise ValueError("cid is None, without an oauth "
"error. This should never happen.")
else:
handler.get_current_user = self.get_user_maker(cid)

return f(handler, *args, **kwargs)

return f(handler, *args, **kwargs)
return wrapper
return wrapper


class OauthBaseHandler(RequestHandler):
Expand Down
12 changes: 6 additions & 6 deletions qiita_db/handlers/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from tornado.web import HTTPError

from .oauth2 import OauthBaseHandler, authenticate_oauth
from .oauth2 import OauthBaseHandler, authenticate_oauth2
from qiita_core.qiita_settings import qiita_config
import qiita_db as qdb

Expand Down Expand Up @@ -50,7 +50,7 @@ def _get_plugin(name, version):


class PluginHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def get(self, name, version):
"""Retrieve the plugin information
Expand Down Expand Up @@ -91,7 +91,7 @@ def get(self, name, version):


class CommandListHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self, name, version):
with qdb.sql_connection.TRN:
plugin = _get_plugin(name, version)
Expand Down Expand Up @@ -154,7 +154,7 @@ def _get_command(plugin_name, plugin_version, cmd_name):


class CommandHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def get(self, plugin_name, plugin_version, cmd_name):
"""Retrieve the command information
Expand Down Expand Up @@ -193,7 +193,7 @@ def get(self, plugin_name, plugin_version, cmd_name):


class CommandActivateHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self, plugin_name, plugin_version, cmd_name):
"""Activates the command
Expand All @@ -214,7 +214,7 @@ def post(self, plugin_name, plugin_version, cmd_name):


class ReloadPluginAPItestHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self):
"""Reloads the plugins"""
conf_files = glob(join(qiita_config.plugin_dir, "*.conf"))
Expand Down
8 changes: 4 additions & 4 deletions qiita_db/handlers/prep_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import pandas as pd

import qiita_db as qdb
from .oauth2 import OauthBaseHandler, authenticate_oauth
from .oauth2 import OauthBaseHandler, authenticate_oauth2


def _get_prep_template(pid):
Expand Down Expand Up @@ -48,7 +48,7 @@ def _get_prep_template(pid):


class PrepTemplateDBHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def get(self, prep_id):
"""Retrieves the prep template information
Expand Down Expand Up @@ -89,7 +89,7 @@ def get(self, prep_id):


class PrepTemplateDataHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def get(self, prep_id):
"""Retrieves the prep contents
Expand All @@ -111,7 +111,7 @@ def get(self, prep_id):


class PrepTemplateAPItestHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self):
prep_info_dict = loads(self.get_argument('prep_info'))
study = self.get_argument('study')
Expand Down
12 changes: 6 additions & 6 deletions qiita_db/handlers/processing_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from qiita_core.qiita_settings import qiita_config
import qiita_db as qdb
from .oauth2 import OauthBaseHandler, authenticate_oauth
from .oauth2 import OauthBaseHandler, authenticate_oauth2


def _get_job(job_id):
Expand Down Expand Up @@ -71,7 +71,7 @@ def _job_completer(job_id, payload):


class JobHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def get(self, job_id):
"""Get the job information
Expand Down Expand Up @@ -103,7 +103,7 @@ def get(self, job_id):


class HeartbeatHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self, job_id):
"""Update the heartbeat timestamp of the job
Expand All @@ -124,7 +124,7 @@ def post(self, job_id):


class ActiveStepHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self, job_id):
"""Changes the current exectuion step of the given job
Expand All @@ -146,7 +146,7 @@ def post(self, job_id):


class CompleteHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self, job_id):
"""Updates the job to one of the completed statuses: 'success', 'error'
Expand All @@ -171,7 +171,7 @@ def post(self, job_id):


class ProcessingJobAPItestHandler(OauthBaseHandler):
@authenticate_oauth
@authenticate_oauth2(default_public=False, inject_user=False)
def post(self):
user = self.get_argument('user', 'test@foo.bar')
s_name, s_version, cmd_name = loads(self.get_argument('command'))
Expand Down

0 comments on commit c1cf276

Please sign in to comment.