Skip to content

Commit

Permalink
Apply rate limit to high-resource endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
Aadesh-Baral committed Aug 14, 2022
1 parent 69d8274 commit d7bf376
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 2 deletions.
6 changes: 5 additions & 1 deletion backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import os
from logging.handlers import RotatingFileHandler

from flask import Flask, redirect
from flask import Flask, redirect, make_response, jsonify
from flask_cors import CORS
from flask_migrate import Migrate
from flask_oauthlib.client import OAuth
from flask_restful import Api
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

from backend.config import EnvironmentConfig

Expand All @@ -35,6 +37,7 @@ def format_url(endpoint):
migrate = Migrate()
mail = Mail()
oauth = OAuth()
limiter = Limiter(key_func=get_remote_address)

osm = oauth.remote_app("osm", app_key="OSM_OAUTH_SETTINGS")

Expand Down Expand Up @@ -64,6 +67,7 @@ def create_app(env="backend.config.EnvironmentConfig"):
db.init_app(app)
migrate.init_app(app, db)
mail.init_app(app)
limiter.init_app(app)

app.logger.debug("Add root redirect route")

Expand Down
6 changes: 6 additions & 0 deletions backend/api/campaigns/resources.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from flask_restful import Resource, request, current_app
from schematics.exceptions import DataError

from backend import limiter, EnvironmentConfig
from backend.models.dtos.campaign_dto import CampaignDTO, NewCampaignDTO
from backend.services.campaign_service import CampaignService
from backend.services.organisation_service import OrganisationService
Expand Down Expand Up @@ -212,6 +213,11 @@ def delete(self, campaign_id):


class CampaignsAllAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

def get(self):
"""
Get all active campaigns
Expand Down
11 changes: 11 additions & 0 deletions backend/api/projects/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from flask_restful import Resource, request, current_app
from schematics.exceptions import DataError

from backend import limiter, EnvironmentConfig
from backend.models.dtos.message_dto import MessageDTO
from backend.models.dtos.grid_dto import GridDTO
from backend.services.project_service import ProjectService, NotFound
Expand Down Expand Up @@ -78,6 +79,11 @@ def post(self, project_id):


class ProjectsActionsMessageContributorsAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self, project_id):
"""
Expand Down Expand Up @@ -355,6 +361,11 @@ def post(self, project_id):


class ProjectActionsIntersectingTilesAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@tm.pm_only()
@token_auth.login_required
def post(self):
Expand Down
7 changes: 7 additions & 0 deletions backend/api/projects/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from flask_restful import Resource, current_app, request
from schematics.exceptions import DataError
from distutils.util import strtobool

from backend import limiter, EnvironmentConfig
from backend.models.dtos.project_dto import (
DraftProjectDTO,
ProjectDTO,
Expand Down Expand Up @@ -32,6 +34,11 @@


class ProjectsRestAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required(optional=True)
def get(self, project_id):
"""
Expand Down
7 changes: 7 additions & 0 deletions backend/api/projects/statistics.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from flask_restful import Resource, current_app

from backend import limiter, EnvironmentConfig
from backend.services.stats_service import NotFound, StatsService
from backend.services.project_service import ProjectService

Expand Down Expand Up @@ -28,6 +30,11 @@ def get(self):


class ProjectsStatisticsAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["GET"])
]

def get(self, project_id):
"""
Get Project Stats
Expand Down
12 changes: 11 additions & 1 deletion backend/api/system/authentication.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from flask import session, current_app, redirect, request
from flask_restful import Resource

from backend import osm
from backend import osm, limiter, EnvironmentConfig
from backend.services.users.authentication_service import (
AuthenticationService,
AuthServiceError,
Expand All @@ -17,6 +17,11 @@ def get_oauth_token():


class SystemAuthenticationLoginAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["GET"])
]

def get(self):
"""
Redirects user to OSM to authenticate
Expand Down Expand Up @@ -44,6 +49,11 @@ def get(self):


class SystemAuthenticationCallbackAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["GET"])
]

def get(self):
"""
Handles the OSM OAuth callback
Expand Down
6 changes: 6 additions & 0 deletions backend/api/system/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from flask_restful import Resource, request, current_app
from flask_swagger import swagger

from backend import limiter, EnvironmentConfig
from backend.services.settings_service import SettingsService
from backend.services.messaging.smtp_service import SMTPService

Expand Down Expand Up @@ -184,6 +185,11 @@ def get(self):


class SystemContactAdminRestAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

def post(self):
"""
Send an email to the system admin
Expand Down
6 changes: 6 additions & 0 deletions backend/api/system/image_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@

from flask_restful import Resource, request, current_app

from backend import limiter, EnvironmentConfig
from backend.services.users.authentication_service import token_auth


class SystemImageUploadRestAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self):
"""
Expand Down
36 changes: 36 additions & 0 deletions backend/api/tasks/actions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from flask_restful import Resource, current_app, request
from schematics.exceptions import DataError

