Skip to content

Commit

Permalink
Merge pull request #123 from marblestation/flasklimiter_upgrade
Browse files Browse the repository at this point in the history
Flasklimiter upgrade
  • Loading branch information
Taylor Shaulis authored Oct 30, 2017
2 parents 0a849e8 + 30b8dee commit b982b10
Show file tree
Hide file tree
Showing 21 changed files with 249 additions and 156 deletions.
12 changes: 4 additions & 8 deletions adsws/accounts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,10 @@

CORS_METHODS = ['GET', 'OPTIONS', 'POST', 'PUT']

CACHE = {
'CACHE_TYPE': 'redis',
'CACHE_REDIS_HOST': 'localhost',
'CACHE_REDIS_PORT': 6379,
'CACHE_REDIS_DB': 0,
'CACHE_KEY_PREFIX': 'accounts_',
}
RATELIMITER_BACKEND = 'flaskcacheredis'
RATELIMIT_STORAGE_URL = "redis://localhost:6379"
RATELIMIT_HEADERS_ENABLED = True
RATELIMIT_SWALLOW_ERRORS = True
RATELIMIT_KEY_PREFIX = "limiter"

GOOGLE_RECAPTCHA_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify'
MAIL_DEFAULT_SENDER = 'no-reply@adslabs.org'
37 changes: 19 additions & 18 deletions adsws/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
EmailChangedNotification



class StatusView(Resource):
"""
Health check resource
Expand Down Expand Up @@ -207,7 +208,7 @@ class PersonalTokenView(Resource):
Implements getting/setting a personal API token
"""
decorators = [
ratelimit(500, 43200, scope_func=scope_func),
ratelimit.shared_limit_and_check("500/43200 second", scope=scope_func),
login_required,
]

Expand Down Expand Up @@ -322,7 +323,7 @@ class ChangeEmailView(Resource):
"""

decorators = [
ratelimit(5, 600, scope_func=scope_func),
ratelimit.shared_limit_and_check("5/600 second", scope=scope_func),
login_required,
]

Expand Down Expand Up @@ -377,7 +378,7 @@ class UserAuthView(Resource):
"""
Implements login and logout functionality
"""
decorators = [ratelimit(30, 120, scope_func=scope_func)]
decorators = [ratelimit.shared_limit_and_check("30/120 second", scope=scope_func)]

def post(self):
"""
Expand Down Expand Up @@ -416,7 +417,7 @@ class VerifyEmailView(Resource):
If the token is decoded, set User.confirm_at to datetime.now()
"""
decorators = [ratelimit(20, 600, scope_func=scope_func)]
decorators = [ratelimit.shared_limit_and_check("20/600 second", scope=scope_func)]

def get(self, token):
try:
Expand Down Expand Up @@ -464,7 +465,7 @@ class CSRFView(Resource):
Returns a csrf token
"""

decorators = [ratelimit(50, 600, scope_func=scope_func)]
decorators = [ratelimit.shared_limit_and_check("50/600 second", scope=scope_func)]

def get(self):
"""
Expand All @@ -478,7 +479,7 @@ class UserRegistrationView(Resource):
Implements new user registration
"""

decorators = [ratelimit(50, 600, scope_func=scope_func)]
decorators = [ratelimit.shared_limit_and_check("50/600 second", scope=scope_func)]

def post(self):
"""
Expand Down Expand Up @@ -542,7 +543,7 @@ def get(self):
a "BB Client" OAuthClient and token depending if that user already has
one in the database
"""

# rca: I'd like to register here my distaste for Flask-Restful and
# how it divorces parameters; it was a big mistake to go with that framework
# and the decision shouldn't have been left to inexperienced developers
Expand All @@ -553,18 +554,18 @@ def get(self):
parser.add_argument('scope', type=str)
parser.add_argument('client_name', type=str)
kwargs = parser.parse_args()

scopes = kwargs.get('scope', None)
client_name = kwargs.get('client_name', None)
redirect_uri = kwargs.get('redirect_uri', None)

