mirror of
https://github.com/LibreTranslate/LibreTranslate.git
synced 2025-05-15 14:32:53 +00:00
Add fingerprinting mechanism
This commit is contained in:
parent
da0890d60f
commit
f2268fe4d9
8 changed files with 65 additions and 20 deletions
|
@ -193,12 +193,13 @@ Arguments passed to the process or set via environment variables are split into
|
|||
### Settings / Flags
|
||||
|
||||
| Argument | Description | Default Setting | Env. name |
|
||||
| --------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------- | ---------------------------- |
|
||||
| ----------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------ |
|
||||
| --debug | Enable debug environment | `Disabled` | LT_DEBUG |
|
||||
| --ssl | Whether to enable SSL | `Disabled` | LT_SSL |
|
||||
| --api-keys | Enable API keys database for per-client rate limits when --req-limit is reached | `Don't use API keys` | LT_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` | LT_REQUIRE_API_KEY_ORIGIN |
|
||||
| --require-api-key-secret | Require use of an API key for programmatic access to the API, unless the client also sends a secret match | `No secrets required` | LT_REQUIRE_API_KEY_SECRET |
|
||||
| --require-api-key-fingerprint | Require use of an API key for programmatic access to the API, unless the client also matches a fingerprint | `No fingerprinting required` | LT_REQUIRE_API_KEY_FINGERPRINT |
|
||||
| --suggestions | Allow user suggestions | `Disabled` | LT_SUGGESTIONS |
|
||||
| --disable-files-translation | Disable files translation | `File translation allowed` | LT_DISABLE_FILES_TRANSLATION |
|
||||
| --disable-web-ui | Disable web ui | `Web Ui enabled` | LT_DISABLE_WEB_UI |
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.7.0
|
||||
1.7.1
|
||||
|
|
|
@ -102,6 +102,9 @@ def get_remote_address():
|
|||
|
||||
return ip
|
||||
|
||||
def get_fingerprint():
|
||||
return request.headers.get("User-Agent", "") + "|" + request.headers.get("Cookie", "")
|
||||
|
||||
|
||||
def get_req_limits(default_limit, api_keys_db, db_multiplier=1, multiplier=1):
|
||||
req_limit = default_limit
|
||||
|
@ -348,6 +351,7 @@ def create_app(args):
|
|||
and not secret.secret_match(req_secret)
|
||||
):
|
||||
need_key = True
|
||||
|
||||
if secret.secret_bogus_match(req_secret):
|
||||
abort(make_response(jsonify({
|
||||
'translatedText': secret.get_emoji(),
|
||||
|
@ -355,6 +359,10 @@ def create_app(args):
|
|||
'detectedLanguage': { 'confidence': 100, 'language': 'en' }
|
||||
}), 200))
|
||||
|
||||
if args.require_api_key_fingerprint:
|
||||
if flood.fingerprint_mismatch(ip, get_fingerprint()):
|
||||
need_key = True
|
||||
|
||||
if need_key:
|
||||
description = _("Please contact the server operator to get an API key")
|
||||
if args.get_api_key_link:
|
||||
|
|
|
@ -151,6 +151,11 @@ _default_options_objects = [
|
|||
'default_value': False,
|
||||
'value_type': 'bool'
|
||||
},
|
||||
{
|
||||
'name': 'REQUIRE_API_KEY_FINGERPRINT',
|
||||
'default_value': False,
|
||||
'value_type': 'bool'
|
||||
},
|
||||
{
|
||||
'name': 'SHARED_STORAGE',
|
||||
'default_value': 'memory://',
|
||||
|
|
|
@ -45,3 +45,16 @@ def is_banned(request_ip):
|
|||
|
||||
# More than X offences?
|
||||
return active and s.get_hash_int("banned", request_ip) >= threshold
|
||||
|
||||
def fingerprint_mismatch(request_ip, fingerprint):
|
||||
if not isinstance(fingerprint, str) or fingerprint == "":
|
||||
return True
|
||||
|
||||
s = get_storage()
|
||||
k = f"fingerprint:{request_ip}"
|
||||
expected = s.get_str(k)
|
||||
if expected == "":
|
||||
s.set_str(k, fingerprint, ex=300)
|
||||
return False
|
||||
else:
|
||||
return fingerprint != expected
|
|
@ -147,6 +147,12 @@ def get_args():
|
|||
action="store_true",
|
||||
help="Require use of an API key for programmatic access to the API, unless the client also sends a secret match",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--require-api-key-fingerprint",
|
||||
default=DEFARGS['REQUIRE_API_KEY_FINGERPRINT'],
|
||||
action="store_true",
|
||||
help="Require use of an API key for programmatic access to the API, unless the client also matches a fingerprint",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--shared-storage",
|
||||
type=str,
|
||||
|
|
|
@ -108,7 +108,7 @@ def get_emoji():
|
|||
return random.choice(["😂", "🤪", "😜", "🤣", "😹", "🐒", "🙈", "🤡", "🥸", "😆", "🥴", "🐸", "🐤", "🐒🙊", "👀", "💩", "🤯", "😛", "🤥", "👻"])
|
||||
|
||||
def setup(args):
|
||||
if args.api_keys and args.require_api_key_secret:
|
||||
if args.require_api_key_secret:
|
||||
s = get_storage()
|
||||
|
||||
if not s.exists("secret_0"):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import redis
|
||||
import time
|
||||
|
||||
storage = None
|
||||
def get_storage():
|
||||
|
@ -18,7 +19,7 @@ class Storage:
|
|||
def get_int(self, key):
|
||||
raise Exception("not implemented")
|
||||
|
||||
def set_str(self, key, value):
|
||||
def set_str(self, key, value, ex=None):
|
||||
raise Exception("not implemented")
|
||||
def get_str(self, key):
|
||||
raise Exception("not implemented")
|
||||
|
@ -56,11 +57,22 @@ class MemoryStorage(Storage):
|
|||
def get_int(self, key):
|
||||
return int(self.store.get(key, 0))
|
||||
|
||||
def set_str(self, key, value):
|
||||
self.store[key] = value
|
||||
def set_str(self, key, value, ex=None):
|
||||
self.store[key] = {
|
||||
'value': value,
|
||||
'ex': time.time() + ex
|
||||
}
|
||||
|
||||
def get_str(self, key):
|
||||
return str(self.store.get(key, ""))
|
||||
d = self.store.get(key, {'value': '', 'ex': None})
|
||||
if d['ex'] is None:
|
||||
return d['value']
|
||||
else:
|
||||
if d['ex'] <= time.time():
|
||||
del self.store[key]
|
||||
return ''
|
||||
else:
|
||||
return d['value']
|
||||
|
||||
def set_hash_int(self, ns, key, value):
|
||||
if ns not in self.store:
|
||||
|
@ -123,8 +135,8 @@ class RedisStorage(Storage):
|
|||
else:
|
||||
return v
|
||||
|
||||
def set_str(self, key, value):
|
||||
self.conn.set(key, value)
|
||||
def set_str(self, key, value, ex=None):
|
||||
self.conn.set(key, value, ex=ex)
|
||||
|
||||
def get_str(self, key):
|
||||
v = self.conn.get(key)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue