Basic labels support
Adds basic labels support to the registry code (V2), and the API. Note that this does not yet add any UI related support.
This commit is contained in:
parent
427070b453
commit
608ffd9663
24 changed files with 907 additions and 36 deletions
|
@ -1,5 +1,5 @@
|
|||
from multiprocessing.sharedctypes import Array
|
||||
from util.validation import MAX_LENGTH
|
||||
from util.validation import MAX_USERNAME_LENGTH
|
||||
|
||||
class SuperUserManager(object):
|
||||
""" In-memory helper class for quickly accessing (and updating) the valid
|
||||
|
@ -11,7 +11,7 @@ class SuperUserManager(object):
|
|||
usernames = app.config.get('SUPER_USERS', [])
|
||||
usernames_str = ','.join(usernames)
|
||||
|
||||
self._max_length = len(usernames_str) + MAX_LENGTH + 1
|
||||
self._max_length = len(usernames_str) + MAX_USERNAME_LENGTH + 1
|
||||
self._array = Array('c', self._max_length, lock=True)
|
||||
self._array.value = usernames_str
|
||||
|
||||
|
|
20
util/label_validator.py
Normal file
20
util/label_validator.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
class LabelValidator(object):
|
||||
""" Helper class for validating that labels meet prefix requirements. """
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
overridden_prefixes = app.config.get('LABEL_KEY_RESERVED_PREFIXES', [])
|
||||
for prefix in overridden_prefixes:
|
||||
if not prefix.endswith('.'):
|
||||
raise Exception('Prefix "%s" in LABEL_KEY_RESERVED_PREFIXES must end in a dot', prefix)
|
||||
|
||||
default_prefixes = app.config.get('DEFAULT_LABEL_KEY_RESERVED_PREFIXES', [])
|
||||
self.reserved_prefixed_set = set(default_prefixes + overridden_prefixes)
|
||||
|
||||
def has_reserved_prefix(self, label_key):
|
||||
""" Validates that the provided label key does not match any reserved prefixes. """
|
||||
for prefix in self.reserved_prefixed_set:
|
||||
if label_key.startswith(prefix):
|
||||
return True
|
||||
|
||||
return False
|
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
|
||||
from sqlalchemy.types import TypeDecorator, Text
|
||||
from sqlalchemy.dialects.mysql import TEXT as MySQLText, LONGTEXT
|
||||
from sqlalchemy.types import TypeDecorator, Text, String
|
||||
from sqlalchemy.dialects.mysql import TEXT as MySQLText, LONGTEXT, VARCHAR as MySQLString
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -20,3 +20,19 @@ class UTF8LongText(TypeDecorator):
|
|||
return dialect.type_descriptor(LONGTEXT(charset='utf8mb4', collation='utf8mb4_unicode_ci'))
|
||||
else:
|
||||
return dialect.type_descriptor(Text())
|
||||
|
||||
|
||||
class UTF8CharField(TypeDecorator):
|
||||
""" Platform-independent UTF-8 Char type.
|
||||
|
||||
Uses MySQL's VARCHAR with charset utf8mb4, otherwise uses String, because
|
||||
other engines default to UTF-8.
|
||||
"""
|
||||
impl = String
|
||||
|
||||
def load_dialect_impl(self, dialect):
|
||||
if dialect.name == 'mysql':
|
||||
return dialect.type_descriptor(MySQLString(charset='utf8mb4', collation='utf8mb4_unicode_ci',
|
||||
length=self.impl.length))
|
||||
else:
|
||||
return dialect.type_descriptor(String(length=self.impl.length))
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import string
|
||||
import re
|
||||
import json
|
||||
|
||||
import anunidecode # Don't listen to pylint's lies. This import is required.
|
||||
|
||||
|
@ -9,14 +10,21 @@ INVALID_PASSWORD_MESSAGE = 'Invalid password, password must be at least ' + \
|
|||
INVALID_USERNAME_CHARACTERS = r'[^a-z0-9_]'
|
||||
VALID_CHARACTERS = string.digits + string.lowercase
|
||||
|
||||
MIN_LENGTH = 4
|
||||
MAX_LENGTH = 30
|
||||
MIN_USERNAME_LENGTH = 4
|
||||
MAX_USERNAME_LENGTH = 30
|
||||
|
||||
VALID_LABEL_KEY_REGEX = r'^[a-z0-9](([a-z0-9]|[-.](?![.-]))*[a-z0-9])?$'
|
||||
|
||||
|
||||
def validate_label_key(label_key):
|
||||
if len(label_key) > 255:
|
||||
return False
|
||||
|
||||
return bool(re.match(VALID_LABEL_KEY_REGEX, label_key))
|
||||
|
||||
|
||||
def validate_email(email_address):
|
||||
if re.match(r'[^@]+@[^@]+\.[^@]+', email_address):
|
||||
return True
|
||||
return False
|
||||
return bool(re.match(r'[^@]+@[^@]+\.[^@]+', email_address))
|
||||
|
||||
|
||||
def validate_username(username):
|
||||
|
@ -25,10 +33,10 @@ def validate_username(username):
|
|||
if not regex_match:
|
||||
return (False, 'Username must match expression [a-z0-9_]+')
|
||||
|
||||
length_match = (len(username) >= MIN_LENGTH and len(username) <= MAX_LENGTH)
|
||||
length_match = (len(username) >= MIN_USERNAME_LENGTH and len(username) <= MAX_USERNAME_LENGTH)
|
||||
if not length_match:
|
||||
return (False, 'Username must be between %s and %s characters in length' %
|
||||
(MIN_LENGTH, MAX_LENGTH))
|
||||
(MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH))
|
||||
|
||||
return (True, '')
|
||||
|
||||
|
@ -58,9 +66,20 @@ def generate_valid_usernames(input_username):
|
|||
if prefix.endswith('_'):
|
||||
prefix = prefix[0:len(prefix) - 1]
|
||||
|
||||
num_filler_chars = max(0, MIN_LENGTH - len(prefix))
|
||||
num_filler_chars = max(0, MIN_USERNAME_LENGTH - len(prefix))
|
||||
|
||||
while num_filler_chars + len(prefix) <= MAX_LENGTH:
|
||||
while num_filler_chars + len(prefix) <= MAX_USERNAME_LENGTH:
|
||||
for suffix in _gen_filler_chars(num_filler_chars):
|
||||
yield prefix + suffix
|
||||
num_filler_chars += 1
|
||||
|
||||
|
||||
def is_json(value):
|
||||
if ((value.startswith('{') and value.endswith('}')) or
|
||||
(value.startswith('[') and value.endswith(']'))):
|
||||
try:
|
||||
json.loads(value)
|
||||
return True
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
|
Reference in a new issue