# If we visit this endpoint and are unauthenticated, then login as
# our anonymous user
if not current_user.is_authenticated():
login_user(user_manipulator.first(
email=current_app.config['BOOTSTRAP_USER_EMAIL']
))

if scopes or client_name:
abort(401, "Sorry, you cant change scopes/name/redirect_uri of this user")

Expand All @@ -586,7 +587,7 @@ def get(self):
session['oauth_client'] = client.client_id
else:
client, token = Bootstrap.bootstrap_user()

if scopes:
client._default_scopes = scopes
if redirect_uri:
Expand All @@ -597,10 +598,10 @@ def get(self):
client.last_activity = datetime.datetime.now()
db.session.commit()
output = print_token(token)

output['client_id'] = client.client_id
output['client_secret'] = client.client_secret

return output

@staticmethod
Expand Down Expand Up @@ -632,7 +633,7 @@ def load_client(clientid):
return client, token

@staticmethod
@ratelimit(400, 60*60*24, scope_func=scope_func)
@ratelimit.shared_limit_and_check("400/86400 second", scope=scope_func)
def bootstrap_bumblebee():
"""
Return or create a OAuthClient owned by the "bumblebee" user.
Expand Down Expand Up @@ -683,7 +684,7 @@ def bootstrap_bumblebee():
return client, token

@staticmethod
@ratelimit(100, 600, scope_func=scope_func)
@ratelimit.shared_limit_and_check("100/600 second", scope=scope_func)
def bootstrap_user():
"""
Return or create a OAuthClient owned by the authenticated real user.
Expand All @@ -703,7 +704,7 @@ def bootstrap_user():
user_id=uid,
name=client_name,
).first()

scopes = ' '.join(current_app.config['USER_DEFAULT_SCOPES'])
salt_length = current_app.config.get('OAUTH2_CLIENT_ID_SALT_LEN', 40)

Expand Down Expand Up @@ -754,8 +755,8 @@ def bootstrap_user():
db.session.add(token)
current_app.logger.info(
"Created BB client for {email}".format(email=current_user.email)
)
)

db.session.commit()
return client, token

12 changes: 4 additions & 8 deletions adsws/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@
]
CORS_METHODS = ['GET', 'OPTIONS', 'POST']

CACHE = {
'CACHE_TYPE': 'redis',
'CACHE_REDIS_HOST': 'localhost',
'CACHE_REDIS_PORT': 6379,
'CACHE_REDIS_DB': 0,
'CACHE_KEY_PREFIX': 'api_',
}
RATELIMITER_BACKEND = 'flaskcacheredis'
RATELIMIT_STORAGE_URL = "redis://localhost:6379"
RATELIMIT_HEADERS_ENABLED = True
RATELIMIT_SWALLOW_ERRORS = True
RATELIMIT_KEY_PREFIX = "limiter"

WEBSERVICES_PUBLISH_ENDPOINT = 'resources'
WEBSERVICES = {
Expand Down
34 changes: 17 additions & 17 deletions adsws/api/discoverer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from urlparse import urljoin
import traceback
from importlib import import_module
from adsws.ext.ratelimiter import ratelimit, scope_func, limit_func
from adsws.ext.ratelimiter import ratelimit, limit_func, scope_func, key_func
from flask.ext.consulate import ConsulService


Expand All @@ -20,6 +20,7 @@ def bootstrap_local_module(service_uri, deploy_path, app):
:param app: flask.Flask application instance
:return: None
"""

app.logger.debug(
'Attempting bootstrap_local_module [{0}]'.format(service_uri)
)
Expand Down Expand Up @@ -48,11 +49,11 @@ def bootstrap_local_module(service_uri, deploy_path, app):
# Decorate the view with ratelimit
if hasattr(attr_base, 'rate_limit'):
d = attr_base.rate_limit[0]
view = ratelimit(
limit=lambda default=d, **kwargs: limit_func(default),
per=attr_base.rate_limit[1],
scope_func=lambda: scope_func(),
key_func=lambda: request.endpoint
view = ratelimit.shared_limit_and_check(
lambda counts=d, per_second=attr_base.rate_limit[1]: limit_func(counts, per_second),
scope=scope_func,
key_func=key_func,
methods=rule.methods,
)(view)

# Decorate the view with require_oauth
Expand All @@ -79,7 +80,6 @@ def bootstrap_remote_service(service_uri, deploy_path, app):
:param app: flask.Flask application instance
:return: None
"""

app.logger.debug(
'Attempting bootstrap_remote_service [{0}]'.format(service_uri)
)
Expand All @@ -99,12 +99,12 @@ def bootstrap_remote_service(service_uri, deploy_path, app):
service_uri,
app.config.get('WEBSERVICES_PUBLISH_ENDPOINT', '/')
)