from backend import limiter, EnvironmentConfig
from backend.models.dtos.grid_dto import SplitTaskDTO
from backend.models.postgis.utils import NotFound, InvalidGeoJson
from backend.services.grid.split_service import SplitService, SplitServiceError
Expand Down Expand Up @@ -196,6 +197,11 @@ def post(self, project_id, task_id):


class TasksActionsMappingUnlockAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self, project_id, task_id):
"""
Expand Down Expand Up @@ -519,6 +525,11 @@ def post(self, project_id):


class TasksActionsValidationUnlockAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self, project_id):
"""
Expand Down Expand Up @@ -599,6 +610,11 @@ def post(self, project_id):


class TasksActionsMapAllAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self, project_id):
"""
Expand Down Expand Up @@ -656,6 +672,11 @@ def post(self, project_id):


class TasksActionsValidateAllAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self, project_id):
"""
Expand Down Expand Up @@ -713,6 +734,11 @@ def post(self, project_id):


class TasksActionsInvalidateAllAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self, project_id):
"""
Expand Down Expand Up @@ -770,6 +796,11 @@ def post(self, project_id):


class TasksActionsResetBadImageryAllAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self, project_id):
"""
Expand Down Expand Up @@ -829,6 +860,11 @@ def post(self, project_id):


class TasksActionsResetAllAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self, project_id):
"""
Expand Down
6 changes: 6 additions & 0 deletions backend/api/teams/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from schematics.exceptions import DataError
import threading

from backend import limiter, EnvironmentConfig
from backend.models.dtos.message_dto import MessageDTO
from backend.services.team_service import TeamService, NotFound, TeamJoinNotAllowed
from backend.services.users.authentication_service import token_auth, tm
Expand Down Expand Up @@ -252,6 +253,11 @@ def post(self, team_id):


class TeamsActionsMessageMembersAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

@token_auth.login_required
def post(self, team_id):
"""
Expand Down
6 changes: 6 additions & 0 deletions backend/api/users/actions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from flask_restful import Resource, current_app, request
from schematics.exceptions import DataError

from backend import limiter, EnvironmentConfig
from backend.models.dtos.user_dto import UserDTO, UserRegisterEmailDTO
from backend.services.messaging.message_service import MessageService
from backend.services.users.authentication_service import token_auth, tm
Expand Down Expand Up @@ -314,6 +315,11 @@ def patch(self):


class UsersActionsRegisterEmailAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["POST"])
]

def post(self):
"""
Registers users without OpenStreetMap account
Expand Down
6 changes: 6 additions & 0 deletions backend/api/users/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import date, timedelta
from flask_restful import Resource, request, current_app

from backend import limiter, EnvironmentConfig
from backend.services.users.user_service import UserService, NotFound
from backend.services.stats_service import StatsService
from backend.services.interests_service import InterestService
Expand Down Expand Up @@ -98,6 +99,11 @@ def get(self, user_id):


class UsersStatisticsAllAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["GET"])
]

@token_auth.login_required
def get(self):
"""
Expand Down
6 changes: 6 additions & 0 deletions backend/api/users/tasks.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from flask_restful import Resource, current_app, request
from dateutil.parser import parse as date_parse

from backend import limiter, EnvironmentConfig
from backend.services.users.authentication_service import token_auth
from backend.services.users.user_service import UserService, NotFound


class UsersTasksAPI(Resource):

decorators = [
limiter.limit(EnvironmentConfig.DEFAULT_RATE_LIMIT_THRESHOLD, methods=["GET"])
]

@token_auth.login_required
def get(self, user_id):
"""
Expand Down
5 changes: 5 additions & 0 deletions backend/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ class EnvironmentConfig:
# If disabled project update emails will not be sent.
SEND_PROJECT_EMAIL_UPDATES = int(os.getenv("TM_SEND_PROJECT_EMAIL_UPDATES", True))

# Threshold for rate limiting api calls
DEFAULT_RATE_LIMIT_THRESHOLD = os.getenv(
"TM_API_RATE_LIMIT_THRESHOLD", "100 per hour"
)

# Languages offered by the Tasking Manager
# Please note that there must be exactly the same number of Codes as languages.
SUPPORTED_LANGUAGES = {
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Flask-RESTful==0.3.8
Flask-Script==2.0.6
Flask-SQLAlchemy==2.4.4
flask-swagger==0.2.14
Flask-Limiter==1.5
gevent==20.9.0
GeoAlchemy2==0.8.4
geojson==1.3.4
Expand Down

0 comments on commit d7bf376

Please sign in to comment.