trove/trove/guestagent/db/models.py

1122 lines
37 KiB
Python

# Copyright (c) 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import re
import netaddr
from six import u
from trove.common import cfg
from trove.common import exception
from trove.common.i18n import _
from trove.common import utils
CONF = cfg.CONF
class Base(object):
def serialize(self):
return self.__dict__
def deserialize(self, o):
self.__dict__ = o
@classmethod
def _validate_dict(cls, value):
reqs = cls._dict_requirements()
return (isinstance(value, dict) and
all(key in value for key in reqs))
@classmethod
@abc.abstractmethod
def _dict_requirements(cls):
"""Get the dictionary requirements for a user created via
deserialization.
:returns: List of required dictionary keys.
"""
class DatastoreSchema(Base):
"""Represents a database schema."""
def __init__(self):
self._name = None
self._collate = None
self._character_set = None
@classmethod
def deserialize_schema(cls, value):
if not cls._validate_dict(value):
raise ValueError(_("Bad dictionary. Keys: %(keys)s. "
"Required: %(reqs)s")
% ({'keys': value.keys(),
'reqs': cls._dict_requirements()}))
schema = cls(deserializing=True)
schema.deserialize(value)
return schema
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._validate_schema_name(value)
self._name = value
@property
def collate(self):
return self._collate
@property
def character_set(self):
return self._character_set
def _validate_schema_name(self, value):
"""Perform validations on a given schema name.
:param value: Validated schema name.
:type value: string
:raises: ValueError On validation errors.
"""
if self._max_schema_name_length and (len(value) >
self._max_schema_name_length):
raise ValueError(_("Schema name '%(name)s' is too long. "
"Max length = %(max_length)d.")
% {'name': value,
'max_length': self._max_schema_name_length})
elif not self._is_valid_schema_name(value):
raise ValueError(_("'%s' is not a valid schema name.") % value)
@abc.abstractproperty
def _max_schema_name_length(self):
"""Return the maximum valid schema name length if any.
:returns: Maximum schema name length or None if unlimited.
"""
@abc.abstractmethod
def _is_valid_schema_name(self, value):
"""Validate a given schema name.
:param value: Validated schema name.
:type value: string
:returns: TRUE if valid, FALSE otherwise.
"""
@classmethod
@abc.abstractmethod
def _dict_requirements(cls):
"""Get the dictionary requirements for a user created via
deserialization.
:returns: List of required dictionary keys.
"""
class MongoDBSchema(DatastoreSchema):
"""Represents the MongoDB schema and its associated properties.
MongoDB database names are limited to 128 characters,
alphanumeric and - and _ only.
"""
name_regex = re.compile(r'^[a-zA-Z0-9_\-]+$')
def __init__(self, name=None, deserializing=False):
super(MongoDBSchema, self).__init__()
# need one or the other, not both, not none (!= ~ XOR)
if not (bool(deserializing) != bool(name)):
raise ValueError(_("Bad args. name: %(name)s, "
"deserializing %(deser)s.")
% ({'name': bool(name),
'deser': bool(deserializing)}))
if not deserializing:
self.name = name
@property
def _max_schema_name_length(self):
return 64
def _is_valid_schema_name(self, value):
# check against the invalid character set from
# http://docs.mongodb.org/manual/reference/limits
return not any(c in value for c in '/\. "$')
@classmethod
def _dict_requirements(cls):
return ['_name']
class CassandraSchema(DatastoreSchema):
"""Represents a Cassandra schema and its associated properties.
Keyspace names are 32 or fewer alpha-numeric characters and underscores,
the first of which is an alpha character.
"""
def __init__(self, name=None, deserializing=False):
super(CassandraSchema, self).__init__()
if not (bool(deserializing) != bool(name)):
raise ValueError(_("Bad args. name: %(name)s, "
"deserializing %(deser)s.")
% ({'name': bool(name),
'deser': bool(deserializing)}))
if not deserializing:
self.name = name
@property
def _max_schema_name_length(self):
return 32
def _is_valid_schema_name(self, value):
return True
@classmethod
def _dict_requirements(cls):
return ['_name']
class CouchDBSchema(DatastoreSchema):
'''Represents the CouchDB schema and its associated properties.
The database name must consist of one or more of the following characters
and the name must begin with a lowercase letter.
- Lowercase characters (a-z)
- Digits (0-9)
- Any of the characters _, $, (, ), +, -, and /
'''
name_regex = re.compile(r'^[a-z][a-z0-9_$()+/-]*$')
def __init__(self, name=None, deserializing=False):
super(CouchDBSchema, self).__init__()
self._ignore_dbs = cfg.get_ignored_dbs()
# need one or the other, not both, not none (!= ~ XOR)
if not (bool(deserializing) != bool(name)):
raise ValueError(_("Bad args. name: %(name)s, "
"deserializing %(deser)s.")
% ({'name': bool(name),
'deser': bool(deserializing)}))
if not deserializing:
self.name = name
@property
def _max_schema_name_length(self):
return None
def _is_valid_schema_name(self, value):
# https://wiki.apache.org/couchdb/HTTP_database_API
if value.lower() in self._ignore_dbs:
return False
if re.match(r'^[a-z]*$', value[0]):
return True
else:
return False
@classmethod
def _dict_requirements(cls):
return ['_name']
class PostgreSQLSchema(DatastoreSchema):
"""Represents a PostgreSQL schema and its associated properties.
Permitted characters in quoted identifiers include the full
Unicode Basic Multilingual Plane (BMP), except U+0000.
Database, table, and column names cannot end with space characters.
"""
name_regex = re.compile(u(r'^[\u0001-\u007F\u0080-\uFFFF]+[^\s]$'))
def __init__(self, name=None, deserializing=False):
super(PostgreSQLSchema, self).__init__()
if not (bool(deserializing) != bool(name)):
raise ValueError(_("Bad args. name: %(name)s, "
"deserializing %(deser)s.")
% ({'name': bool(name),
'deser': bool(deserializing)}))
if not deserializing:
self.name = name
@property
def _max_schema_name_length(self):
return 63
def _is_valid_schema_name(self, value):
return self.name_regex.match(value) is not None
@classmethod
def _dict_requirements(cls):
return ['_name']
class MySQLDatabase(Base):
"""Represents a Database and its properties."""
# Defaults
__charset__ = "utf8"
__collation__ = "utf8_general_ci"
dbname = re.compile("^[A-Za-z0-9_-]+[\s\?\#\@]*[A-Za-z0-9_-]+$")
# Complete list of acceptable values
charset = {"big5": ["big5_chinese_ci", "big5_bin"],
"dec8": ["dec8_swedish_ci", "dec8_bin"],
"cp850": ["cp850_general_ci", "cp850_bin"],
"hp8": ["hp8_english_ci", "hp8_bin"],
"koi8r": ["koi8r_general_ci", "koi8r_bin"],
"latin1": ["latin1_swedish_ci",
"latin1_german1_ci",
"latin1_danish_ci",
"latin1_german2_ci",
"latin1_bin",
"latin1_general_ci",
"latin1_general_cs",
"latin1_spanish_ci"],
"latin2": ["latin2_general_ci",
"latin2_czech_cs",
"latin2_hungarian_ci",
"latin2_croatian_ci",
"latin2_bin"],
"swe7": ["swe7_swedish_ci", "swe7_bin"],
"ascii": ["ascii_general_ci", "ascii_bin"],
"ujis": ["ujis_japanese_ci", "ujis_bin"],
"sjis": ["sjis_japanese_ci", "sjis_bin"],
"hebrew": ["hebrew_general_ci", "hebrew_bin"],
"tis620": ["tis620_thai_ci", "tis620_bin"],
"euckr": ["euckr_korean_ci", "euckr_bin"],
"koi8u": ["koi8u_general_ci", "koi8u_bin"],
"gb2312": ["gb2312_chinese_ci", "gb2312_bin"],
"greek": ["greek_general_ci", "greek_bin"],
"cp1250": ["cp1250_general_ci",
"cp1250_czech_cs",
"cp1250_croatian_ci",
"cp1250_bin",
"cp1250_polish_ci"],
"gbk": ["gbk_chinese_ci", "gbk_bin"],
"latin5": ["latin5_turkish_ci", "latin5_bin"],
"armscii8": ["armscii8_general_ci", "armscii8_bin"],
"utf8mb4": ["utf8_unicode_ci", "utf8mb4_unicode_ci"],
"utf8": ["utf8_general_ci",
"utf8_bin",
"utf8_unicode_ci",
"utf8_icelandic_ci",
"utf8_latvian_ci",
"utf8_romanian_ci",
"utf8_slovenian_ci",
"utf8_polish_ci",
"utf8_estonian_ci",
"utf8_spanish_ci",
"utf8_swedish_ci",
"utf8_turkish_ci",
"utf8_czech_ci",
"utf8_danish_ci",
"utf8_lithuanian_ci",
"utf8_slovak_ci",
"utf8_spanish2_ci",
"utf8_roman_ci",
"utf8_persian_ci",
"utf8_esperanto_ci",
"utf8_hungarian_ci"],
"ucs2": ["ucs2_general_ci",
"ucs2_bin",
"ucs2_unicode_ci",
"ucs2_icelandic_ci",
"ucs2_latvian_ci",
"ucs2_romanian_ci",
"ucs2_slovenian_ci",
"ucs2_polish_ci",
"ucs2_estonian_ci",
"ucs2_spanish_ci",
"ucs2_swedish_ci",
"ucs2_turkish_ci",
"ucs2_czech_ci",
"ucs2_danish_ci",
"ucs2_lithuanian_ci",
"ucs2_slovak_ci",
"ucs2_spanish2_ci",
"ucs2_roman_ci",
"ucs2_persian_ci",
"ucs2_esperanto_ci",
"ucs2_hungarian_ci"],
"cp866": ["cp866_general_ci", "cp866_bin"],
"keybcs2": ["keybcs2_general_ci", "keybcs2_bin"],
"macce": ["macce_general_ci", "macce_bin"],
"macroman": ["macroman_general_ci", "macroman_bin"],
"cp852": ["cp852_general_ci", "cp852_bin"],
"latin7": ["latin7_general_ci",
"latin7_estonian_cs",
"latin7_general_cs",
"latin7_bin"],
"cp1251": ["cp1251_general_ci",
"cp1251_bulgarian_ci",
"cp1251_ukrainian_ci",
"cp1251_bin",
"cp1251_general_cs"],
"cp1256": ["cp1256_general_ci", "cp1256_bin"],
"cp1257": ["cp1257_general_ci",
"cp1257_lithuanian_ci",
"cp1257_bin"],
"binary": ["binary"],
"geostd8": ["geostd8_general_ci", "geostd8_bin"],
"cp932": ["cp932_japanese_ci", "cp932_bin"],
"eucjpms": ["eucjpms_japanese_ci", "eucjpms_bin"]}
collation = {"big5_chinese_ci": "big5",
"big5_bin": "big5",
"dec8_swedish_ci": "dec8",
"dec8_bin": "dec8",
"cp850_general_ci": "cp850",
"cp850_bin": "cp850",
"hp8_english_ci": "hp8",
"hp8_bin": "hp8",
"koi8r_general_ci": "koi8r",
"koi8r_bin": "koi8r",
"latin1_german1_ci": "latin1",
"latin1_swedish_ci": "latin1",
"latin1_danish_ci": "latin1",
"latin1_german2_ci": "latin1",
"latin1_bin": "latin1",
"latin1_general_ci": "latin1",
"latin1_general_cs": "latin1",
"latin1_spanish_ci": "latin1",
"latin2_czech_cs": "latin2",
"latin2_general_ci": "latin2",
"latin2_hungarian_ci": "latin2",
"latin2_croatian_ci": "latin2",
"latin2_bin": "latin2",
"swe7_swedish_ci": "swe7",
"swe7_bin": "swe7",
"ascii_general_ci": "ascii",
"ascii_bin": "ascii",
"ujis_japanese_ci": "ujis",
"ujis_bin": "ujis",
"sjis_japanese_ci": "sjis",
"sjis_bin": "sjis",
"hebrew_general_ci": "hebrew",
"hebrew_bin": "hebrew",
"tis620_thai_ci": "tis620",
"tis620_bin": "tis620",
"euckr_korean_ci": "euckr",
"euckr_bin": "euckr",
"koi8u_general_ci": "koi8u",
"koi8u_bin": "koi8u",
"gb2312_chinese_ci": "gb2312",
"gb2312_bin": "gb2312",
"greek_general_ci": "greek",
"greek_bin": "greek",
"cp1250_general_ci": "cp1250",
"cp1250_czech_cs": "cp1250",
"cp1250_croatian_ci": "cp1250",
"cp1250_bin": "cp1250",
"cp1250_polish_ci": "cp1250",
"gbk_chinese_ci": "gbk",
"gbk_bin": "gbk",
"latin5_turkish_ci": "latin5",
"latin5_bin": "latin5",
"armscii8_general_ci": "armscii8",
"armscii8_bin": "armscii8",
"utf8mb4_unicode_ci": "utf8mb4",
"utf8_general_ci": "utf8",
"utf8_bin": "utf8",
"utf8_unicode_ci": "utf8",
"utf8_icelandic_ci": "utf8",
"utf8_latvian_ci": "utf8",
"utf8_romanian_ci": "utf8",
"utf8_slovenian_ci": "utf8",
"utf8_polish_ci": "utf8",
"utf8_estonian_ci": "utf8",
"utf8_spanish_ci": "utf8",
"utf8_swedish_ci": "utf8",
"utf8_turkish_ci": "utf8",
"utf8_czech_ci": "utf8",
"utf8_danish_ci": "utf8",
"utf8_lithuanian_ci": "utf8",
"utf8_slovak_ci": "utf8",
"utf8_spanish2_ci": "utf8",
"utf8_roman_ci": "utf8",
"utf8_persian_ci": "utf8",
"utf8_esperanto_ci": "utf8",
"utf8_hungarian_ci": "utf8",
"ucs2_general_ci": "ucs2",
"ucs2_bin": "ucs2",
"ucs2_unicode_ci": "ucs2",
"ucs2_icelandic_ci": "ucs2",
"ucs2_latvian_ci": "ucs2",
"ucs2_romanian_ci": "ucs2",
"ucs2_slovenian_ci": "ucs2",
"ucs2_polish_ci": "ucs2",
"ucs2_estonian_ci": "ucs2",
"ucs2_spanish_ci": "ucs2",
"ucs2_swedish_ci": "ucs2",
"ucs2_turkish_ci": "ucs2",
"ucs2_czech_ci": "ucs2",
"ucs2_danish_ci": "ucs2",
"ucs2_lithuanian_ci": "ucs2",
"ucs2_slovak_ci": "ucs2",
"ucs2_spanish2_ci": "ucs2",
"ucs2_roman_ci": "ucs2",
"ucs2_persian_ci": "ucs2",
"ucs2_esperanto_ci": "ucs2",
"ucs2_hungarian_ci": "ucs2",
"cp866_general_ci": "cp866",
"cp866_bin": "cp866",
"keybcs2_general_ci": "keybcs2",
"keybcs2_bin": "keybcs2",
"macce_general_ci": "macce",
"macce_bin": "macce",
"macroman_general_ci": "macroman",
"macroman_bin": "macroman",
"cp852_general_ci": "cp852",
"cp852_bin": "cp852",
"latin7_estonian_cs": "latin7",
"latin7_general_ci": "latin7",
"latin7_general_cs": "latin7",
"latin7_bin": "latin7",
"cp1251_bulgarian_ci": "cp1251",
"cp1251_ukrainian_ci": "cp1251",
"cp1251_bin": "cp1251",
"cp1251_general_ci": "cp1251",
"cp1251_general_cs": "cp1251",
"cp1256_general_ci": "cp1256",
"cp1256_bin": "cp1256",
"cp1257_lithuanian_ci": "cp1257",
"cp1257_bin": "cp1257",
"cp1257_general_ci": "cp1257",
"binary": "binary",
"geostd8_general_ci": "geostd8",
"geostd8_bin": "geostd8",
"cp932_japanese_ci": "cp932",
"cp932_bin": "cp932",
"eucjpms_japanese_ci": "eucjpms",
"eucjpms_bin": "eucjpms"}
def __init__(self):
self._name = None
self._collate = None
self._character_set = None
self._ignore_dbs = cfg.get_ignored_dbs()
@property
def name(self):
return self._name
def _is_valid(self, value):
return value.lower() not in self._ignore_dbs
@name.setter
def name(self, value):
self._name = value
@property
def collate(self):
"""Get the appropriate collate value."""
if not self._collate and not self._character_set:
return self.__collation__
elif not self._collate:
return self.charset[self._character_set][0]
else:
return self._collate
@collate.setter
def collate(self, value):
"""Validate the collation and set it."""
if not value:
pass
elif self._character_set:
if value not in self.charset[self._character_set]:
msg = (_("%(val)s not a valid collation for charset %(char)s.")
% {'val': value, 'char': self._character_set})
raise ValueError(msg)
self._collate = value
else:
if value not in self.collation:
raise ValueError(_("'%s' not a valid collation.") % value)
self._collate = value
self._character_set = self.collation[value]
@property
def character_set(self):
"""Get the appropriate character set value."""
if not self._character_set:
return self.__charset__
else:
return self._character_set
@character_set.setter
def character_set(self, value):
"""Validate the character set and set it."""
if not value:
pass
elif value not in self.charset:
raise ValueError(_("'%s' not a valid character set.") % value)
else:
self._character_set = value
class ValidatedMySQLDatabase(MySQLDatabase):
@MySQLDatabase.name.setter
def name(self, value):
if any([not value,
not self._is_valid(value),
not self.dbname.match(value),
("%r" % value).find("\\") != -1]):
raise ValueError(_("'%s' is not a valid database name.") % value)
elif len(value) > 64:
msg = _("Database name '%s' is too long. Max length = 64.")
raise ValueError(msg % value)
else:
self._name = value
class DatastoreUser(Base):
"""Represents a datastore user."""
_HOSTNAME_WILDCARD = '%'
def __init__(self):
self._name = None
self._password = None
self._host = None
self._databases = []
@classmethod
def deserialize_user(cls, value):
if not cls._validate_dict(value):
raise ValueError(_("Bad dictionary. Keys: %(keys)s. "
"Required: %(reqs)s")
% ({'keys': value.keys(),
'reqs': cls._dict_requirements()}))
user = cls(deserializing=True)
user.deserialize(value)
return user
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._validate_user_name(value)
self._name = value
@property
def password(self):
return self._password
@password.setter
def password(self, value):
if self._is_valid_password(value):
self._password = value
else:
raise ValueError(_("'%s' is not a valid password.") % value)
@property
def databases(self):
return self._databases
@databases.setter
def databases(self, value):
mydb = self._build_database_schema(value)
self._databases.append(mydb.serialize())
@property
def host(self):
if self._host is None:
return self._HOSTNAME_WILDCARD
return self._host
@host.setter
def host(self, value):
if self._is_valid_host_name(value):
self._host = value
else:
raise ValueError(_("'%s' is not a valid hostname.") % value)
@abc.abstractmethod
def _build_database_schema(self, name):
"""Build a schema for this user.
:type name: string
:type character_set: string
:type collate: string
"""
def _validate_user_name(self, value):
"""Perform validations on a given user name.
:param value: Validated user name.
:type value: string
:raises: ValueError On validation errors.
"""
if self._max_username_length and (len(value) >
self._max_username_length):
raise ValueError(_("User name '%(name)s' is too long. "
"Max length = %(max_length)d.")
% {'name': value,
'max_length': self._max_username_length})
elif not self._is_valid_name(value):
raise ValueError(_("'%s' is not a valid user name.") % value)
@abc.abstractproperty
def _max_username_length(self):
"""Return the maximum valid user name length if any.
:returns: Maximum user name length or None if unlimited.
"""
@abc.abstractmethod
def _is_valid_name(self, value):
"""Validate a given user name.
:param value: User name to be validated.
:type value: string
:returns: TRUE if valid, FALSE otherwise.
"""
@abc.abstractmethod
def _is_valid_host_name(self, value):
"""Validate a given host name.
:param value: Host name to be validated.
:type value: string
:returns: TRUE if valid, FALSE otherwise.
"""
@abc.abstractmethod
def _is_valid_password(self, value):
"""Validate a given password.
:param value: Password to be validated.
:type value: string
:returns: TRUE if valid, FALSE otherwise.
"""
@classmethod
@abc.abstractmethod
def _dict_requirements(cls):
"""Get the dictionary requirements for a user created via
deserialization.
:returns: List of required dictionary keys.
"""
class MongoDBUser(DatastoreUser):
"""Represents a MongoDB user and its associated properties.
MongoDB users are identified using their namd and database.
Trove stores this as <database>.<username>
"""
def __init__(self, name=None, password=None, deserializing=False):
super(MongoDBUser, self).__init__()
self._name = None
self._username = None
self._database = None
self._roles = []
# need only one of: deserializing, name, or (name and password)
if ((not (bool(deserializing) != bool(name))) or
(bool(deserializing) and bool(password))):
raise ValueError(_("Bad args. name: %(name)s, "
"password %(pass)s, "
"deserializing %(deser)s.")
% ({'name': bool(name),
'pass': bool(password),
'deser': bool(deserializing)}))
if not deserializing:
self.name = name
self.password = password
@property
def username(self):
return self._username
@username.setter
def username(self, value):
self._update_name(username=value)
@property
def database(self):
return MongoDBSchema.deserialize_schema(self._database)
@database.setter
def database(self, value):
self._update_name(database=value)
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._update_name(name=value)
def _update_name(self, name=None, username=None, database=None):
"""Keep the name, username, and database values in sync."""
if name:
(database, username) = self._parse_name(name)
if not (database and username):
missing = 'username' if self.database else 'database'
raise ValueError(_("MongoDB user's name missing %s.")
% missing)
else:
if username:
if not self.database:
raise ValueError(_('MongoDB user missing database.'))
database = self.database.name
else: # database
if not self.username:
raise ValueError(_('MongoDB user missing username.'))
username = self.username
name = '%s.%s' % (database, username)
self._name = name
self._username = username
self._database = self._build_database_schema(database).serialize()
@property
def roles(self):
return self._roles
@roles.setter
def roles(self, value):
if isinstance(value, list):
for role in value:
self._add_role(role)
else:
self._add_role(value)
def revoke_role(self, role):
if role in self.roles:
self._roles.remove(role)
def _init_roles(self):
if '_roles' not in self.__dict__:
self._roles = []
for db in self._databases:
self._roles.append({'db': db['_name'], 'role': 'readWrite'})
@classmethod
def deserialize_user(cls, value):
user = super(MongoDBUser, cls).deserialize_user(value)
user.name = user._name
user._init_roles()
return user
def _build_database_schema(self, name):
return MongoDBSchema(name)
@staticmethod
def _parse_name(value):
"""The name will be <database>.<username>, so split it."""
parts = value.split('.', 1)
if len(parts) != 2:
raise exception.BadRequest(_(
'MongoDB user name "%s" not in <database>.<username> format.'
) % value)
return parts[0], parts[1]
@property
def _max_username_length(self):
return None
def _is_valid_name(self, value):
return True
def _is_valid_host_name(self, value):
return True
def _is_valid_password(self, value):
return True
def _add_role(self, value):
if not self._is_valid_role(value):
raise ValueError(_('Role %s is invalid.') % value)
self._roles.append(value)
if value['role'] == 'readWrite':
self.databases = value['db']
def _is_valid_role(self, value):
if not isinstance(value, dict):
return False
if not {'db', 'role'} == set(value):
return False
return True
@classmethod
def _dict_requirements(cls):
return ['_name']
class CassandraUser(DatastoreUser):
"""Represents a Cassandra user and its associated properties."""
def __init__(self, name=None, password=None, deserializing=False):
super(CassandraUser, self).__init__()
if ((not (bool(deserializing) != bool(name))) or
(bool(deserializing) and bool(password))):
raise ValueError(_("Bad args. name: %(name)s, "
"password %(pass)s, "
"deserializing %(deser)s.")
% ({'name': bool(name),
'pass': bool(password),
'deser': bool(deserializing)}))
if not deserializing:
self.name = name
self.password = password
def _build_database_schema(self, name):
return CassandraSchema(name)
@property
def _max_username_length(self):
return 65535
def _is_valid_name(self, value):
return True
def _is_valid_host_name(self, value):
return True
def _is_valid_password(self, value):
return True
@classmethod
def _dict_requirements(cls):
return ['_name']
class CouchDBUser(DatastoreUser):
"""Represents a CouchDB user and its associated properties."""
def __init__(self):
self._name = None
self._host = None
self._password = None
self._databases = []
self._ignore_users = cfg.get_ignored_users()
def _is_valid(self, value):
return True
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not self._is_valid(value):
raise ValueError(_("'%s' is not a valid user name.") % value)
else:
self._name = value
@property
def password(self):
return self._password
@password.setter
def password(self, value):
if not self._is_valid(value):
raise ValueError(_("'%s' is not a valid password.") % value)
else:
self._password = value
@property
def databases(self):
return self._databases
@databases.setter
def databases(self, value):
mydb = ValidatedMySQLDatabase()
mydb.name = value
self._databases.append(mydb.serialize())
@property
def host(self):
if self._host is None:
return '%'
return self._host
@host.setter
def host(self, value):
if not self._is_valid_host_name(value):
raise ValueError(_("'%s' is not a valid hostname.") % value)
else:
self._host = value
class MySQLUser(Base):
"""Represents a MySQL User and its associated properties."""
not_supported_chars = re.compile("^\s|\s$|'|\"|;|`|,|/|\\\\")
def __init__(self):
self._name = None
self._host = None
self._password = None
self._databases = []
self._ignore_users = cfg.get_ignored_users()
def _is_valid(self, value):
if (not value or
self.not_supported_chars.search(value) or
("%r" % value).find("\\") != -1):
return False
else:
return True
def _is_valid_user_name(self, value):
if (self._is_valid(value) and
value.lower() not in self._ignore_users):
return True
return False
def _is_valid_host_name(self, value):
if value in [None, "%"]:
# % is MySQL shorthand for "everywhere". Always permitted.
# Null host defaults to % anyway.
return True
if CONF.hostname_require_valid_ip:
try:
# '%' works as a MySQL wildcard, but it is not a valid
# part of an IPAddress
netaddr.IPAddress(value.replace('%', '1'))
except (ValueError, netaddr.AddrFormatError):
return False
else:
return True
else:
# If it wasn't required, anything else goes.
return True
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not self._is_valid_user_name(value):
raise ValueError(_("'%s' is not a valid user name.") % value)
elif len(value) > 16:
raise ValueError(_("User name '%s' is too long. Max length = 16.")
% value)
else:
self._name = value
@property
def password(self):
return self._password
@password.setter
def password(self, value):
if not self._is_valid(value):
raise ValueError(_("'%s' is not a valid password.") % value)
else:
self._password = value
@property
def databases(self):
return self._databases
@databases.setter
def databases(self, value):
mydb = ValidatedMySQLDatabase()
mydb.name = value
self._databases.append(mydb.serialize())
@property
def host(self):
if self._host is None:
return '%'
return self._host
@host.setter
def host(self, value):
if not self._is_valid_host_name(value):
raise ValueError(_("'%s' is not a valid hostname.") % value)
else:
self._host = value
class PostgreSQLUser(DatastoreUser):
"""Represents a PostgreSQL user and its associated properties."""
def __init__(self, name=None, password=None, deserializing=False):
super(PostgreSQLUser, self).__init__()
if ((not (bool(deserializing) != bool(name))) or
(bool(deserializing) and bool(password))):
raise ValueError(_("Bad args. name: %(name)s, "
"password %(pass)s, "
"deserializing %(deser)s.")
% ({'name': bool(name),
'pass': bool(password),
'deser': bool(deserializing)}))
if not deserializing:
self.name = name
self.password = password
def _build_database_schema(self, name):
return PostgreSQLSchema(name)
@property
def _max_username_length(self):
return 63
def _is_valid_name(self, value):
return True
def _is_valid_host_name(self, value):
return True
def _is_valid_password(self, value):
return True
@classmethod
def _dict_requirements(cls):
return ['name']
class RootUser(MySQLUser):
"""Overrides _ignore_users from the MySQLUser class."""
def __init__(self):
self._ignore_users = []
class MySQLRootUser(RootUser):
"""Represents the MySQL root user."""
def __init__(self, password=None):
super(MySQLRootUser, self).__init__()
self._name = "root"
self._host = "%"
if password is None:
self._password = utils.generate_random_password()
else:
self._password = password
class CassandraRootUser(CassandraUser):
"""Represents the Cassandra default superuser."""
def __init__(self, password=None, *args, **kwargs):
if password is None:
password = utils.generate_random_password()
super(CassandraRootUser, self).__init__("cassandra", password=password,
*args, **kwargs)
class PostgreSQLRootUser(PostgreSQLUser):
"""Represents the PostgreSQL default superuser."""
def __init__(self, password=None):
password = password if not None else utils.generate_random_password()
super(PostgreSQLRootUser, self).__init__("postgres", password=password)