cache_key = service_uri.replace('/', '').replace('\\', '').replace('.', '')
cache_dir = app.config.get('WEBSERVICES_DISCOVERY_CACHE_DIR', '')
cache_path = os.path.join(cache_dir, cache_key)
resource_json = {}

# discover the ratelimits/urls/permissions from the service itself;
# if not available, use a cached values (if any)
try:
Expand All @@ -116,14 +116,14 @@ def bootstrap_remote_service(service_uri, deploy_path, app):
cf.write(json.dumps(resource_json))
except IOError:
app.logger.error('Cant write cached resource {0}'.format(cache_path))
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
if cache_dir and os.path.exists(cache_path):
with open(cache_path, 'r') as cf:
resource_json = json.loads(cf.read())
else:
app.logger.info('Could not discover {0}'.format(service_uri))
return



# Start constructing the ProxyViews based on what we got when querying
Expand All @@ -141,7 +141,7 @@ def bootstrap_remote_service(service_uri, deploy_path, app):
# the location to the third party resource (ProxyView.endpoint)
with app.app_context():
# app_context to allow config lookup via current_app in __init__
proxyview = ProxyView(remote_route, service_uri, deploy_path)
proxyview = ProxyView(remote_route, service_uri, deploy_path, route)

for method in properties['methods']:
if method not in proxyview.methods:
Expand All @@ -156,11 +156,11 @@ def bootstrap_remote_service(service_uri, deploy_path, app):

# Decorate the view with ratelimit.
d = properties['rate_limit'][0]
view = ratelimit(
limit=lambda default=d, **kwargs: limit_func(default),
per=properties['rate_limit'][1],
scope_func=lambda: scope_func(),
key_func=lambda: request.endpoint,
view = ratelimit.shared_limit_and_check(
lambda counts=d, per_second=properties['rate_limit'][1]: limit_func(counts, per_second),
scope=scope_func,
key_func=key_func,
methods=[method],
)(view)

# Decorate with the advertised oauth2 scopes
Expand Down
3 changes: 2 additions & 1 deletion adsws/api/discoverer/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
class ProxyView(Resource):
"""Proxies a request to a remote webservice"""

def __init__(self, endpoint, service_uri, deploy_path):
def __init__(self, endpoint, service_uri, deploy_path, route):
self.endpoint = endpoint
self.service_uri = service_uri
self.deploy_path = deploy_path
self.route = route
self.cs = None
if service_uri.startswith('consul://'):
self.cs = ConsulService(
Expand Down
4 changes: 2 additions & 2 deletions adsws/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
#flask Setting for debug view in case of errors
DEBUG = False
#Flask setting for unittest (be sure this value is FALSE on real site!)
TESTING = False
TESTING = False

# Be careful; if using sqlite AND the path is relative
# then each application can get a different database
# e.g. if you have 'sqlite:///adsws.sqlite' then the
# e.g. if you have 'sqlite:///adsws.sqlite' then the
# db will be saved at adsws/api/adsws.sqlite
SQLALCHEMY_DATABASE_URI = 'sqlite://'
SITE_SECURE_URL = 'http://0.0.0.0:5000'
Loading

0 comments on commit b982b10

Please sign in to comment.