Merge pull request #89 from pierotofy/origin

Programmatic api keys access
This commit is contained in:
Piero Toffanin 2021-05-17 11:44:41 -04:00 committed by GitHub
commit 167f551a96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 31 additions and 4 deletions

View file

@ -115,6 +115,7 @@ docker-compose up -d --build
| --frontend-language-target | Set frontend default language - target | `es` | | --frontend-language-target | Set frontend default language - target | `es` |
| --frontend-timeout | Set frontend translation timeout | `500` | | --frontend-timeout | Set frontend translation timeout | `500` |
| --api-keys | Enable API keys database for per-user rate limits lookup | `Don't use API keys` | | --api-keys | Enable API keys database for per-user rate limits lookup | `Don't use API keys` |
| --require-api-key-origin | Require use of an API key for programmatic access to the API, unless the request origin matches this domain | `No restrictions on domain origin` |
| --load-only | Set available languages | `all from argostranslate` | | --load-only | Set available languages | `all from argostranslate` |
## Manage API Keys ## Manage API Keys

View file

@ -6,6 +6,7 @@ from pkg_resources import resource_filename
from .api_keys import Database from .api_keys import Database
from app.language import detect_languages, transliterate from app.language import detect_languages, transliterate
from app import flood from app import flood
from functools import wraps
def get_json_dict(request): def get_json_dict(request):
d = request.get_json() d = request.get_json()
@ -50,6 +51,7 @@ def get_routes_limits(default_req_limit, daily_req_limit, api_keys_db):
return res return res
def create_app(args): def create_app(args):
from app.init import boot from app.init import boot
boot(args.load_only) boot(args.load_only)
@ -77,12 +79,16 @@ def create_app(args):
if frontend_argos_language_target is None: if frontend_argos_language_target is None:
raise AttributeError(f"{args.frontend_language_target} as frontend target language is not supported.") raise AttributeError(f"{args.frontend_language_target} as frontend target language is not supported.")
api_keys_db = None
if args.req_limit > 0 or args.api_keys or args.daily_req_limit > 0: if args.req_limit > 0 or args.api_keys or args.daily_req_limit > 0:
api_keys_db = Database() if args.api_keys else None
from flask_limiter import Limiter from flask_limiter import Limiter
limiter = Limiter( limiter = Limiter(
app, app,
key_func=get_remote_address, key_func=get_remote_address,
default_limits=get_routes_limits(args.req_limit, args.daily_req_limit, Database() if args.api_keys else None) default_limits=get_routes_limits(args.req_limit, args.daily_req_limit, api_keys_db)
) )
else: else:
from .no_limiter import Limiter from .no_limiter import Limiter
@ -91,6 +97,25 @@ def create_app(args):
if args.req_flood_threshold > 0: if args.req_flood_threshold > 0:
flood.setup(args.req_flood_threshold) flood.setup(args.req_flood_threshold)
def access_check(f):
@wraps(f)
def func(*a, **kw):
if flood.is_banned(get_remote_address()):
abort(403, description="Too many request limits violations")
if args.api_keys and args.require_api_key_origin:
if request.is_json:
json = get_json_dict(request)
ak = json.get("api_key")
else:
ak = request.values.get("api_key")
if api_keys_db.lookup(ak) is None and request.headers.get("Origin") != args.require_api_key_origin:
abort(403, description="Please contact the server operator to obtain an API key")
return f(*a, **kw)
return func
@app.errorhandler(400) @app.errorhandler(400)
def invalid_api(e): def invalid_api(e):
return jsonify({"error": str(e.description)}), 400 return jsonify({"error": str(e.description)}), 400
@ -167,6 +192,7 @@ def create_app(args):
@app.route("/translate", methods=['POST']) @app.route("/translate", methods=['POST'])
@access_check
def translate(): def translate():
""" """
Translate text from a language to another Translate text from a language to another
@ -254,9 +280,6 @@ def create_app(args):
type: string type: string
description: Error message description: Error message
""" """
if flood.is_banned(get_remote_address()):
abort(403, description="Too many request limits violations")
if request.is_json: if request.is_json:
json = get_json_dict(request) json = get_json_dict(request)
q = json.get('q') q = json.get('q')
@ -320,6 +343,7 @@ def create_app(args):
abort(500, description="Cannot translate text: %s" % str(e)) abort(500, description="Cannot translate text: %s" % str(e))
@app.route("/detect", methods=['POST']) @app.route("/detect", methods=['POST'])
@access_check
def detect(): def detect():
""" """
Detect the language of a single text Detect the language of a single text

View file

@ -32,6 +32,8 @@ def main():
help='Set frontend translation timeout (%(default)s)') help='Set frontend translation timeout (%(default)s)')
parser.add_argument('--api-keys', default=False, action="store_true", parser.add_argument('--api-keys', default=False, action="store_true",
help="Enable API keys database for per-user rate limits lookup") help="Enable API keys database for per-user rate limits lookup")
parser.add_argument('--require-api-key-origin', type=str, default="",
help="Require use of an API key for programmatic access to the API, unless the request origin matches this domain")
parser.add_argument('--load-only', type=operator.methodcaller('split', ','), parser.add_argument('--load-only', type=operator.methodcaller('split', ','),
metavar='<comma-separated language codes>', metavar='<comma-separated language codes>',
help='Set available languages (ar,de,en,es,fr,ga,hi,it,ja,ko,pt,ru,zh)') help='Set available languages (ar,de,en,es,fr,ga,hi,it,ja,ko,pt,ru,zh)')