summaryrefslogtreecommitdiff
path: root/src/leap/bitmask/bonafide/provider.py
diff options
context:
space:
mode:
authorKali Kaneko (leap communications) <kali@leap.se>2016-08-29 23:00:25 -0400
committerKali Kaneko (leap communications) <kali@leap.se>2016-08-29 23:00:25 -0400
commitaa882b7a64f2c821c8e241a5ff725f27c64fc15f (patch)
tree52554e55956bdd8a5c6596d9b355c36fec00f7cc /src/leap/bitmask/bonafide/provider.py
parent52abf28677a90780505228dbd6bcba54780766b8 (diff)
[pkg] move bonafide source to leap.bitmask.bonafide
Diffstat (limited to 'src/leap/bitmask/bonafide/provider.py')
-rw-r--r--src/leap/bitmask/bonafide/provider.py186
1 files changed, 186 insertions, 0 deletions
diff --git a/src/leap/bitmask/bonafide/provider.py b/src/leap/bitmask/bonafide/provider.py
new file mode 100644
index 00000000..b44dae2e
--- /dev/null
+++ b/src/leap/bitmask/bonafide/provider.py
@@ -0,0 +1,186 @@
+# -*- coding: utf-8 -*-
+# provier.py
+# Copyright (C) 2015 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 API.
+"""
+
+from copy import deepcopy
+import re
+from urlparse import urlparse
+
+
+"""
+Maximum API version number supported by bonafide
+"""
+MAX_API_VERSION = 1
+
+
+class _MetaActionDispatcher(type):
+
+ """
+ A metaclass that will create dispatcher methods dynamically for each
+ action made available by the LEAP provider API.
+
+ The new methods will be created according to the values contained in an
+ `_actions` dictionary, with the following format::
+
+ {'action_name': (uri_template, method)}
+
+ where `uri_template` is a string that will be formatted with an arbitrary
+ number of keyword arguments.
+
+ Any class that uses this one as its metaclass needs to implement two
+ private methods::
+
+ _get_uri(self, action_name, **extra_params)
+ _get_method(self, action_name)
+
+ Beware that currently they cannot be inherited from bases.
+ """
+
+ def __new__(meta, name, bases, dct):
+
+ def _generate_action_funs(dct):
+ _get_uri = dct['_get_uri']
+ _get_method = dct['_get_method']
+ newdct = deepcopy(dct)
+ actions = dct['_actions']
+
+ def create_uri_fun(action_name):
+ return lambda self, **kw: _get_uri(
+ self, action_name=action_name, **kw)
+
+ def create_met_fun(action_name):
+ return lambda self: _get_method(
+ self, action_name=action_name)
+
+ for action in actions:
+ uri, method = actions[action]
+ _action_uri = 'get_%s_uri' % action
+ _action_met = 'get_%s_method' % action
+ newdct[_action_uri] = create_uri_fun(action)
+ newdct[_action_met] = create_met_fun(action)
+ return newdct
+
+ newdct = _generate_action_funs(dct)
+ return super(_MetaActionDispatcher, meta).__new__(
+ meta, name, bases, newdct)
+
+
+class BaseProvider(object):
+
+ def __init__(self, netloc, version=1):
+ parsed = urlparse(netloc)
+ if parsed.scheme != 'https':
+ raise ValueError(
+ 'ProviderApi needs to be passed a url with https scheme')
+ self.netloc = parsed.netloc
+
+ self.version = version
+ if version > MAX_API_VERSION:
+ self.version = MAX_API_VERSION
+
+ def get_hostname(self):
+ return urlparse(self._get_base_url()).hostname
+
+ def _get_base_url(self):
+ return "https://{0}/{1}".format(self.netloc, self.version)
+
+
+class Api(BaseProvider):
+ """
+ An object that has all the information that a client needs to communicate
+ with the remote methods exposed by the web API of a LEAP provider.
+
+ The actions are described in https://leap.se/bonafide
+
+ By using the _MetaActionDispatcher as a metaclass, the _actions dict will
+ be translated dynamically into a set of instance methods that will allow
+ getting the uri and method for each action.
+
+ The keyword arguments specified in the format string will automatically
+ raise a KeyError if the needed keyword arguments are not passed to the
+ dynamically created methods.
+ """
+
+ # TODO when should the provider-api object be created?
+ # TODO pass a Provider object to constructor, with autoconf flag.
+ # TODO make the actions attribute depend on the api version
+ # TODO missing UPDATE USER RECORD
+
+ __metaclass__ = _MetaActionDispatcher
+ _actions = {
+ 'signup': ('users', 'POST'),
+ 'update_user': ('users/{uid}', 'PUT'),
+ 'handshake': ('sessions', 'POST'),
+ 'authenticate': ('sessions/{login}', 'PUT'),
+ 'logout': ('logout', 'DELETE'),
+ 'vpn_cert': ('cert', 'POST'),
+ 'smtp_cert': ('smtp_cert', 'POST'),
+ }
+
+ # Methods expected by the dispatcher metaclass
+
+ def _get_uri(self, action_name, **extra_params):
+ resource, _ = self._actions.get(action_name)
+ uri = '{0}/{1}'.format(
+ bytes(self._get_base_url()),
+ bytes(resource)).format(**extra_params)
+ return uri
+
+ def _get_method(self, action_name):
+ _, method = self._actions.get(action_name)
+ return method
+
+
+class Discovery(BaseProvider):
+ """
+ Discover basic information about a provider, including the provided
+ services.
+ """
+
+ __metaclass__ = _MetaActionDispatcher
+ _actions = {
+ 'provider_info': ('provider.json', 'GET'),
+ 'configs': ('1/configs.json', 'GET'),
+ }
+
+ def _get_base_url(self):
+ return "https://{0}".format(self.netloc)
+
+ def get_base_uri(self):
+ return self._get_base_url()
+
+ # Methods expected by the dispatcher metaclass
+
+ def _get_uri(self, action_name, **extra_params):
+ resource, _ = self._actions.get(action_name)
+ uri = '{0}/{1}'.format(
+ bytes(self._get_base_url()),
+ bytes(resource)).format(**extra_params)
+ return uri
+
+ def _get_method(self, action_name):
+ _, method = self._actions.get(action_name)
+ return method
+
+
+def validate_username(username):
+ accepted_characters = '^[a-z0-9\-\_\.]*$'
+ if not re.match(accepted_characters, username):
+ raise ValueError('Only lowercase letters, digits, . - and _ allowed.')