1122 lines
37 KiB
Python
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)
|