summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-03-06 15:36:37 -0300
committerTomás Touceda <chiiph@leap.se>2013-03-06 15:36:37 -0300
commitd4452e77afc63ec49684ef4b6cf6459456d293e1 (patch)
treeaf581adedfd04dd0d4aa3759eae99787cd4764a3
parenta11bcd4e3ee699f8050b21ef5d31b9f6031ddd7f (diff)
Add basic config handling
-rw-r--r--src/leap/config/__init__.py0
-rw-r--r--src/leap/config/baseconfig.py181
-rw-r--r--src/leap/config/pluggableconfig.py473
-rw-r--r--src/leap/config/prefixers.py86
-rw-r--r--src/leap/config/provider_spec.py75
-rw-r--r--src/leap/config/providerconfig.py144
6 files changed, 959 insertions, 0 deletions
diff --git a/src/leap/config/__init__.py b/src/leap/config/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/leap/config/__init__.py
diff --git a/src/leap/config/baseconfig.py b/src/leap/config/baseconfig.py
new file mode 100644
index 00000000..d553255e
--- /dev/null
+++ b/src/leap/config/baseconfig.py
@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+# baseconfig.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Implements the abstract base class for configuration
+"""
+
+import logging
+import functools
+import os
+import errno
+import copy
+
+from abc import ABCMeta, abstractmethod
+
+from leap.config.prefixers import get_platform_prefixer
+from leap.config.pluggableconfig import PluggableConfig
+
+logger = logging.getLogger(__name__)
+
+
+class BaseConfig:
+ """
+ Abstract base class for any JSON based configuration
+ """
+
+ __metaclass__ = ABCMeta
+
+ def __init__(self):
+ self._data = {}
+ self._config_checker = None
+
+ @abstractmethod
+ def _get_spec(self):
+ """
+ Returns the spec object for the specific configuration
+ """
+ return None
+
+ def _safe_get_value(self, key):
+ """
+ Tries to return a value only if the config has already been loaded
+
+ @rtype: depends on the config structure, dict, str, array, int
+ @return: returns the value for the specified key in the config
+ """
+ assert self._config_checker, "Load the config first"
+ return self._config_checker.config[key]
+
+ def get_path_prefix(self, standalone=False):
+ """
+ Returns the platform dependant path prefixer
+
+ @param standalone: if True it will return the prefix for a
+ standalone application. Otherwise, it will return the system
+ default for configuration storage.
+ @param type: bool
+ """
+ return get_platform_prefixer().get_path_prefix(standalone=standalone)
+
+ def loaded(self):
+ """
+ Returns True if the configuration has been already
+ loaded. False otherwise
+ """
+ return self._config_checker is not None
+
+ def save(self, path_list):
+ """
+ Saves the current configuration to disk
+
+ @param path: relative path to configuration. The absolute path
+ will be calculated depending on the platform.
+ @type path: list
+
+ @return: True if saved to disk correctly, False otherwise
+ """
+ config_path = os.path.join(self.get_path_prefix(), *(path_list[:-1]))
+ try:
+ os.makedirs(config_path)
+ except OSError as e:
+ if e.errno == errno.EEXIST and os.path.isdir(config_path):
+ pass
+ else:
+ raise
+
+ try:
+ self._config_checker.serialize(os.path.join(config_path,
+ path_list[-1]))
+ except Exception as e:
+ logger.warning("%s" % (e,))
+ raise
+ return True
+
+ def load(self, path="", data=None):
+ """
+ Loads the configuration from disk
+
+ @type path: str
+ @param path: relative path to configuration. The absolute path
+ will be calculated depending on the platform
+
+ @return: True if loaded to disk correctly, False otherwise
+ """
+
+ # TODO: retrieve standalone option from app-level config
+ config_path = os.path.join(self.get_path_prefix(),
+ path)
+
+ self._config_checker = PluggableConfig(format="json")
+ self._config_checker.options = copy.deepcopy(self._get_spec())
+
+ try:
+ if data is None:
+ self._config_checker.load(fromfile=config_path)
+ else:
+ self._config_checker.load(data)
+ except Exception as e:
+ logger.warning("Something went wrong while loading " +
+ "the config from %s\n%s" % (config_path, e))
+ self._config_checker = None
+ return False
+ return True
+
+
+class LocalizedKey(object):
+ """
+ Decorator used for keys that are localized in a configuration
+ """
+
+ def __init__(self, func, **kwargs):
+ self._func = func
+
+ def __call__(self, instance, lang="en"):
+ """
+ Tries to return the string for the specified language, otherwise
+ informs the problem and returns an empty string
+
+ @param lang: language code
+ @param type: str
+
+ @return: localized value from the possible values returned by
+ self._func
+ """
+ descriptions = self._func(instance)
+ description_lang = ""
+ if lang in descriptions.keys():
+ description_lang = descriptions[lang]
+ else:
+ logger.warning("Unknown language: %s" % (lang,))
+ return description_lang
+
+ def __get__(self, instance, instancetype):
+ """
+ Implement the descriptor protocol to make decorating instance
+ method possible.
+ """
+ # Return a partial function with the first argument is the instance
+ # of the class decorated.
+ return functools.partial(self.__call__, instance)
+
+if __name__ == "__main__":
+ try:
+ config = BaseConfig() # should throw TypeError for _get_spec
+ except Exception as e:
+ assert isinstance(e, TypeError), "Something went wrong"
+ print "Abstract BaseConfig class is working as expected"
diff --git a/src/leap/config/pluggableconfig.py b/src/leap/config/pluggableconfig.py
new file mode 100644
index 00000000..64aa05cc
--- /dev/null
+++ b/src/leap/config/pluggableconfig.py
@@ -0,0 +1,473 @@
+# -*- coding: utf-8 -*-
+# pluggableconfig.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+generic configuration handlers
+"""
+import copy
+import json
+import logging
+import os
+import time
+import urlparse
+
+import jsonschema
+
+#from leap.base.util.translations import LEAPTranslatable
+
+logger = logging.getLogger(__name__)
+
+
+__all__ = ['PluggableConfig',
+ 'adaptors',
+ 'types',
+ 'UnknownOptionException',
+ 'MissingValueException',
+ 'ConfigurationProviderException',
+ 'TypeCastException']
+
+# exceptions
+
+
+class UnknownOptionException(Exception):
+ """exception raised when a non-configuration
+ value is present in the configuration"""
+
+
+class MissingValueException(Exception):
+ """exception raised when a required value is missing"""
+
+
+class ConfigurationProviderException(Exception):
+ """exception raised when a configuration provider is missing, etc"""
+
+
+class TypeCastException(Exception):
+ """exception raised when a
+ configuration item cannot be coerced to a type"""
+
+
+class ConfigAdaptor(object):
+ """
+ abstract base class for config adaotors for
+ serialization/deserialization and custom validation
+ and type casting.
+ """
+ def read(self, filename):
+ raise NotImplementedError("abstract base class")
+
+ def write(self, config, filename):
+ with open(filename, 'w') as f:
+ self._write(f, config)
+
+ def _write(self, fp, config):
+ raise NotImplementedError("abstract base class")
+
+ def validate(self, config, schema):
+ raise NotImplementedError("abstract base class")
+
+
+adaptors = {}
+
+
+class JSONSchemaEncoder(json.JSONEncoder):
+ """
+ custom default encoder that
+ casts python objects to json objects for
+ the schema validation
+ """
+ def default(self, obj):
+ if obj is str:
+ return 'string'
+ if obj is unicode:
+ return 'string'
+ if obj is int:
+ return 'integer'
+ if obj is list:
+ return 'array'
+ if obj is dict:
+ return 'object'
+ if obj is bool:
+ return 'boolean'
+
+
+class JSONAdaptor(ConfigAdaptor):
+ indent = 2
+ extensions = ['json']
+
+ def read(self, _from):
+ if isinstance(_from, file):
+ _from_string = _from.read()
+ if isinstance(_from, str):
+ _from_string = _from
+ return json.loads(_from_string)
+
+ def _write(self, fp, config):
+ fp.write(json.dumps(config,
+ indent=self.indent,
+ sort_keys=True))
+
+ def validate(self, config, schema_obj):
+ schema_json = JSONSchemaEncoder().encode(schema_obj)
+ schema = json.loads(schema_json)
+ jsonschema.validate(config, schema)
+
+
+adaptors['json'] = JSONAdaptor()
+
+#
+# Adaptors
+#
+# Allow to apply a predefined set of types to the
+# specs, so it checks the validity of formats and cast it
+# to proper python types.
+
+# TODO:
+# - HTTPS uri
+
+
+class DateType(object):
+ fmt = '%Y-%m-%d'
+
+ def to_python(self, data):
+ return time.strptime(data, self.fmt)
+
+ def get_prep_value(self, data):
+ return time.strftime(self.fmt, data)
+
+
+class TranslatableType(object):
+ """
+ a type that casts to LEAPTranslatable objects.
+ Used for labels we get from providers and stuff.
+ """
+
+ def to_python(self, data):
+ # TODO: add translatable
+ return data#LEAPTranslatable(data)
+
+ # needed? we already have an extended dict...
+ #def get_prep_value(self, data):
+ #return dict(data)
+
+
+class URIType(object):
+
+ def to_python(self, data):
+ parsed = urlparse.urlparse(data)
+ if not parsed.scheme:
+ raise TypeCastException("uri %s has no schema" % data)
+ return parsed.geturl()
+
+ def get_prep_value(self, data):
+ return data
+
+
+class HTTPSURIType(object):
+
+ def to_python(self, data):
+ parsed = urlparse.urlparse(data)
+ if not parsed.scheme:
+ raise TypeCastException("uri %s has no schema" % data)
+ if parsed.scheme != "https":
+ raise TypeCastException(
+ "uri %s does not has "
+ "https schema" % data)
+ return parsed.geturl()
+
+ def get_prep_value(self, data):
+ return data
+
+
+types = {
+ 'date': DateType(),
+ 'uri': URIType(),
+ 'https-uri': HTTPSURIType(),
+ 'translatable': TranslatableType(),
+}
+
+
+class PluggableConfig(object):
+
+ options = {}
+
+ def __init__(self,
+ adaptors=adaptors,
+ types=types,
+ format=None):
+
+ self.config = {}
+ self.adaptors = adaptors
+ self.types = types
+ self._format = format
+ self.mtime = None
+ self.dirty = False
+
+ @property
+ def option_dict(self):
+ if hasattr(self, 'options') and isinstance(self.options, dict):
+ return self.options.get('properties', None)
+
+ def items(self):
+ """
+ act like an iterator
+ """
+ if isinstance(self.option_dict, dict):
+ return self.option_dict.items()
+ return self.options
+
+ def validate(self, config, format=None):
+ """
+ validate config
+ """
+ schema = self.options
+ if format is None:
+ format = self._format
+
+ if format:
+ adaptor = self.get_adaptor(self._format)
+ adaptor.validate(config, schema)
+ else:
+ # we really should make format mandatory...
+ logger.error('no format passed to validate')
+
+ # first round of validation is ok.
+ # now we proceed to cast types if any specified.
+ self.to_python(config)
+
+ def to_python(self, config):
+ """
+ cast types following first type and then format indications.
+ """
+ unseen_options = [i for i in config if i not in self.option_dict]
+ if unseen_options:
+ raise UnknownOptionException(
+ "Unknown options: %s" % ', '.join(unseen_options))
+
+ for key, value in config.items():
+ _type = self.option_dict[key].get('type')
+ if _type is None and 'default' in self.option_dict[key]:
+ _type = type(self.option_dict[key]['default'])
+ if _type is not None:
+ tocast = True
+ if not callable(_type) and isinstance(value, _type):
+ tocast = False
+ if tocast:
+ try:
+ config[key] = _type(value)
+ except BaseException, e:
+ raise TypeCastException(
+ "Could not coerce %s, %s, "
+ "to type %s: %s" % (key, value, _type.__name__, e))
+ _format = self.option_dict[key].get('format', None)
+ _ftype = self.types.get(_format, None)
+ if _ftype:
+ try:
+ config[key] = _ftype.to_python(value)
+ except BaseException, e:
+ raise TypeCastException(
+ "Could not coerce %s, %s, "
+ "to format %s: %s" % (key, value,
+ _ftype.__class__.__name__,
+ e))
+
+ return config
+
+ def prep_value(self, config):
+ """
+ the inverse of to_python method,
+ called just before serialization
+ """
+ for key, value in config.items():
+ _format = self.option_dict[key].get('format', None)
+ _ftype = self.types.get(_format, None)
+ if _ftype and hasattr(_ftype, 'get_prep_value'):
+ try:
+ config[key] = _ftype.get_prep_value(value)
+ except BaseException, e:
+ raise TypeCastException(
+ "Could not serialize %s, %s, "
+ "by format %s: %s" % (key, value,
+ _ftype.__class__.__name__,
+ e))
+ else:
+ config[key] = value
+ return config
+
+ # methods for adding configuration
+
+ def get_default_values(self):
+ """
+ return a config options from configuration defaults
+ """
+ defaults = {}
+ for key, value in self.items():
+ if 'default' in value:
+ defaults[key] = value['default']
+ return copy.deepcopy(defaults)
+
+ def get_adaptor(self, format):
+ """
+ get specified format adaptor or
+ guess for a given filename
+ """
+ adaptor = self.adaptors.get(format, None)
+ if adaptor:
+ return adaptor
+
+ # not registered in adaptors dict, let's try all
+ for adaptor in self.adaptors.values():
+ if format in adaptor.extensions:
+ return adaptor
+
+ def filename2format(self, filename):
+ extension = os.path.splitext(filename)[-1]
+ return extension.lstrip('.') or None
+
+ def serialize(self, filename, format=None, full=False):
+ if not format:
+ format = self._format
+ if not format:
+ format = self.filename2format(filename)
+ if not format:
+ raise Exception('Please specify a format')
+ # TODO: more specific exception type
+
+ adaptor = self.get_adaptor(format)
+ if not adaptor:
+ raise Exception("Adaptor not found for format: %s" % format)
+
+ config = copy.deepcopy(self.config)
+ serializable = self.prep_value(config)
+ adaptor.write(serializable, filename)
+
+ if self.mtime:
+ self.touch_mtime(filename)
+
+ def touch_mtime(self, filename):
+ mtime = self.mtime
+ os.utime(filename, (mtime, mtime))
+
+ def deserialize(self, string=None, fromfile=None, format=None):
+ """
+ load configuration from a file or string
+ """
+
+ def _try_deserialize():
+ if fromfile:
+ with open(fromfile, 'r') as f:
+ content = adaptor.read(f)
+ elif string:
+ content = adaptor.read(string)
+ return content
+
+ # XXX cleanup this!
+
+ if fromfile:
+ assert os.path.exists(fromfile)
+ if not format:
+ format = self.filename2format(fromfile)
+
+ if not format:
+ format = self._format
+ if format:
+ adaptor = self.get_adaptor(format)
+ else:
+ adaptor = None
+
+ if adaptor:
+ content = _try_deserialize()
+ return content
+
+ # no adaptor, let's try rest of adaptors
+
+ adaptors = self.adaptors[:]
+
+ if format:
+ adaptors.sort(
+ key=lambda x: int(
+ format in x.extensions),
+ reverse=True)
+
+ for adaptor in adaptors:
+ content = _try_deserialize()
+ return content
+
+ def set_dirty(self):
+ self.dirty = True
+
+ def is_dirty(self):
+ return self.dirty
+
+ def load(self, *args, **kwargs):
+ """
+ load from string or file
+ if no string of fromfile option is given,
+ it will attempt to load from defaults
+ defined in the schema.
+ """
+ string = args[0] if args else None
+ fromfile = kwargs.get("fromfile", None)
+ mtime = kwargs.pop("mtime", None)
+ self.mtime = mtime
+ content = None
+
+ # start with defaults, so we can
+ # have partial values applied.
+ content = self.get_default_values()
+ if string and isinstance(string, str):
+ content = self.deserialize(string)
+
+ if not string and fromfile is not None:
+ #import ipdb;ipdb.set_trace()
+ content = self.deserialize(fromfile=fromfile)
+
+ if not content:
+ logger.error('no content could be loaded')
+ # XXX raise!
+ return
+
+ # lazy evaluation until first level of nesting
+ # to allow lambdas with context-dependant info
+ # like os.path.expanduser
+ for k, v in content.iteritems():
+ if callable(v):
+ content[k] = v()
+
+ self.validate(content)
+ self.config = content
+ return True
+
+
+def testmain(): # pragma: no cover
+
+ from tests import test_validation as t
+ import pprint
+
+ config = PluggableConfig(_format="json")
+ properties = copy.deepcopy(t.sample_spec)
+
+ config.options = properties
+ config.load(fromfile='data.json')
+
+ print 'config'
+ pprint.pprint(config.config)
+
+ config.serialize('/tmp/testserial.json')
+
+if __name__ == "__main__":
+ testmain()
diff --git a/src/leap/config/prefixers.py b/src/leap/config/prefixers.py
new file mode 100644
index 00000000..a33e68aa
--- /dev/null
+++ b/src/leap/config/prefixers.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+# prefixers.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Platform dependant configuration path prefixers
+"""
+import os
+import platform
+
+from abc import ABCMeta, abstractmethod
+from xdg import BaseDirectory
+
+
+class Prefixer:
+ """
+ Abstract prefixer class
+ """
+
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def get_path_prefix(self, standalone=False):
+ """
+ Returns the platform dependant path prefixer
+
+ @param standalone: if True it will return the prefix for a
+ standalone application. Otherwise, it will return the system
+ default for configuration storage.
+ @param type: bool
+ """
+ return ""
+
+
+def get_platform_prefixer():
+ prefixer = globals()[platform.system() + "Prefixer"]
+ assert prefixer, "Unimplemented platform prefixer: %s" % \
+ (platform.system(),)
+ return prefixer()
+
+
+class LinuxPrefixer(Prefixer):
+ """
+ Config prefixer for the Linux platform
+ """
+
+ def get_path_prefix(self, standalone=False):
+ """
+ Returns the platform dependant path prefixer.
+ This method expects an env variable named LEAP_CLIENT_PATH if
+ standalone is used.
+
+ @param standalone: if True it will return the prefix for a
+ standalone application. Otherwise, it will return the system
+ default for configuration storage.
+ @param type: bool
+ """
+ config_dir = BaseDirectory.xdg_config_home
+ if not standalone:
+ return config_dir
+ return os.getenv("LEAP_CLIENT_PATH", config_dir)
+
+
+if __name__ == "__main__":
+ try:
+ abs_prefixer = Prefixer()
+ except Exception as e:
+ assert isinstance(e, TypeError), "Something went wrong"
+ print "Abstract Prefixer class is working as expected"
+
+ linux_prefixer = LinuxPrefixer()
+ print linux_prefixer.get_path_prefix(standalone=True)
+ print linux_prefixer.get_path_prefix()
diff --git a/src/leap/config/provider_spec.py b/src/leap/config/provider_spec.py
new file mode 100644
index 00000000..958f7846
--- /dev/null
+++ b/src/leap/config/provider_spec.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+# provider_spec.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+leap_provider_spec = {
+ 'description': 'provider definition',
+ 'type': 'object',
+ 'properties': {
+ 'version': {
+ 'type': unicode,
+ 'default': '0.1.0'
+ },
+ "default_language": {
+ 'type': unicode,
+ 'default': 'en'
+ },
+ 'domain': {
+ 'type': unicode, # XXX define uri type
+ 'default': 'testprovider.example.org'
+ },
+ 'name': {
+ 'type': dict,
+ 'format': 'translatable',
+ 'default': {u'en': u'Test Provider'}
+ },
+ 'description': {
+ #'type': LEAPTranslatable,
+ 'type': dict,
+ 'format': 'translatable',
+ 'default': {u'en': u'Test provider'}
+ },
+ 'enrollment_policy': {
+ 'type': unicode, # oneof ??
+ 'default': 'open'
+ },
+ 'services': {
+ 'type': list, # oneof ??
+ 'default': ['eip']
+ },
+ 'api_version': {
+ 'type': unicode,
+ 'default': '0.1.0' # version regexp
+ },
+ 'api_uri': {
+ 'type': unicode # uri
+ },
+ 'public_key': {
+ 'type': unicode # fingerprint
+ },
+ 'ca_cert_fingerprint': {
+ 'type': unicode,
+ },
+ 'ca_cert_uri': {
+ 'type': unicode,
+ 'format': 'https-uri'
+ },
+ 'languages': {
+ 'type': list,
+ 'default': ['en']
+ }
+ }
+}
diff --git a/src/leap/config/providerconfig.py b/src/leap/config/providerconfig.py
new file mode 100644
index 00000000..c3c2c298
--- /dev/null
+++ b/src/leap/config/providerconfig.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+# providerconfig.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Provider configuration
+"""
+import logging
+import os
+
+from leap.config.baseconfig import BaseConfig, LocalizedKey
+from leap.config.provider_spec import leap_provider_spec
+
+logger = logging.getLogger(__name__)
+
+
+class ProviderConfig(BaseConfig):
+ """
+ Provider configuration abstraction class
+ """
+
+ def __init__(self):
+ BaseConfig.__init__(self)
+
+ def _get_spec(self):
+ """
+ Returns the spec object for the specific configuration
+ """
+ return leap_provider_spec
+
+ def get_api_uri(self):
+ return self._safe_get_value("api_uri")
+
+ def get_api_version(self):
+ return self._safe_get_value("api_version")
+
+ def get_ca_cert_fingerprint(self):
+ return self._safe_get_value("ca_cert_fingerprint")
+
+ def get_ca_cert_uri(self):
+ return self._safe_get_value("ca_cert_uri")
+
+ def get_default_language(self):
+ return self._safe_get_value("default_language")
+
+ @LocalizedKey
+ def get_description(self):
+ return self._safe_get_value("description")
+
+ def get_domain(self):
+ return self._safe_get_value("domain")
+
+ def get_enrollment_policy(self):
+ return self._safe_get_value("enrollment_policy")
+
+ def get_languages(self):
+ return self._safe_get_value("languages")
+
+ @LocalizedKey
+ def get_name(self):
+ return self._safe_get_value("name")
+
+ def get_services(self):
+ return self._safe_get_value("services")
+
+ def get_ca_cert_path(self, about_to_download=False):
+ """
+ Returns the path to the certificate for the current provider
+
+ @param about_to_download: defines wether we want the path to
+ download the cert or not. This helps avoid checking if the
+ cert exists because we are about to write it.
+ @type about_to_download: bool
+ """
+
+ cert_path = os.path.join(self.get_path_prefix(),
+ "leap",
+ "providers",
+ self.get_domain(),
+ "keys",
+ "ca",
+ "cacert.pem")
+
+ if not about_to_download:
+ assert os.path.exists(cert_path), \
+ "You need to download the certificate first"
+ logger.debug("Going to verify SSL against %s" % (cert_path,))
+
+ return cert_path
+
+ def provides_eip(self):
+ """
+ Returns True if this particular provider has the EIP
+ service. False otherwise
+ """
+ return "openvpn" in self.get_services()
+
+
+if __name__ == "__main__":
+ logger = logging.getLogger(name='leap')
+ logger.setLevel(logging.DEBUG)
+ console = logging.StreamHandler()
+ console.setLevel(logging.DEBUG)
+ formatter = logging.Formatter(
+ '%(asctime)s '
+ '- %(name)s - %(levelname)s - %(message)s')
+ console.setFormatter(formatter)
+ logger.addHandler(console)
+
+ provider = ProviderConfig()
+
+ try:
+ provider.get_api_version()
+ except Exception as e:
+ assert isinstance(e, AssertionError), "Expected an assert"
+ print "Safe value getting is working"
+
+ # standalone minitest
+ #if provider.load("provider_bad.json"):
+ if provider.load("leap/providers/bitmask.net/provider.json"):
+ print provider.get_api_version()
+ print provider.get_ca_cert_fingerprint()
+ print provider.get_ca_cert_uri()
+ print provider.get_default_language()
+ print provider.get_description()
+ print provider.get_description(lang="asd")
+ print provider.get_domain()
+ print provider.get_enrollment_policy()
+ print provider.get_languages()
+ print provider.get_name()
+ print provider.get_services()