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:
Joseph Schorr 2016-07-18 18:20:00 -04:00
parent 427070b453
commit 608ffd9663
24 changed files with 907 additions and 36 deletions

View file

@ -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
View 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

View file

@ -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))

View file

@ -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