diff --git a/sylk/applications/conference/configuration.py b/sylk/applications/conference/configuration.py index 4dd9df7..897ad79 100644 --- a/sylk/applications/conference/configuration.py +++ b/sylk/applications/conference/configuration.py @@ -1,177 +1,125 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details. # __all__ = ['ConferenceConfig', 'get_room_config'] import re -import urllib -import urlparse from application.configuration import ConfigFile, ConfigSection, ConfigSetting -from sylk.configuration.datatypes import Path +from sylk.configuration.datatypes import Path, URL # Datatypes class AccessPolicyValue(str): allowed_values = ('allow,deny', 'deny,allow') def __new__(cls, value): value = re.sub('\s', '', value) if value not in cls.allowed_values: raise ValueError('invalid value, allowed values are: %s' % ' | '.join(cls.allowed_values)) return str.__new__(cls, value) class Domain(str): domain_re = re.compile(r"^[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)*$") def __new__(cls, value): value = str(value) if not cls.domain_re.match(value): raise ValueError("illegal domain: %s" % value) return str.__new__(cls, value) class SIPAddress(str): def __new__(cls, address): address = str(address) address = address.replace('@', '%40', address.count('@')-1) try: username, domain = address.split('@') Domain(domain) except ValueError: raise ValueError("illegal SIP address: %s, must be in user@domain format" % address) return str.__new__(cls, address) class PolicySettingValue(list): def __init__(self, value): if isinstance(value, (tuple, list)): l = [str(x) for x in value] elif isinstance(value, basestring): if value.lower() in ('none', ''): return list.__init__(self, []) elif value.lower() in ('any', 'all', '*'): return list.__init__(self, ['*']) else: l = re.split(r'\s*,\s*', value) else: raise TypeError("value must be a string, list or tuple") values = [] for item in l: if '@' in item: values.append(SIPAddress(item)) else: values.append(Domain(item)) return list.__init__(self, values) def match(self, uri): if self == ['*']: return True domain = uri.host uri = re.sub('^(sip:|sips:)', '', str(uri)) return uri in self or domain in self -class URL(object): - """A class describing an URL and providing access to its elements""" - - def __init__(self, url): - scheme, netloc, path, query, fragment = urlparse.urlsplit(url) - if netloc: - if "@" in netloc: - userinfo, hostport = netloc.split("@", 1) - if ":" in userinfo: - username, password = userinfo.split(":", 1) - else: - username, password = userinfo, None - else: - username = password = None - hostport = netloc - if ':' in hostport: - host, port = hostport.split(':', 1) - else: - host, port = hostport, None - else: - username = password = host = port = None - self.original_url = url - self.scheme = scheme - self.username = username - self.password = password - self.host = host - self.port = int(port) if port is not None else None - self.path = urllib.url2pathname(path) - self.query_items = dict(urlparse.parse_qsl(query)) - self.fragment = fragment - - def __str__(self): - return urlparse.urlunsplit((self.scheme, self.netloc, urllib.pathname2url(self.path), self.query, self.fragment)) - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, self.__str__()) - - url = property(__str__) - - @property - def query(self): - return urllib.urlencode(self.query_items) - - @property - def netloc(self): - authinfo = ':'.join(str(x) for x in (self.username, self.password) if x is not None) or None - hostport = ':'.join(str(x) for x in (self.host or '', self.port) if x is not None) - return '@'.join(x for x in (authinfo, hostport) if x is not None) - - class WebURL(str): def __new__(cls, url): url = URL(url) if url.scheme.lower() not in ('http', 'https'): raise ValueError('invalid web URL: %s' % url.original_url) return url.url # Configuration objects class ConferenceConfig(ConfigSection): __cfgfile__ = 'conference.ini' __section__ = 'Conference' db_uri = ConfigSetting(type=str, value='sqlite:///var/lib/sylkserver/conference.sqlite') history_table = ConfigSetting(type=str, value='message_history') replay_history = 20 access_policy = ConfigSetting(type=AccessPolicyValue, value=AccessPolicyValue('allow, deny')) allow = ConfigSetting(type=PolicySettingValue, value=PolicySettingValue('all')) deny = ConfigSetting(type=PolicySettingValue, value=PolicySettingValue('none')) file_transfer_dir = ConfigSetting(type=Path, value=Path('/var/spool/sylkserver')) screen_sharing_url = ConfigSetting(type=WebURL, value=Path('http://localhost/sylkserver/screensharing/')) screen_sharing_dir = ConfigSetting(type=Path, value=Path('/var/www/sylkserver/screensharing')) push_file_transfer = False class RoomConfig(ConfigSection): __cfgfile__ = 'conference.ini' access_policy = ConfigSetting(type=AccessPolicyValue, value=AccessPolicyValue('allow, deny')) allow = ConfigSetting(type=PolicySettingValue, value=PolicySettingValue('all')) deny = ConfigSetting(type=PolicySettingValue, value=PolicySettingValue('none')) class Configuration(object): def __init__(self, data): self.__dict__.update(data) def get_room_config(room): config_file = ConfigFile(RoomConfig.__cfgfile__) section = config_file.get_section(room) if section is not None: RoomConfig.read(section=room) config = Configuration(dict(RoomConfig)) RoomConfig.reset() else: # Apply general policy config = Configuration(dict((attr, getattr(ConferenceConfig, attr)) for attr in ('access_policy', 'allow', 'deny'))) return config diff --git a/sylk/configuration/datatypes.py b/sylk/configuration/datatypes.py index f9093ee..98f0294 100644 --- a/sylk/configuration/datatypes.py +++ b/sylk/configuration/datatypes.py @@ -1,163 +1,215 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details. # import os import re import socket import sys +import urllib +import urlparse from application.python.descriptor import classproperty from sipsimple.configuration.datatypes import AudioCodecList, Hostname, SIPTransport class AudioCodecs(list): def __new__(cls, value): if isinstance(value, (tuple, list)): return [str(x) for x in value if x in AudioCodecList.available_values] or None elif isinstance(value, basestring): if value.lower() in ('none', ''): return None return [x for x in re.split(r'\s*,\s*', value) if x in AudioCodecList.available_values] or None else: raise TypeError("value must be a string, list or tuple") class IPAddress(str): """An IP address in quad dotted number notation""" def __new__(cls, value): if value == '0.0.0.0': raise ValueError("%s is not allowed, please specify a specific IP address" % value) else: try: socket.inet_aton(value) except socket.error: raise ValueError("invalid IP address: %r" % value) except TypeError: raise TypeError("value must be a string") return str(value) class ResourcePath(object): def __init__(self, path): self.path = os.path.normpath(str(path)) def __getstate__(self): return unicode(self.path) def __setstate__(self, state): self.__init__(state) @property def normalized(self): path = os.path.expanduser(self.path) if os.path.isabs(path): return os.path.realpath(path) return os.path.realpath(os.path.join(self.resources_directory, path)) @classproperty def resources_directory(cls): binary_directory = os.path.dirname(os.path.realpath(sys.argv[0])) if os.path.basename(binary_directory) == 'bin': application_directory = os.path.dirname(binary_directory) resources_component = 'share/sylkserver' else: application_directory = binary_directory resources_component = 'resources' return os.path.realpath(os.path.join(application_directory, resources_component)) def __eq__(self, other): try: return self.path == other.path except AttributeError: return False def __hash__(self): return hash(self.path) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.path) def __unicode__(self): return unicode(self.path) class Port(int): def __new__(cls, value): try: value = int(value) except ValueError: return None if not (0 <= value <= 65535): raise ValueError("illegal port value: %s" % value) return value class PortRange(object): """A port range in the form start:end with start and end being even numbers in the [1024, 65536] range""" def __init__(self, value): self.start, self.end = [int(p) for p in value.split(':', 1)] allowed = xrange(1024, 65537, 2) if not (self.start in allowed and self.end in allowed and self.start < self.end): raise ValueError("bad range: %r: ports must be even numbers in the range [1024, 65536] with start < end" % value) class SIPProxyAddress(object): _description_re = re.compile(r"^(?P(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)*))(:(?P\d+))?(;transport=(?P.+))?$") def __new__(cls, description): if not description: return None if not cls._description_re.match(description): raise ValueError("illegal SIP proxy address: %s" % description) return super(SIPProxyAddress, cls).__new__(cls) def __init__(self, description): match = self.__class__._description_re.match(description) data = match.groupdict() host = data.get('host') port = data.get('port', None) or 5060 transport = data.get('transport', None) or 'udp' self.host = Hostname(host) self.port = Port(port) if self.port == 0: raise ValueError("illegal port value: 0") self.transport = SIPTransport(transport) def __getstate__(self): return unicode(self) def __setstate__(self, state): if not self.__class__._description_re.match(state): raise ValueError("illegal SIP proxy address: %s" % state) self.__init__(state) def __eq__(self, other): try: return (self.host, self.port, self.transport) == (other.host, other.port, other.transport) except AttributeError: return False def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.host, self.port, self.transport)) def __unicode__(self): return u'%s:%d;transport=%s' % (self.host, self.port, self.transport) class NillablePath(unicode): def __new__(cls, path): path = os.path.normpath(path) if not os.path.exists(path): return None return unicode.__new__(cls, path) @property def normalized(self): return os.path.expanduser(self) class Path(unicode): def __new__(cls, path): path = os.path.normpath(path) return unicode.__new__(cls, path) @property def normalized(self): return os.path.expanduser(self) +class URL(object): + """A class describing an URL and providing access to its elements""" + + def __init__(self, url): + scheme, netloc, path, query, fragment = urlparse.urlsplit(url) + if netloc: + if "@" in netloc: + userinfo, hostport = netloc.split("@", 1) + if ":" in userinfo: + username, password = userinfo.split(":", 1) + else: + username, password = userinfo, None + else: + username = password = None + hostport = netloc + if ':' in hostport: + host, port = hostport.split(':', 1) + else: + host, port = hostport, None + else: + username = password = host = port = None + self.original_url = url + self.scheme = scheme + self.username = username + self.password = password + self.host = host + self.port = int(port) if port is not None else None + self.path = urllib.url2pathname(path) + self.query_items = dict(urlparse.parse_qsl(query)) + self.fragment = fragment + + def __str__(self): + return urlparse.urlunsplit((self.scheme, self.netloc, urllib.pathname2url(self.path), self.query, self.fragment)) + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, self.__str__()) + + url = property(__str__) + + @property + def query(self): + return urllib.urlencode(self.query_items) + + @property + def netloc(self): + authinfo = ':'.join(str(x) for x in (self.username, self.password) if x is not None) or None + hostport = ':'.join(str(x) for x in (self.host or '', self.port) if x is not None) + return '@'.join(x for x in (authinfo, hostport) if x is not None) + +