summaryrefslogtreecommitdiff
path: root/bonafide/src
diff options
context:
space:
mode:
Diffstat (limited to 'bonafide/src')
-rw-r--r--bonafide/src/leap/__init__.py6
-rw-r--r--bonafide/src/leap/bonafide/__init__.py4
-rw-r--r--bonafide/src/leap/bonafide/_http.py95
-rw-r--r--bonafide/src/leap/bonafide/_protocol.py174
-rw-r--r--bonafide/src/leap/bonafide/_srp.py147
-rw-r--r--bonafide/src/leap/bonafide/_version.py460
-rw-r--r--bonafide/src/leap/bonafide/bootstrap.py0
-rw-r--r--bonafide/src/leap/bonafide/config.py508
-rw-r--r--bonafide/src/leap/bonafide/cred_srp.py157
-rw-r--r--bonafide/src/leap/bonafide/provider.py186
-rw-r--r--bonafide/src/leap/bonafide/service.py120
-rw-r--r--bonafide/src/leap/bonafide/services/TODO1
-rw-r--r--bonafide/src/leap/bonafide/services/__init__.py0
-rw-r--r--bonafide/src/leap/bonafide/services/eip.py0
-rw-r--r--bonafide/src/leap/bonafide/services/mail.py0
-rw-r--r--bonafide/src/leap/bonafide/services/soledad.py0
-rw-r--r--bonafide/src/leap/bonafide/session.py219
-rw-r--r--bonafide/src/leap/bonafide/ssh_service.py2
-rw-r--r--bonafide/src/leap/bonafide/tests/__init__.py0
19 files changed, 0 insertions, 2079 deletions
diff --git a/bonafide/src/leap/__init__.py b/bonafide/src/leap/__init__.py
deleted file mode 100644
index f48ad105..00000000
--- a/bonafide/src/leap/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
-try:
- __import__('pkg_resources').declare_namespace(__name__)
-except ImportError:
- from pkgutil import extend_path
- __path__ = extend_path(__path__, __name__)
diff --git a/bonafide/src/leap/bonafide/__init__.py b/bonafide/src/leap/bonafide/__init__.py
deleted file mode 100644
index 74f4e668..00000000
--- a/bonafide/src/leap/bonafide/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-
-from ._version import get_versions
-__version__ = get_versions()['version']
-del get_versions
diff --git a/bonafide/src/leap/bonafide/_http.py b/bonafide/src/leap/bonafide/_http.py
deleted file mode 100644
index 8f05b421..00000000
--- a/bonafide/src/leap/bonafide/_http.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# -*- coding: utf-8 -*-
-# _http.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/>.
-
-"""
-twisted.web utils for bonafide.
-"""
-import base64
-import cookielib
-import urllib
-
-from twisted.internet import defer, protocol, reactor
-from twisted.internet.ssl import Certificate
-from twisted.python.filepath import FilePath
-from twisted.web.client import Agent, CookieAgent
-from twisted.web.client import BrowserLikePolicyForHTTPS
-from twisted.web.http_headers import Headers
-from twisted.web.iweb import IBodyProducer
-from zope.interface import implements
-
-
-def cookieAgentFactory(verify_path, connectTimeout=30):
- customPolicy = BrowserLikePolicyForHTTPS(
- Certificate.loadPEM(FilePath(verify_path).getContent()))
- agent = Agent(reactor, customPolicy, connectTimeout=connectTimeout)
- cookiejar = cookielib.CookieJar()
- return CookieAgent(agent, cookiejar)
-
-
-def httpRequest(agent, url, values={}, headers={}, method='POST', token=None):
- data = ''
- if values:
- data = urllib.urlencode(values)
- headers['Content-Type'] = ['application/x-www-form-urlencoded']
-
- if token:
- headers['Authorization'] = ['Token token="%s"' % (bytes(token))]
-
- def handle_response(response):
- # print "RESPONSE CODE", response.code
- if response.code == 204:
- d = defer.succeed('')
- else:
- class SimpleReceiver(protocol.Protocol):
- def __init__(s, d):
- s.buf = ''
- s.d = d
-
- def dataReceived(s, data):
- s.buf += data
-
- def connectionLost(s, reason):
- # TODO: test if reason is twisted.web.client.ResponseDone,
- # if not, do an errback
- s.d.callback(s.buf)
- d = defer.Deferred()
- response.deliverBody(SimpleReceiver(d))
- return d
-
- d = agent.request(method, url, Headers(headers),
- StringProducer(data) if data else None)
- d.addCallback(handle_response)
- return d
-
-
-class StringProducer(object):
-
- implements(IBodyProducer)
-
- def __init__(self, body):
- self.body = body
- self.length = len(body)
-
- def startProducing(self, consumer):
- consumer.write(self.body)
- return defer.succeed(None)
-
- def pauseProducing(self):
- pass
-
- def stopProducing(self):
- pass
diff --git a/bonafide/src/leap/bonafide/_protocol.py b/bonafide/src/leap/bonafide/_protocol.py
deleted file mode 100644
index 726185ea..00000000
--- a/bonafide/src/leap/bonafide/_protocol.py
+++ /dev/null
@@ -1,174 +0,0 @@
-# -*- coding: utf-8 -*-
-# _protocol.py
-# Copyright (C) 2014-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/>.
-"""
-Bonafide protocol.
-"""
-import os
-import resource
-from collections import defaultdict
-
-from leap.bonafide import config
-from leap.bonafide.provider import Api
-from leap.bonafide.session import Session, OK
-from leap.common.config import get_path_prefix
-
-from twisted.cred.credentials import UsernamePassword
-from twisted.internet.defer import fail
-from twisted.python import log
-
-
-# TODO [ ] enable-disable services
-# TODO [ ] read provider info
-
-COMMANDS = 'signup', 'authenticate', 'logout', 'stats'
-_preffix = get_path_prefix()
-
-
-class BonafideProtocol(object):
- """
- Expose the protocol that interacts with the Bonafide Service API.
- """
-
- _apis = defaultdict(None)
- _sessions = defaultdict(None)
-
- def _get_api(self, provider):
- # TODO should get deferred
- if provider.domain in self._apis:
- return self._apis[provider.domain]
-
- # TODO defer the autoconfig for the provider if needed...
- api = Api(provider.api_uri, provider.version)
- self._apis[provider.domain] = api
- return api
-
- def _get_session(self, provider, full_id, password=""):
- if full_id in self._sessions:
- return self._sessions[full_id]
-
- # TODO if password/username null, then pass AnonymousCreds
- # TODO use twisted.cred instead
- username, provider_id = config.get_username_and_provider(full_id)
- credentials = UsernamePassword(username, password)
- api = self._get_api(provider)
- provider_pem = _get_provider_ca_path(provider_id)
- session = Session(credentials, api, provider_pem)
- self._sessions[full_id] = session
- return session
-
- def _del_session_errback(self, failure, full_id):
- if full_id in self._sessions:
- del self._sessions[full_id]
- return failure
-
- # Service public methods
-
- def do_signup(self, full_id, password):
- log.msg('SIGNUP for %s' % full_id)
- _, provider_id = config.get_username_and_provider(full_id)
-
- provider = config.Provider(provider_id)
- d = provider.callWhenReady(
- self._do_signup, provider, full_id, password)
- return d
-
- def _do_signup(self, provider, full_id, password):
-
- # XXX check it's unauthenticated
- def return_user(result, _session):
- return_code, user = result
- if return_code == OK:
- return user
-
- username, _ = config.get_username_and_provider(full_id)
- # XXX get deferred?
- session = self._get_session(provider, full_id, password)
- d = session.signup(username, password)
- d.addCallback(return_user, session)
- d.addErrback(self._del_session_errback, full_id)
- return d
-
- def do_authenticate(self, full_id, password):
- _, provider_id = config.get_username_and_provider(full_id)
-
- provider = config.Provider(provider_id)
-
- def maybe_finish_provider_bootstrap(result, provider):
- session = self._get_session(provider, full_id, password)
- d = provider.download_services_config_with_auth(session)
- d.addCallback(lambda _: result)
- return d
-
- d = provider.callWhenReady(
- self._do_authenticate, provider, full_id, password)
- d.addCallback(maybe_finish_provider_bootstrap, provider)
- return d
-
- def _do_authenticate(self, provider, full_id, password):
-
- def return_token_and_uuid(result, _session):
- if result == OK:
- # TODO -- turn this into JSON response
- return str(_session.token), str(_session.uuid)
-
- log.msg('AUTH for %s' % full_id)
-
- # XXX get deferred?
- session = self._get_session(provider, full_id, password)
- d = session.authenticate()
- d.addCallback(return_token_and_uuid, session)
- d.addErrback(self._del_session_errback, full_id)
- return d
-
- def do_logout(self, full_id):
- # XXX use the AVATAR here
- log.msg('LOGOUT for %s' % full_id)
- if (full_id not in self._sessions or
- not self._sessions[full_id].is_authenticated):
- return fail(RuntimeError("There is no session for such user"))
- session = self._sessions[full_id]
-
- d = session.logout()
- d.addCallback(lambda _: self._sessions.pop(full_id))
- d.addCallback(lambda _: '%s logged out' % full_id)
- return d
-
- def do_get_smtp_cert(self, full_id):
- if (full_id not in self._sessions or
- not self._sessions[full_id].is_authenticated):
- return fail(RuntimeError("There is no session for such user"))
- d = self._sessions[full_id].get_smtp_cert()
- return d
-
- def do_get_vpn_cert(self):
- # FIXME to be implemented
- pass
-
- def do_update_user(self):
- # FIXME to be implemented
- pass
-
- def do_stats(self):
- log.msg('Calculating Bonafide Service STATS')
- mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
- return {'sessions': len(self._sessions),
- 'mem': '%s KB' % (mem / 1024)}
-
-
-def _get_provider_ca_path(provider_id):
- return os.path.join(
- _preffix, 'leap', 'providers', provider_id, 'keys', 'ca', 'cacert.pem')
diff --git a/bonafide/src/leap/bonafide/_srp.py b/bonafide/src/leap/bonafide/_srp.py
deleted file mode 100644
index 38f657b3..00000000
--- a/bonafide/src/leap/bonafide/_srp.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# -*- coding: utf-8 -*-
-# _srp.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/>.
-
-"""
-SRP Authentication.
-"""
-
-import binascii
-import json
-
-import srp
-
-
-class SRPAuthMechanism(object):
-
- """
- Implement a protocol-agnostic SRP Authentication mechanism.
- """
-
- def __init__(self, username, password):
- self.username = username
- self.srp_user = srp.User(username, password,
- srp.SHA256, srp.NG_1024)
- _, A = self.srp_user.start_authentication()
- self.A = A
- self.M = None
- self.M2 = None
-
- def get_handshake_params(self):
- return {'login': bytes(self.username),
- 'A': binascii.hexlify(self.A)}
-
- def process_handshake(self, handshake_response):
- challenge = json.loads(handshake_response)
- self._check_for_errors(challenge)
- salt = challenge.get('salt', None)
- B = challenge.get('B', None)
- unhex_salt, unhex_B = self._unhex_salt_B(salt, B)
- self.M = self.srp_user.process_challenge(unhex_salt, unhex_B)
-
- def get_authentication_params(self):
- # It looks A is not used server side
- return {'client_auth': binascii.hexlify(self.M),
- 'A': binascii.hexlify(self.A)}
-
- def process_authentication(self, authentication_response):
- auth = json.loads(authentication_response)
- self._check_for_errors(auth)
- uuid = auth.get('id', None)
- token = auth.get('token', None)
- self.M2 = auth.get('M2', None)
- self._check_auth_params(uuid, token, self.M2)
- return uuid, token
-
- def verify_authentication(self):
- unhex_M2 = _safe_unhexlify(self.M2)
- self.srp_user.verify_session(unhex_M2)
- assert self.srp_user.authenticated()
-
- def _check_for_errors(self, response):
- if 'errors' in response:
- msg = response['errors']['base']
- raise SRPAuthError(unicode(msg).encode('utf-8'))
-
- def _unhex_salt_B(self, salt, B):
- if salt is None:
- raise SRPAuthNoSalt()
- if B is None:
- raise SRPAuthNoB()
- try:
- unhex_salt = _safe_unhexlify(salt)
- unhex_B = _safe_unhexlify(B)
- except (TypeError, ValueError) as e:
- raise SRPAuthBadDataFromServer(str(e))
- return unhex_salt, unhex_B
-
- def _check_auth_params(self, uuid, token, M2):
- if not all((uuid, token, M2)):
- msg = '%s' % str((M2, uuid, token))
- raise SRPAuthBadDataFromServer(msg)
-
-
-class SRPSignupMechanism(object):
-
- """
- Implement a protocol-agnostic SRP Registration mechanism.
- """
-
- def get_signup_params(self, username, password):
- salt, verifier = srp.create_salted_verification_key(
- bytes(username), bytes(password),
- srp.SHA256, srp.NG_1024)
- user_data = {
- 'user[login]': username,
- 'user[password_salt]': binascii.hexlify(salt),
- 'user[password_verifier]': binascii.hexlify(verifier)}
- return user_data
-
- def process_signup(self, signup_response):
- signup = json.loads(signup_response)
- errors = signup.get('errors')
- if errors:
- msg = 'username ' + errors.get('login')[0]
- raise SRPRegistrationError(msg)
- else:
- username = signup.get('login')
- return username
-
-
-def _safe_unhexlify(val):
- return binascii.unhexlify(val) \
- if (len(val) % 2 == 0) else binascii.unhexlify('0' + val)
-
-
-class SRPAuthError(Exception):
- """
- Base exception for srp authentication errors
- """
-
-
-class SRPAuthNoSalt(SRPAuthError):
- message = 'The server didn\'t send the salt parameter'
-
-
-class SRPAuthNoB(SRPAuthError):
- message = 'The server didn\'t send the B parameter'
-
-
-class SRPAuthBadDataFromServer(SRPAuthError):
- pass
-
-class SRPRegistrationError(Exception):
- pass
diff --git a/bonafide/src/leap/bonafide/_version.py b/bonafide/src/leap/bonafide/_version.py
deleted file mode 100644
index 91fb65cc..00000000
--- a/bonafide/src/leap/bonafide/_version.py
+++ /dev/null
@@ -1,460 +0,0 @@
-
-# This file helps to compute a version number in source trees obtained from
-# git-archive tarball (such as those provided by githubs download-from-tag
-# feature). Distribution tarballs (built by setup.py sdist) and build
-# directories (produced by setup.py build) will contain a much shorter file
-# that just contains the computed version number.
-
-# This file is released into the public domain. Generated by
-# versioneer-0.15 (https://github.com/warner/python-versioneer)
-
-import errno
-import os
-import re
-import subprocess
-import sys
-
-
-def get_keywords():
- # these strings will be replaced by git during git-archive.
- # setup.py/versioneer.py will grep for the variable names, so they must
- # each be defined on a line of their own. _version.py will just call
- # get_keywords().
- git_refnames = "$Format:%d$"
- git_full = "$Format:%H$"
- keywords = {"refnames": git_refnames, "full": git_full}
- return keywords
-
-
-class VersioneerConfig:
- pass
-
-
-def get_config():
- # these strings are filled in when 'setup.py versioneer' creates
- # _version.py
- cfg = VersioneerConfig()
- cfg.VCS = "git"
- cfg.style = "pep440"
- cfg.tag_prefix = "None"
- cfg.parentdir_prefix = "None"
- cfg.versionfile_source = "src/leap/bonafide/_version.py"
- cfg.verbose = False
- return cfg
-
-
-class NotThisMethod(Exception):
- pass
-
-
-LONG_VERSION_PY = {}
-HANDLERS = {}
-
-
-def register_vcs_handler(vcs, method): # decorator
- def decorate(f):
- if vcs not in HANDLERS:
- HANDLERS[vcs] = {}
- HANDLERS[vcs][method] = f
- return f
- return decorate
-
-
-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
- assert isinstance(commands, list)
- p = None
- for c in commands:
- try:
- dispcmd = str([c] + args)
- # remember shell=False, so use git.cmd on windows, not just git
- p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
- stderr=(subprocess.PIPE if hide_stderr
- else None))
- break
- except EnvironmentError:
- e = sys.exc_info()[1]
- if e.errno == errno.ENOENT:
- continue
- if verbose:
- print("unable to run %s" % dispcmd)
- print(e)
- return None
- else:
- if verbose:
- print("unable to find command, tried %s" % (commands,))
- return None
- stdout = p.communicate()[0].strip()
- if sys.version_info[0] >= 3:
- stdout = stdout.decode()
- if p.returncode != 0:
- if verbose:
- print("unable to run %s (error)" % dispcmd)
- return None
- return stdout
-
-
-def versions_from_parentdir(parentdir_prefix, root, verbose):
- # Source tarballs conventionally unpack into a directory that includes
- # both the project name and a version string.
- dirname = os.path.basename(root)
- if not dirname.startswith(parentdir_prefix):
- if verbose:
- print("guessing rootdir is '%s', but '%s' doesn't start with "
- "prefix '%s'" % (root, dirname, parentdir_prefix))
- raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
- return {"version": dirname[len(parentdir_prefix):],
- "full-revisionid": None,
- "dirty": False, "error": None}
-
-
-@register_vcs_handler("git", "get_keywords")
-def git_get_keywords(versionfile_abs):
- # the code embedded in _version.py can just fetch the value of these
- # keywords. When used from setup.py, we don't want to import _version.py,
- # so we do it with a regexp instead. This function is not used from
- # _version.py.
- keywords = {}
- try:
- f = open(versionfile_abs, "r")
- for line in f.readlines():
- if line.strip().startswith("git_refnames ="):
- mo = re.search(r'=\s*"(.*)"', line)
- if mo:
- keywords["refnames"] = mo.group(1)
- if line.strip().startswith("git_full ="):
- mo = re.search(r'=\s*"(.*)"', line)
- if mo:
- keywords["full"] = mo.group(1)
- f.close()
- except EnvironmentError:
- pass
- return keywords
-
-
-@register_vcs_handler("git", "keywords")
-def git_versions_from_keywords(keywords, tag_prefix, verbose):
- if not keywords:
- raise NotThisMethod("no keywords at all, weird")
- refnames = keywords["refnames"].strip()
- if refnames.startswith("$Format"):
- if verbose:
- print("keywords are unexpanded, not using")
- raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
- refs = set([r.strip() for r in refnames.strip("()").split(",")])
- # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
- # just "foo-1.0". If we see a "tag: " prefix, prefer those.
- TAG = "tag: "
- tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
- if not tags:
- # Either we're using git < 1.8.3, or there really are no tags. We use
- # a heuristic: assume all version tags have a digit. The old git %d
- # expansion behaves like git log --decorate=short and strips out the
- # refs/heads/ and refs/tags/ prefixes that would let us distinguish
- # between branches and tags. By ignoring refnames without digits, we
- # filter out many common branch names like "release" and
- # "stabilization", as well as "HEAD" and "master".
- tags = set([r for r in refs if re.search(r'\d', r)])
- if verbose:
- print("discarding '%s', no digits" % ",".join(refs-tags))
- if verbose:
- print("likely tags: %s" % ",".join(sorted(tags)))
- for ref in sorted(tags):
- # sorting will prefer e.g. "2.0" over "2.0rc1"
- if ref.startswith(tag_prefix):
- r = ref[len(tag_prefix):]
- if verbose:
- print("picking %s" % r)
- return {"version": r,
- "full-revisionid": keywords["full"].strip(),
- "dirty": False, "error": None
- }
- # no suitable tags, so version is "0+unknown", but full hex is still there
- if verbose:
- print("no suitable tags, using unknown + full revision id")
- return {"version": "0+unknown",
- "full-revisionid": keywords["full"].strip(),
- "dirty": False, "error": "no suitable tags"}
-
-
-@register_vcs_handler("git", "pieces_from_vcs")
-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
- # this runs 'git' from the root of the source tree. This only gets called
- # if the git-archive 'subst' keywords were *not* expanded, and
- # _version.py hasn't already been rewritten with a short version string,
- # meaning we're inside a checked out source tree.
-
- if not os.path.exists(os.path.join(root, ".git")):
- if verbose:
- print("no .git in %s" % root)
- raise NotThisMethod("no .git directory")
-
- GITS = ["git"]
- if sys.platform == "win32":
- GITS = ["git.cmd", "git.exe"]
- # if there is a tag, this yields TAG-NUM-gHEX[-dirty]
- # if there are no tags, this yields HEX[-dirty] (no NUM)
- describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
- "--always", "--long"],
- cwd=root)
- # --long was added in git-1.5.5
- if describe_out is None:
- raise NotThisMethod("'git describe' failed")
- describe_out = describe_out.strip()
- full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
- if full_out is None:
- raise NotThisMethod("'git rev-parse' failed")
- full_out = full_out.strip()
-
- pieces = {}
- pieces["long"] = full_out
- pieces["short"] = full_out[:7] # maybe improved later
- pieces["error"] = None
-
- # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
- # TAG might have hyphens.
- git_describe = describe_out
-
- # look for -dirty suffix
- dirty = git_describe.endswith("-dirty")
- pieces["dirty"] = dirty
- if dirty:
- git_describe = git_describe[:git_describe.rindex("-dirty")]
-
- # now we have TAG-NUM-gHEX or HEX
-
- if "-" in git_describe:
- # TAG-NUM-gHEX
- mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
- if not mo:
- # unparseable. Maybe git-describe is misbehaving?
- pieces["error"] = ("unable to parse git-describe output: '%s'"
- % describe_out)
- return pieces
-
- # tag
- full_tag = mo.group(1)
- if not full_tag.startswith(tag_prefix):
- if verbose:
- fmt = "tag '%s' doesn't start with prefix '%s'"
- print(fmt % (full_tag, tag_prefix))
- pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
- % (full_tag, tag_prefix))
- return pieces
- pieces["closest-tag"] = full_tag[len(tag_prefix):]
-
- # distance: number of commits since tag
- pieces["distance"] = int(mo.group(2))
-
- # commit: short hex revision ID
- pieces["short"] = mo.group(3)
-
- else:
- # HEX: no tags
- pieces["closest-tag"] = None
- count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
- cwd=root)
- pieces["distance"] = int(count_out) # total number of commits
-
- return pieces
-
-
-def plus_or_dot(pieces):
- if "+" in pieces.get("closest-tag", ""):
- return "."
- return "+"
-
-
-def render_pep440(pieces):
- # now build up version string, with post-release "local version
- # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
- # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
-
- # exceptions:
- # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
-
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"] or pieces["dirty"]:
- rendered += plus_or_dot(pieces)
- rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
- if pieces["dirty"]:
- rendered += ".dirty"
- else:
- # exception #1
- rendered = "0+untagged.%d.g%s" % (pieces["distance"],
- pieces["short"])
- if pieces["dirty"]:
- rendered += ".dirty"
- return rendered
-
-
-def render_pep440_pre(pieces):
- # TAG[.post.devDISTANCE] . No -dirty
-
- # exceptions:
- # 1: no tags. 0.post.devDISTANCE
-
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"]:
- rendered += ".post.dev%d" % pieces["distance"]
- else:
- # exception #1
- rendered = "0.post.dev%d" % pieces["distance"]
- return rendered
-
-
-def render_pep440_post(pieces):
- # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that
- # .dev0 sorts backwards (a dirty tree will appear "older" than the
- # corresponding clean one), but you shouldn't be releasing software with
- # -dirty anyways.
-
- # exceptions:
- # 1: no tags. 0.postDISTANCE[.dev0]
-
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"] or pieces["dirty"]:
- rendered += ".post%d" % pieces["distance"]
- if pieces["dirty"]:
- rendered += ".dev0"
- rendered += plus_or_dot(pieces)
- rendered += "g%s" % pieces["short"]
- else:
- # exception #1
- rendered = "0.post%d" % pieces["distance"]
- if pieces["dirty"]:
- rendered += ".dev0"
- rendered += "+g%s" % pieces["short"]
- return rendered
-
-
-def render_pep440_old(pieces):
- # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty.
-
- # exceptions:
- # 1: no tags. 0.postDISTANCE[.dev0]
-
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"] or pieces["dirty"]:
- rendered += ".post%d" % pieces["distance"]
- if pieces["dirty"]:
- rendered += ".dev0"
- else:
- # exception #1
- rendered = "0.post%d" % pieces["distance"]
- if pieces["dirty"]:
- rendered += ".dev0"
- return rendered
-
-
-def render_git_describe(pieces):
- # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty
- # --always'
-
- # exceptions:
- # 1: no tags. HEX[-dirty] (note: no 'g' prefix)
-
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"]:
- rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
- else:
- # exception #1
- rendered = pieces["short"]
- if pieces["dirty"]:
- rendered += "-dirty"
- return rendered
-
-
-def render_git_describe_long(pieces):
- # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty
- # --always -long'. The distance/hash is unconditional.
-
- # exceptions:
- # 1: no tags. HEX[-dirty] (note: no 'g' prefix)
-
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
- else:
- # exception #1
- rendered = pieces["short"]
- if pieces["dirty"]:
- rendered += "-dirty"
- return rendered
-
-
-def render(pieces, style):
- if pieces["error"]:
- return {"version": "unknown",
- "full-revisionid": pieces.get("long"),
- "dirty": None,
- "error": pieces["error"]}
-
- if not style or style == "default":
- style = "pep440" # the default
-
- if style == "pep440":
- rendered = render_pep440(pieces)
- elif style == "pep440-pre":
- rendered = render_pep440_pre(pieces)
- elif style == "pep440-post":
- rendered = render_pep440_post(pieces)
- elif style == "pep440-old":
- rendered = render_pep440_old(pieces)
- elif style == "git-describe":
- rendered = render_git_describe(pieces)
- elif style == "git-describe-long":
- rendered = render_git_describe_long(pieces)
- else:
- raise ValueError("unknown style '%s'" % style)
-
- return {"version": rendered, "full-revisionid": pieces["long"],
- "dirty": pieces["dirty"], "error": None}
-
-
-def get_versions():
- # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
- # __file__, we can work backwards from there to the root. Some
- # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
- # case we can only use expanded keywords.
-
- cfg = get_config()
- verbose = cfg.verbose
-
- try:
- return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
- verbose)
- except NotThisMethod:
- pass
-
- try:
- root = os.path.realpath(__file__)
- # versionfile_source is the relative path from the top of the source
- # tree (where the .git directory might live) to this file. Invert
- # this to find the root from __file__.
- for i in cfg.versionfile_source.split('/'):
- root = os.path.dirname(root)
- except NameError:
- return {"version": "0+unknown", "full-revisionid": None,
- "dirty": None,
- "error": "unable to find root of source tree"}
-
- try:
- pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
- return render(pieces, cfg.style)
- except NotThisMethod:
- pass
-
- try:
- if cfg.parentdir_prefix:
- return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
- except NotThisMethod:
- pass
-
- return {"version": "0+unknown", "full-revisionid": None,
- "dirty": None,
- "error": "unable to compute version"}
diff --git a/bonafide/src/leap/bonafide/bootstrap.py b/bonafide/src/leap/bonafide/bootstrap.py
deleted file mode 100644
index e69de29b..00000000
--- a/bonafide/src/leap/bonafide/bootstrap.py
+++ /dev/null
diff --git a/bonafide/src/leap/bonafide/config.py b/bonafide/src/leap/bonafide/config.py
deleted file mode 100644
index ae66a0e8..00000000
--- a/bonafide/src/leap/bonafide/config.py
+++ /dev/null
@@ -1,508 +0,0 @@
-# -*- coding: utf-8 -*-
-# config.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/>.
-"""
-Configuration for a LEAP provider.
-"""
-import datetime
-import json
-import os
-import sys
-
-from collections import defaultdict
-from urlparse import urlparse
-
-from twisted.internet import defer, reactor
-from twisted.internet.ssl import ClientContextFactory
-from twisted.python import log
-from twisted.web.client import Agent, downloadPage
-
-from leap.bonafide._http import httpRequest
-from leap.bonafide.provider import Discovery
-
-from leap.common.check import leap_assert
-from leap.common.config import get_path_prefix as common_get_path_prefix
-from leap.common.files import mkdir_p
-# check_and_fix_urw_only, get_mtime
-
-
-APPNAME = "bonafide"
-ENDPOINT = "ipc:///tmp/%s.sock" % APPNAME
-
-
-def get_path_prefix(standalone=False):
- return common_get_path_prefix(standalone)
-
-
-_preffix = get_path_prefix()
-
-
-def get_provider_path(domain, config='provider.json'):
- """
- Returns relative path for provider configs.
-
- :param domain: the domain to which this providerconfig belongs to.
- :type domain: str
- :returns: the path
- :rtype: str
- """
- # TODO sanitize domain
- leap_assert(domain is not None, 'get_provider_path: We need a domain')
- return os.path.join('providers', domain, config)
-
-
-def get_ca_cert_path(domain):
- # TODO sanitize domain
- leap_assert(domain is not None, 'get_provider_path: We need a domain')
- return os.path.join('providers', domain, 'keys', 'ca', 'cacert.pem')
-
-
-def get_modification_ts(path):
- """
- Gets modification time of a file.
-
- :param path: the path to get ts from
- :type path: str
- :returns: modification time
- :rtype: datetime object
- """
- ts = os.path.getmtime(path)
- return datetime.datetime.fromtimestamp(ts)
-
-
-def update_modification_ts(path):
- """
- Sets modification time of a file to current time.
-
- :param path: the path to set ts to.
- :type path: str
- :returns: modification time
- :rtype: datetime object
- """
- os.utime(path, None)
- return get_modification_ts(path)
-
-
-def is_file(path):
- """
- Returns True if the path exists and is a file.
- """
- return os.path.isfile(path)
-
-
-def is_empty_file(path):
- """
- Returns True if the file at path is empty.
- """
- return os.stat(path).st_size is 0
-
-
-def make_address(user, provider):
- """
- Return a full identifier for an user, as a email-like
- identifier.
-
- :param user: the username
- :type user: basestring
- :param provider: the provider domain
- :type provider: basestring
- """
- return '%s@%s' % (user, provider)
-
-
-def get_username_and_provider(full_id):
- return full_id.split('@')
-
-
-class Provider(object):
- # TODO add validation
-
- SERVICES_MAP = {
- 'openvpn': ['eip'],
- 'mx': ['soledad', 'smtp']}
-
- first_bootstrap = defaultdict(None)
- ongoing_bootstrap = defaultdict(None)
- stuck_bootstrap = defaultdict(None)
-
- def __init__(self, domain, autoconf=True, basedir=None,
- check_certificate=True):
- if not basedir:
- basedir = os.path.join(_preffix, 'leap')
- self._basedir = os.path.expanduser(basedir)
- self._domain = domain
- self._disco = Discovery('https://%s' % domain)
- self._provider_config = None
-
- is_configured = self.is_configured()
- if not is_configured:
- check_certificate = False
-
- if check_certificate:
- self.contextFactory = None
- else:
- # XXX we should do this only for the FIRST provider download.
- # For the rest, we should pass the ca cert to the agent.
- # That means that RIGHT AFTER DOWNLOADING provider_info,
- # we should instantiate a new Agent...
- self.contextFactory = WebClientContextFactory()
- self._agent = Agent(reactor, self.contextFactory)
-
- self._load_provider_json()
-
- if not is_configured and autoconf:
- log.msg('provider %s not configured: downloading files...' %
- domain)
- self.bootstrap()
- else:
- log.msg('Provider already initialized')
- self.first_bootstrap[self._domain] = defer.succeed(
- 'already_initialized')
- self.ongoing_bootstrap[self._domain] = defer.succeed(
- 'already_initialized')
-
- @property
- def domain(self):
- return self._domain
-
- @property
- def api_uri(self):
- if not self._provider_config:
- return 'https://api.%s:4430' % self._domain
- return self._provider_config.api_uri
-
- @property
- def version(self):
- if not self._provider_config:
- return 1
- return int(self._provider_config.api_version)
-
- def is_configured(self):
- provider_json = self._get_provider_json_path()
- # XXX check if all the services are there
- if not is_file(provider_json):
- return False
- if not is_file(self._get_ca_cert_path()):
- return False
- if not self.has_config_for_all_services():
- return False
- return True
-
- def bootstrap(self):
- domain = self._domain
- log.msg("Bootstrapping provider %s" % domain)
- ongoing = self.ongoing_bootstrap.get(domain)
- if ongoing:
- log.msg('already bootstrapping this provider...')
- return
-
- self.first_bootstrap[self._domain] = defer.Deferred()
-
- def first_bootstrap_done(ignored):
- try:
- self.first_bootstrap[domain].callback('got config')
- except defer.AlreadyCalledError:
- pass
-
- d = self.maybe_download_provider_info()
- d.addCallback(self.maybe_download_ca_cert)
- d.addCallback(self.validate_ca_cert)
- d.addCallback(first_bootstrap_done)
- d.addCallback(self.maybe_download_services_config)
- self.ongoing_bootstrap[domain] = d
-
- def callWhenMainConfigReady(self, cb, *args, **kw):
- d = self.first_bootstrap[self._domain]
- d.addCallback(lambda _: cb(*args, **kw))
- return d
-
- def callWhenReady(self, cb, *args, **kw):
- d = self.ongoing_bootstrap[self._domain]
- d.addCallback(lambda _: cb(*args, **kw))
- return d
-
- def has_valid_certificate(self):
- pass
-
- def maybe_download_provider_info(self, replace=False):
- """
- Download the provider.json info from the main domain.
- This SHOULD only be used once with the DOMAIN url.
- """
- # TODO handle pre-seeded providers?
- # or let client handle that? We could move them to bonafide.
- provider_json = self._get_provider_json_path()
- if is_file(provider_json) and not replace:
- return defer.succeed('provider_info_already_exists')
-
- folders, f = os.path.split(provider_json)
- mkdir_p(folders)
-
- uri = self._disco.get_provider_info_uri()
- met = self._disco.get_provider_info_method()
-
- d = downloadPage(uri, provider_json, method=met)
- d.addCallback(lambda _: self._load_provider_json())
- d.addErrback(log.err)
- return d
-
- def update_provider_info(self):
- """
- Get more recent copy of provider.json from the api URL.
- """
- pass
-
- def maybe_download_ca_cert(self, ignored):
- """
- :rtype: deferred
- """
- path = self._get_ca_cert_path()
- if is_file(path):
- return defer.succeed('ca_cert_path_already_exists')
-
- uri = self._get_ca_cert_uri()
- mkdir_p(os.path.split(path)[0])
- d = downloadPage(uri, path)
- d.addErrback(log.err)
- return d
-
- def validate_ca_cert(self, ignored):
- # TODO Need to verify fingerprint against the one in provider.json
- expected = self._get_expected_ca_cert_fingerprint()
- print "EXPECTED FINGERPRINT:", expected
-
- def _get_expected_ca_cert_fingerprint(self):
- try:
- fgp = self._provider_config.ca_cert_fingerprint
- except AttributeError:
- fgp = None
- return fgp
-
- # Services config files
-
- def has_fetched_services_config(self):
- return os.path.isfile(self._get_configs_path())
-
- def maybe_download_services_config(self, ignored):
-
- # TODO --- currently, some providers (mail.bitmask.net) raise 401
- # UNAUTHENTICATED if we try to get the services
- # See: # https://leap.se/code/issues/7906
-
- def further_bootstrap_needs_auth(ignored):
- log.err('cannot download services config yet, need auth')
- pending_deferred = defer.Deferred()
- self.stuck_bootstrap[self._domain] = pending_deferred
- return pending_deferred
-
- uri, met, path = self._get_configs_download_params()
-
- d = downloadPage(uri, path, method=met)
- d.addCallback(lambda _: self._load_provider_json())
- d.addCallback(
- lambda _: self._get_config_for_all_services(session=None))
- d.addErrback(further_bootstrap_needs_auth)
- return d
-
- def download_services_config_with_auth(self, session):
-
- def verify_provider_configs(ignored):
- self._load_provider_configs()
- return True
-
- def workaround_for_config_fetch(failure):
- # FIXME --- configs.json raises 500, see #7914.
- # This is a workaround until that's fixed.
- log.err(failure)
- log.msg(
- "COULD NOT VERIFY CONFIGS.JSON, WORKAROUND: DIRECT DOWNLOAD")
-
- if 'mx' in self._provider_config.services:
- soledad_uri = '/1/config/soledad-service.json'
- smtp_uri = '/1/config/smtp-service.json'
- base = self._disco.netloc
-
- fetch = self._fetch_provider_configs_unauthenticated
- get_path = self._get_service_config_path
-
- d1 = fetch(
- 'https://' + str(base + soledad_uri), get_path('soledad'))
- d2 = fetch(
- 'https://' + str(base + smtp_uri), get_path('smtp'))
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _: finish_stuck_after_workaround())
- return d
-
- def finish_stuck_after_workaround():
- stuck = self.stuck_bootstrap.get(self._domain, None)
- if stuck:
- stuck.callback('continue!')
-
- def complete_bootstrapping(ignored):
- stuck = self.stuck_bootstrap.get(self._domain, None)
- if stuck:
- d = self._get_config_for_all_services(session)
- d.addCallback(lambda _: stuck.callback('continue!'))
- d.addErrback(log.err)
- return d
-
- if not self.has_fetched_services_config():
- self._load_provider_json()
- uri, met, path = self._get_configs_download_params()
- d = session.fetch_provider_configs(uri, path)
- d.addCallback(verify_provider_configs)
- d.addCallback(complete_bootstrapping)
- d.addErrback(workaround_for_config_fetch)
- return d
- else:
- d = defer.succeed('already downloaded')
- d.addCallback(complete_bootstrapping)
- return d
-
- def _get_configs_download_params(self):
- uri = self._disco.get_configs_uri()
- met = self._disco.get_configs_method()
- path = self._get_configs_path()
- return uri, met, path
-
- def offers_service(self, service):
- if service not in self.SERVICES_MAP.keys():
- raise RuntimeError('Unknown service: %s' % service)
- return service in self._provider_config.services
-
- def is_service_enabled(self, service):
- # TODO implement on some config file
- return True
-
- def has_config_for_service(self, service):
- has_file = os.path.isfile
- path = self._get_service_config_path
- smap = self.SERVICES_MAP
-
- result = all([has_file(path(subservice)) for
- subservice in smap[service]])
- return result
-
- def has_config_for_all_services(self):
- self._load_provider_json()
- if not self._provider_config:
- return False
- all_services = self._provider_config.services
- has_all = all(
- [self.has_config_for_service(service) for service in
- all_services])
- return has_all
-
- def _get_provider_json_path(self):
- domain = self._domain.encode(sys.getfilesystemencoding())
- provider_json_path = os.path.join(
- self._basedir, get_provider_path(domain, config='provider.json'))
- return provider_json_path
-
- def _get_configs_path(self):
- domain = self._domain.encode(sys.getfilesystemencoding())
- configs_path = os.path.join(
- self._basedir, get_provider_path(domain, config='configs.json'))
- return configs_path
-
- def _get_service_config_path(self, service):
- domain = self._domain.encode(sys.getfilesystemencoding())
- configs_path = os.path.join(
- self._basedir, get_provider_path(
- domain, config='%s-service.json' % service))
- return configs_path
-
- def _get_ca_cert_path(self):
- domain = self._domain.encode(sys.getfilesystemencoding())
- cert_path = os.path.join(self._basedir, get_ca_cert_path(domain))
- return cert_path
-
- def _get_ca_cert_uri(self):
- try:
- uri = self._provider_config.ca_cert_uri
- uri = str(uri)
- except Exception:
- uri = None
- return uri
-
- def _load_provider_json(self):
- path = self._get_provider_json_path()
- if not is_file(path):
- log.msg("Cannot LOAD provider config path %s" % path)
- return
-
- with open(path, 'r') as config:
- self._provider_config = Record(**json.load(config))
-
- api_uri = self._provider_config.api_uri
- if api_uri:
- parsed = urlparse(api_uri)
- self._disco.netloc = parsed.netloc
-
- def _get_config_for_all_services(self, session):
- services_dict = self._load_provider_configs()
- configs_path = self._get_configs_path()
- with open(configs_path) as jsonf:
- services_dict = Record(**json.load(jsonf)).services
- pending = []
- base = self._disco.get_base_uri()
- for service in self._provider_config.services:
- if service in self.SERVICES_MAP.keys():
- for subservice in self.SERVICES_MAP[service]:
- uri = base + str(services_dict[subservice])
- path = self._get_service_config_path(subservice)
- if session:
- d = session.fetch_provider_configs(uri, path)
- else:
- d = self._fetch_provider_configs_unauthenticated(
- uri, path)
- pending.append(d)
- return defer.gatherResults(pending)
-
- def _load_provider_configs(self):
- configs_path = self._get_configs_path()
- with open(configs_path) as jsonf:
- services_dict = Record(**json.load(jsonf)).services
- return services_dict
-
- def _fetch_provider_configs_unauthenticated(self, uri, path):
- log.msg('Downloading config for %s...' % uri)
- d = downloadPage(uri, path, method='GET')
- return d
-
- def _http_request(self, *args, **kw):
- # XXX pass if-modified-since header
- return httpRequest(self._agent, *args, **kw)
-
-
-class Record(object):
- def __init__(self, **kw):
- self.__dict__.update(kw)
-
-
-class WebClientContextFactory(ClientContextFactory):
- def getContext(self, hostname, port):
- return ClientContextFactory.getContext(self)
-
-
-if __name__ == '__main__':
-
- def print_done():
- print '>>> bootstrapping done!!!'
-
- provider = Provider('cdev.bitmask.net')
- provider.callWhenReady(print_done)
- reactor.run()
diff --git a/bonafide/src/leap/bonafide/cred_srp.py b/bonafide/src/leap/bonafide/cred_srp.py
deleted file mode 100644
index 9fcb97bc..00000000
--- a/bonafide/src/leap/bonafide/cred_srp.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# -*- coding: utf-8 -*-
-# srp_cred.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/>.
-
-"""
-Credential module for authenticating SRP requests against the LEAP platform.
-"""
-
-# ----------------- DOC ------------------------------------------------------
-# See examples of cred modules:
-# https://github.com/oubiwann-unsupported/txBrowserID/blob/master/browserid/checker.py
-# http://stackoverflow.com/questions/19171686/book-twisted-network-programming-essentials-example-9-1-does-not-work
-# ----------------- DOC ------------------------------------------------------
-
-from zope.interface import implements, implementer, Interface, Attribute
-
-from twisted.cred import portal, credentials, error as credError
-from twisted.cred.checkers import ICredentialsChecker
-from twisted.internet import defer, reactor
-
-from leap.bonafide.session import Session
-
-
-@implementer(ICredentialsChecker)
-class SRPCredentialsChecker(object):
-
- # TODO need to decide if the credentials that we pass here are per provider
- # or not.
- # I think it's better to have the credentials passed with the full user_id,
- # and here split user/provider.
- # XXX then we need to check if the provider is properly configured, to get
- # the right api info AND the needed certificates.
- # XXX might need to initialize credential checker with a ProviderAPI
-
- credentialInterfaces = (credentials.IUsernamePassword,)
-
- def requestAvatarId(self, credentials):
- # TODO If we are already authenticated, we should just
- # return the session object, somehow.
- # XXX If not authenticated (ie, no cached credentials?)
- # we pass credentials to srpauth.authenticate method
- # another srpauth class should interface with the blocking srpauth
- # library, and chain all the calls needed to do the handshake.
- # Therefore:
- # should keep reference to the srpauth instances somewhere
- # TODO If we want to return an anonymous user (useful for getting the
- # anon-vpn cert), we should return an empty tuple from here.
-
- return defer.maybeDeferred(_get_leap_session(credentials)).addCallback(
- self._check_srp_auth)
-
- def _check_srp_auth(session, username):
- if session.is_authenticated:
- # is ok! --- should add it to some global cache?
- return defer.succeed(username)
- else:
- return defer.fail(credError.UnauthorizedLogin(
- "Bad username/password combination"))
-
-
-def _get_leap_session(credentials):
- session = Session(credentials)
- d = session.authenticate()
- d.addCallback(lambda _: session)
- return d
-
-
-class ILeapUserAvatar(Interface):
-
- # TODO add attributes for username, uuid, token, session_id
-
- def logout():
- """
- Clean up per-login resource allocated to this avatar.
- """
-
-
-@implementer(ILeapUserAvatar)
-class LeapUserAvatar(object):
-
- # TODO initialize with: username, uuid, token, session_id
- # TODO initialize provider data (for api)
- # TODO how does this relate to LeapSession? maybe we should get one passed?
-
- def logout(self):
-
- # TODO reset the (global?) srpauth object.
- # https://leap.se/en/docs/design/bonafide#logout
- # DELETE API_BASE/logout(.json)
- pass
-
-
-class LeapAuthRealm(object):
- """
- The realm corresponds to an application domain and is in charge of avatars,
- which are network-accessible business logic objects.
- """
-
- # TODO should be initialized with provider API objects.
-
- implements(portal.IRealm)
-
- def requestAvatar(self, avatarId, mind, *interfaces):
-
- if ILeapUserAvatar in interfaces:
- # XXX how should we get the details for the requested avatar?
- avatar = LeapUserAvatar()
- return ILeapUserAvatar, avatar, avatar.logout
-
- raise NotImplementedError(
- "This realm only supports the ILeapUserAvatar interface.")
-
-
-if __name__ == '__main__':
-
- # from the browser-id implementation
- #import sys
- #def _done(res):
- #print res
- #reactor.stop()
- #assertion = sys.argv[1]
- #d = defer.Deferred()
- #reactor.callLater(0, d.callback, "http://localhost:8081")
- #d.addCallback(SRPCredentialsChecker)
- #d.addCallback(lambda c: c.requestAvatarId(assertion))
- #d.addBoth(_done)
- #reactor.run()
-
- # XXX move boilerplate to some bitmask-core template.
-
- leap_realm = LeapAuthRealm()
- # XXX should pass a provider mapping to realm too?
-
- leap_portal = portal.Portal(leap_realm)
-
- # XXX should we add an offline credentials checker, that's able
- # to unlock local soledad sqlcipher backend?
- # XXX should pass a provider mapping to credentials checker too?
- srp_checker = SRPCredentialsChecker()
- leap_portal.registerChecker(srp_checker)
-
- # XXX tie this to some sample server...
- reactor.listenTCP(8000, EchoFactory(leap_portal))
- reactor.run()
diff --git a/bonafide/src/leap/bonafide/provider.py b/bonafide/src/leap/bonafide/provider.py
deleted file mode 100644
index b44dae2e..00000000
--- a/bonafide/src/leap/bonafide/provider.py
+++ /dev/null
@@ -1,186 +0,0 @@
-# -*- 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.')
diff --git a/bonafide/src/leap/bonafide/service.py b/bonafide/src/leap/bonafide/service.py
deleted file mode 100644
index 14585eff..00000000
--- a/bonafide/src/leap/bonafide/service.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# -*- coding: utf-8 -*-
-# service.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/>.
-
-"""
-Bonafide Service.
-"""
-import os
-from collections import defaultdict
-
-from leap.common.config import get_path_prefix
-from leap.common.service_hooks import HookableService
-from leap.bonafide._protocol import BonafideProtocol
-
-from twisted.internet import defer
-from twisted.python import log
-
-
-_preffix = get_path_prefix()
-
-
-class BonafideService(HookableService):
-
- def __init__(self, basedir=None):
- if not basedir:
- basedir = os.path.join(_preffix, 'leap')
- self._basedir = os.path.expanduser(basedir)
- self._bonafide = BonafideProtocol()
- self.service_hooks = defaultdict(list)
-
- # XXX this is a quick hack to get a ref
- # to the latest authenticated user.
- self._active_user = None
-
- def startService(self):
- log.msg('Starting Bonafide Service')
- super(BonafideService, self).startService()
-
- # Commands
-
- def do_authenticate(self, username, password):
-
- def notify_passphrase_entry(username, password):
- data = dict(username=username, password=password)
- self.trigger_hook('on_passphrase_entry', **data)
-
- def notify_bonafide_auth(result):
- if not result:
- msg = "Authentication hook did not return anything"
- log.msg(msg)
- raise RuntimeError(msg)
-
- token, uuid = result
- data = dict(username=username, token=token, uuid=uuid,
- password=password)
- self.trigger_hook('on_bonafide_auth', **data)
-
- self._active_user = username
- return result
-
- # XXX I still have doubts from where it's best to trigger this.
- # We probably should wait for BOTH deferreds and
- # handle local and remote authentication success together
- # (and fail if either one fails). Going with fire-and-forget for
- # now, but needs needs improvement.
-
- notify_passphrase_entry(username, password)
-
- d = self._bonafide.do_authenticate(username, password)
- d.addCallback(notify_bonafide_auth)
- d.addCallback(lambda response: {
- 'srp_token': response[0], 'uuid': response[1]})
- return d
-
- def do_signup(self, username, password):
- d = self._bonafide.do_signup(username, password)
- d.addCallback(lambda response: {'signup': 'ok', 'user': response})
- return d
-
- def do_logout(self, username):
- if not username:
- username = self._active_user
-
- def reset_active(passthrough):
- self._active_user = None
- return passthrough
-
- d = self._bonafide.do_logout(username)
- d.addCallback(reset_active)
- d.addCallback(lambda response: {'logout': 'ok'})
- return d
-
- def do_get_smtp_cert(self, username=None):
- if not username:
- username = self._active_user
- if not username:
- return defer.fail(
- RuntimeError('No active user, cannot get SMTP cert.'))
-
- d = self._bonafide.do_get_smtp_cert(username)
- d.addCallback(lambda response: (username, response))
- return d
-
- def do_get_active_user(self):
- user = self._active_user or '<none>'
- info = {'user': user}
- return defer.succeed(info)
diff --git a/bonafide/src/leap/bonafide/services/TODO b/bonafide/src/leap/bonafide/services/TODO
deleted file mode 100644
index 97dc639a..00000000
--- a/bonafide/src/leap/bonafide/services/TODO
+++ /dev/null
@@ -1 +0,0 @@
-# XXX this should be discoverable through the config.
diff --git a/bonafide/src/leap/bonafide/services/__init__.py b/bonafide/src/leap/bonafide/services/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/bonafide/src/leap/bonafide/services/__init__.py
+++ /dev/null
diff --git a/bonafide/src/leap/bonafide/services/eip.py b/bonafide/src/leap/bonafide/services/eip.py
deleted file mode 100644
index e69de29b..00000000
--- a/bonafide/src/leap/bonafide/services/eip.py
+++ /dev/null
diff --git a/bonafide/src/leap/bonafide/services/mail.py b/bonafide/src/leap/bonafide/services/mail.py
deleted file mode 100644
index e69de29b..00000000
--- a/bonafide/src/leap/bonafide/services/mail.py
+++ /dev/null
diff --git a/bonafide/src/leap/bonafide/services/soledad.py b/bonafide/src/leap/bonafide/services/soledad.py
deleted file mode 100644
index e69de29b..00000000
--- a/bonafide/src/leap/bonafide/services/soledad.py
+++ /dev/null
diff --git a/bonafide/src/leap/bonafide/session.py b/bonafide/src/leap/bonafide/session.py
deleted file mode 100644
index 4180041a..00000000
--- a/bonafide/src/leap/bonafide/session.py
+++ /dev/null
@@ -1,219 +0,0 @@
-# -*- coding: utf-8 -*-
-# session.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 Session management.
-"""
-from twisted.internet import defer, reactor
-from twisted.python import log
-
-from leap.bonafide import _srp
-from leap.bonafide import provider
-from leap.bonafide._http import httpRequest, cookieAgentFactory
-
-OK = 'ok'
-
-
-def _auth_required(func):
- """
- Decorate a method so that it will not be called if the instance
- attribute `is_authenticated` does not evaluate to True.
- """
- def decorated(*args, **kwargs):
- instance = args[0]
- allowed = getattr(instance, 'is_authenticated')
- if not allowed:
- raise RuntimeError('This method requires authentication')
- return func(*args, **kwargs)
- return decorated
-
-
-class Session(object):
-
- def __init__(self, credentials, api, provider_cert):
- # TODO check if an anonymous credentials is passed.
- # TODO move provider_cert to api object.
- # On creation, it should be able to retrieve all the info it needs
- # (calling bootstrap).
- # TODO could get a "provider" object instead.
- # this provider can have an api attribute,
- # and a "autoconfig" attribute passed on initialization.
- # TODO get a file-descriptor for password if not in credentials
- # TODO merge self._request with config.Provider._http_request ?
-
- self.username = credentials.username
- self.password = credentials.password
- self._provider_cert = provider_cert
- self._api = api
- self._initialize_session()
-
- def _initialize_session(self):
- self._agent = cookieAgentFactory(self._provider_cert)
- username = self.username or ''
- password = self.password or ''
- self._srp_auth = _srp.SRPAuthMechanism(username, password)
- self._srp_signup = _srp.SRPSignupMechanism()
- self._token = None
- self._uuid = None
-
- # Session
-
- @property
- def token(self):
- return self._token
-
- @property
- def uuid(self):
- return self._uuid
-
- @property
- def is_authenticated(self):
- return self._srp_auth.srp_user.authenticated()
-
- @defer.inlineCallbacks
- def authenticate(self):
- uri = self._api.get_handshake_uri()
- met = self._api.get_handshake_method()
- log.msg("%s to %s" % (met, uri))
- params = self._srp_auth.get_handshake_params()
-
- handshake = yield self._request(self._agent, uri, values=params,
- method=met)
-
- self._srp_auth.process_handshake(handshake)
- uri = self._api.get_authenticate_uri(login=self.username)
- met = self._api.get_authenticate_method()
-
- log.msg("%s to %s" % (met, uri))
- params = self._srp_auth.get_authentication_params()
-
- auth = yield self._request(self._agent, uri, values=params,
- method=met)
-
- uuid, token = self._srp_auth.process_authentication(auth)
- self._srp_auth.verify_authentication()
-
- self._uuid = uuid
- self._token = token
- defer.returnValue(OK)
-
- @_auth_required
- @defer.inlineCallbacks
- def logout(self):
- uri = self._api.get_logout_uri()
- met = self._api.get_logout_method()
- auth = yield self._request(self._agent, uri, method=met)
- print 'AUTH', auth
- print 'resetting user/pass'
- self.username = None
- self.password = None
- self._initialize_session()
- defer.returnValue(OK)
-
- # User certificates
-
- def get_vpn_cert(self):
- # TODO pass it to the provider object so that it can save it in the
- # right path.
- uri = self._api.get_vpn_cert_uri()
- met = self._api.get_vpn_cert_method()
- return self._request(self._agent, uri, method=met)
-
- @_auth_required
- def get_smtp_cert(self):
- # TODO pass it to the provider object so that it can save it in the
- # right path.
- uri = self._api.get_smtp_cert_uri()
- met = self._api.get_smtp_cert_method()
- print met, "to", uri
- return self._request(self._agent, uri, method=met)
-
- def _request(self, *args, **kw):
- kw['token'] = self._token
- return httpRequest(*args, **kw)
-
- # User management
-
- @defer.inlineCallbacks
- def signup(self, username, password):
- # XXX should check that it_IS_NOT_authenticated
- provider.validate_username(username)
- uri = self._api.get_signup_uri()
- met = self._api.get_signup_method()
- params = self._srp_signup.get_signup_params(
- username, password)
-
- signup = yield self._request(self._agent, uri, values=params,
- method=met)
- registered_user = self._srp_signup.process_signup(signup)
- self.username = username
- self.password = password
- defer.returnValue((OK, registered_user))
-
- @_auth_required
- def update_user_record(self):
- # FIXME to be implemented
- pass
-
- # Authentication-protected configuration
-
- @defer.inlineCallbacks
- def fetch_provider_configs(self, uri, path):
- config = yield self._request(self._agent, uri)
- with open(path, 'w') as cf:
- cf.write(config)
- defer.returnValue('ok')
-
-
-if __name__ == "__main__":
- import os
- import sys
- from twisted.cred.credentials import UsernamePassword
-
- if len(sys.argv) != 4:
- print "Usage:", sys.argv[0], "provider", "username", "password"
- sys.exit()
- _provider, username, password = sys.argv[1], sys.argv[2], sys.argv[3]
- api = provider.Api('https://api.%s:4430' % _provider)
- credentials = UsernamePassword(username, password)
- cdev_pem = os.path.expanduser(
- '~/.config/leap/providers/%s/keys/ca/cacert.pem' % _provider)
- session = Session(credentials, api, cdev_pem)
-
- def print_result(result):
- print result
- return result
-
- def cbShutDown(ignored):
- reactor.stop()
-
- def auth_eb(failure):
- print "[ERROR!]", failure.getErrorMessage()
- log.err(failure)
-
- d = session.authenticate()
- d.addCallback(print_result)
- d.addErrback(auth_eb)
-
- d.addCallback(lambda _: session.get_smtp_cert())
- #d.addCallback(lambda _: session.get_vpn_cert())
- d.addCallback(print_result)
- d.addErrback(auth_eb)
-
- d.addCallback(lambda _: session.logout())
- d.addErrback(auth_eb)
- d.addBoth(cbShutDown)
- reactor.run()
diff --git a/bonafide/src/leap/bonafide/ssh_service.py b/bonafide/src/leap/bonafide/ssh_service.py
deleted file mode 100644
index 3777b10f..00000000
--- a/bonafide/src/leap/bonafide/ssh_service.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# TODO expose bonafide protocol through ssh, accessible through a hidden
-# service
diff --git a/bonafide/src/leap/bonafide/tests/__init__.py b/bonafide/src/leap/bonafide/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/bonafide/src/leap/bonafide/tests/__init__.py
+++ /dev/null