summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Pollan <meskio@sindominio.net>2016-09-06 19:27:31 +0200
committerKali Kaneko (leap communications) <kali@leap.se>2016-09-07 10:04:45 -0400
commit7a6e187e0a970ad6fb722fc9dfd3be784b254c06 (patch)
tree70092fe6f1635b7a22266c8e136510eb6934f162
parent1d51bc24ec9afcbb30b05905217bc7aafedca5f1 (diff)
[feat] Add manual provider registration
- Resolves: #8400
-rw-r--r--docs/next-changelog.rst1
-rw-r--r--src/leap/bitmask/bonafide/_protocol.py22
-rw-r--r--src/leap/bitmask/bonafide/config.py54
-rw-r--r--src/leap/bitmask/bonafide/errors.py23
-rw-r--r--src/leap/bitmask/bonafide/service.py23
-rw-r--r--src/leap/bitmask/cli/user.py10
-rw-r--r--src/leap/bitmask/core/dispatcher.py62
-rw-r--r--src/leap/bitmask/core/web/bitmask.js116
-rw-r--r--src/leap/bitmask/core/web/index.html72
9 files changed, 312 insertions, 71 deletions
diff --git a/docs/next-changelog.rst b/docs/next-changelog.rst
index 26e9cdc..a0f048b 100644
--- a/docs/next-changelog.rst
+++ b/docs/next-changelog.rst
@@ -12,6 +12,7 @@ Features
~~~~~~~~
- `#7965 <https://leap.se/code/issues/7965>`_: Add basic keymanagement to the cli.
- `#8265 <https://leap.se/code/issues/8265>`_: Add a REST API and bitmask.js library for it.
+- `#8400 <https://leap.se/code/issues/8265>`_: Add manual provider registration.
- Use mail_auth token in the core instead of imap/smtp tokens.
- `#1234 <https://leap.se/code/issues/1234>`_: Description of the new feature corresponding with issue #1234.
diff --git a/src/leap/bitmask/bonafide/_protocol.py b/src/leap/bitmask/bonafide/_protocol.py
index 3986540..ff357a1 100644
--- a/src/leap/bitmask/bonafide/_protocol.py
+++ b/src/leap/bitmask/bonafide/_protocol.py
@@ -21,10 +21,10 @@ import os
import resource
from collections import defaultdict
-from leap.common.config import get_path_prefix
from leap.bitmask.bonafide import config
from leap.bitmask.bonafide.provider import Api
from leap.bitmask.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
@@ -77,11 +77,11 @@ class BonafideProtocol(object):
# Service public methods
- def do_signup(self, full_id, password):
+ def do_signup(self, full_id, password, autoconf=False):
log.msg('SIGNUP for %s' % full_id)
_, provider_id = config.get_username_and_provider(full_id)
- provider = config.Provider(provider_id)
+ provider = config.Provider(provider_id, autoconf=autoconf)
d = provider.callWhenReady(
self._do_signup, provider, full_id, password)
return d
@@ -102,10 +102,10 @@ class BonafideProtocol(object):
d.addErrback(self._del_session_errback, full_id)
return d
- def do_authenticate(self, full_id, password):
+ def do_authenticate(self, full_id, password, autoconf=False):
_, provider_id = config.get_username_and_provider(full_id)
- provider = config.Provider(provider_id)
+ provider = config.Provider(provider_id, autoconf=autoconf)
def maybe_finish_provider_bootstrap(result, provider):
session = self._get_session(provider, full_id, password)
@@ -147,6 +147,18 @@ class BonafideProtocol(object):
d.addCallback(lambda _: '%s logged out' % full_id)
return d
+ def do_get_provider(self, provider_id, autoconf=False):
+ provider = config.Provider(provider_id, autoconf=autoconf)
+ return provider.callWhenMainConfigReady(provider.config)
+
+ def do_provider_delete(self, provider_id):
+ return config.delete_provider(provider_id)
+
+ def do_provider_list(self, seeded=False):
+ # TODO: seeded, we don't have pinned providers yet
+ providers = config.list_providers()
+ return [{"domain": p} for p in providers]
+
def do_get_smtp_cert(self, full_id):
if (full_id not in self._sessions or
not self._sessions[full_id].is_authenticated):
diff --git a/src/leap/bitmask/bonafide/config.py b/src/leap/bitmask/bonafide/config.py
index 0288676..8ef6c1b 100644
--- a/src/leap/bitmask/bonafide/config.py
+++ b/src/leap/bitmask/bonafide/config.py
@@ -20,6 +20,7 @@ Configuration for a LEAP provider.
import datetime
import json
import os
+import shutil
import sys
from collections import defaultdict
@@ -30,12 +31,14 @@ from twisted.internet.ssl import ClientContextFactory
from twisted.python import log
from twisted.web.client import Agent, downloadPage
+from leap.bitmask.bonafide._http import httpRequest
+from leap.bitmask.bonafide.provider import Discovery
+from leap.bitmask.bonafide.errors import NotConfiguredError
+
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
-from leap.bitmask.bonafide._http import httpRequest
-from leap.bitmask.bonafide.provider import Discovery
APPNAME = "bonafide"
@@ -126,6 +129,27 @@ def get_username_and_provider(full_id):
return full_id.split('@')
+def list_providers():
+ path = os.path.join(_preffix, "leap", "providers")
+ path = os.path.expanduser(path)
+ return os.listdir(path)
+
+
+def delete_provider(domain):
+ path = os.path.join(_preffix, "leap", "providers", domain)
+ path = os.path.expanduser(path)
+ if not os.path.exists(path):
+ raise NotConfiguredError("Provider %s is not configured, can't be "
+ "deleted" % (domain,))
+ shutil.rmtree(path)
+
+ # FIXME: this feels hacky, can we find a better way??
+ if domain in Provider.first_bootstrap:
+ del Provider.first_bootstrap[domain]
+ if domain in Provider.ongoing_bootstrap:
+ del Provider.ongoing_bootstrap[domain]
+
+
class Provider(object):
# TODO add validation
@@ -137,8 +161,9 @@ class Provider(object):
ongoing_bootstrap = defaultdict(None)
stuck_bootstrap = defaultdict(None)
- def __init__(self, domain, autoconf=True, basedir=None,
+ def __init__(self, domain, autoconf=False, basedir=None,
check_certificate=True):
+ # TODO: I need a way to know if it was already configured
if not basedir:
basedir = os.path.join(_preffix, 'leap')
self._basedir = os.path.expanduser(basedir)
@@ -162,10 +187,14 @@ class Provider(object):
self._load_provider_json()
- if not is_configured and autoconf:
- log.msg('provider %s not configured: downloading files...' %
- domain)
- self.bootstrap()
+ if not is_configured:
+ if autoconf:
+ log.msg('provider %s not configured: downloading files...' %
+ domain)
+ self.bootstrap()
+ else:
+ raise NotConfiguredError("Provider %s is not configured"
+ % (domain,))
else:
log.msg('Provider already initialized')
self.first_bootstrap[self._domain] = defer.succeed(
@@ -191,15 +220,17 @@ class Provider(object):
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 config(self):
+ if not self._provider_config:
+ self._load_provider_json()
+ return self._provider_config.dict()
+
def bootstrap(self):
domain = self._domain
log.msg("Bootstrapping provider %s" % domain)
@@ -491,6 +522,9 @@ class Record(object):
def __init__(self, **kw):
self.__dict__.update(kw)
+ def dict(self):
+ return self.__dict__
+
class WebClientContextFactory(ClientContextFactory):
def getContext(self, hostname, port):
diff --git a/src/leap/bitmask/bonafide/errors.py b/src/leap/bitmask/bonafide/errors.py
new file mode 100644
index 0000000..485c43e
--- /dev/null
+++ b/src/leap/bitmask/bonafide/errors.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# errors.py
+# Copyright (C) 2016 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/>.
+"""
+Exceptions for bonafide
+"""
+
+
+class NotConfiguredError(Exception):
+ pass
diff --git a/src/leap/bitmask/bonafide/service.py b/src/leap/bitmask/bonafide/service.py
index deead22..5e3b0f1 100644
--- a/src/leap/bitmask/bonafide/service.py
+++ b/src/leap/bitmask/bonafide/service.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# service.py
-# Copyright (C) 2015-2016 LEAP
+# 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
@@ -14,6 +14,7 @@
#
# 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.
"""
@@ -50,7 +51,7 @@ class BonafideService(HookableService):
# Commands
- def do_authenticate(self, username, password):
+ def do_authenticate(self, username, password, autoconf=False):
def notify_passphrase_entry(username, password):
data = dict(username=username, password=password)
@@ -78,14 +79,14 @@ class BonafideService(HookableService):
notify_passphrase_entry(username, password)
- d = self._bonafide.do_authenticate(username, password)
+ d = self._bonafide.do_authenticate(username, password, autoconf)
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)
+ def do_signup(self, username, password, autoconf=False):
+ d = self._bonafide.do_signup(username, password, autoconf)
d.addCallback(lambda response: {'signup': 'ok', 'user': response})
return d
@@ -102,6 +103,18 @@ class BonafideService(HookableService):
d.addCallback(lambda response: {'logout': 'ok'})
return d
+ def do_provider_create(self, domain):
+ return self._bonafide.do_get_provider(domain, autoconf=True)
+
+ def do_provider_read(self, domain):
+ return self._bonafide.do_get_provider(domain)
+
+ def do_provider_delete(self, domain):
+ return self._bonafide.do_provider_delete(domain)
+
+ def do_provider_list(self, seeded=False):
+ return self._bonafide.do_provider_list(seeded)
+
def do_get_smtp_cert(self, username=None):
if not username:
username = self._active_user
diff --git a/src/leap/bitmask/cli/user.py b/src/leap/bitmask/cli/user.py
index dccfc7d..3cea0c5 100644
--- a/src/leap/bitmask/cli/user.py
+++ b/src/leap/bitmask/cli/user.py
@@ -25,7 +25,7 @@ from leap.bitmask.cli import command
class User(command.Command):
- service = 'user'
+ service = 'bonafide'
usage = '''{name} user <subcommand>
Bitmask account service
@@ -41,16 +41,20 @@ SUBCOMMANDS:
commands = ['active']
+ def __init__(self):
+ super(User, self).__init__()
+ self.data.append('user')
+
def create(self, raw_args):
username = self.username(raw_args)
passwd = getpass.getpass()
- self.data += ['signup', username, passwd]
+ self.data += ['signup', username, passwd, True]
return self._send(printer=command.default_dict_printer)
def auth(self, raw_args):
username = self.username(raw_args)
passwd = getpass.getpass()
- self.data += ['authenticate', username, passwd]
+ self.data += ['authenticate', username, passwd, True]
return self._send(printer=command.default_dict_printer)
def logout(self, raw_args):
diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py
index 368a5bb..55b1960 100644
--- a/src/leap/bitmask/core/dispatcher.py
+++ b/src/leap/bitmask/core/dispatcher.py
@@ -46,23 +46,71 @@ class SubCommand(object):
_method = None
if not _method:
- raise RuntimeError('No such subcommand')
+ raise RuntimeError('No such subcommand: ' + subcmd)
return defer.maybeDeferred(_method, service, *parts, **kw)
+class BonafideCmd(SubCommand):
+
+ label = 'bonafide'
+
+ def __init__(self):
+ self.subcommand_user = UserCmd()
+ self.subcommand_provider = ProviderCmd()
+
+ def do_USER(self, bonafide, *parts):
+ return self.subcommand_user.dispatch(bonafide, *parts[1:])
+
+ def do_PROVIDER(self, bonafide, *parts):
+ return self.subcommand_provider.dispatch(bonafide, *parts[1:])
+
+
+class ProviderCmd(SubCommand):
+
+ label = 'bonafide.provider'
+
+ @register_method("{'domain': str, 'api_uri': str, 'api_version': str}")
+ def do_CREATE(self, bonafide, *parts):
+ domain = parts[2]
+ return bonafide.do_provider_create(domain)
+
+ @register_method("{'domain': str, 'api_uri': str, 'api_version': str}")
+ def do_READ(self, bonafide, *parts):
+ domain = parts[2]
+ return bonafide.do_provider_read(domain)
+
+ @register_method("")
+ def do_DELETE(self, bonafide, *parts):
+ domain = parts[2]
+ bonafide.do_provider_delete(domain)
+
+ @register_method("[{'domain': str}]")
+ def do_LIST(self, bonafide, *parts):
+ seeded = False
+ if len(parts) > 2:
+ seeded = parts[2]
+ return bonafide.do_provider_list(seeded)
+
+
class UserCmd(SubCommand):
- label = 'user'
+ label = 'bonafide.user'
@register_method("{'srp_token': unicode, 'uuid': unicode}")
def do_AUTHENTICATE(self, bonafide, *parts):
user, password = parts[2], parts[3]
- return bonafide.do_authenticate(user, password)
+ autoconf = False
+ if len(parts) > 4:
+ autoconf = parts[4]
+ return bonafide.do_authenticate(user, password, autoconf)
@register_method("{'signup': 'ok', 'user': str}")
def do_SIGNUP(self, bonafide, *parts):
user, password = parts[2], parts[3]
- return bonafide.do_signup(user, password)
+ autoconf = False
+ if len(parts) > 4:
+ autoconf = parts[4]
+ return bonafide.do_signup(user, password, autoconf)
@register_method("{'logout': 'ok'}")
def do_LOGOUT(self, bonafide, *parts):
@@ -286,7 +334,7 @@ class CommandDispatcher(object):
def __init__(self, core):
self.core = core
- self.subcommand_user = UserCmd()
+ self.subcommand_bonafide = BonafideCmd()
self.subcommand_eip = EIPCmd()
self.subcommand_mail = MailCmd()
self.subcommand_keys = KeysCmd()
@@ -314,9 +362,9 @@ class CommandDispatcher(object):
# -----------------------------------------------
- def do_USER(self, *parts):
+ def do_BONAFIDE(self, *parts):
bonafide = self._get_service('bonafide')
- d = self.subcommand_user.dispatch(bonafide, *parts)
+ d = self.subcommand_bonafide.dispatch(bonafide, *parts)
d.addCallbacks(_format_result, _format_error)
return d
diff --git a/src/leap/bitmask/core/web/bitmask.js b/src/leap/bitmask/core/web/bitmask.js
index 39e677f..9ac11b7 100644
--- a/src/leap/bitmask/core/web/bitmask.js
+++ b/src/leap/bitmask/core/web/bitmask.js
@@ -29,9 +29,14 @@
*/
var bitmask = function(){
var event_handlers = {};
+
+ var api_url = '/API/';
+ if (window.location.protocol === "file:") {
+ api_url = 'http://localhost:7070/API/';
+ }
function call(command) {
- var url = '/API/' + command.slice(0, 2).join('/');
+ var url = api_url + command.slice(0, 2).join('/');
var data = JSON.stringify(command.slice(2));
return new Promise(function(resolve, reject) {
@@ -88,50 +93,83 @@ var bitmask = function(){
};
return {
- /**
- * uids are of the form user@provider.net
- */
- user: {
- /**
- * Check wich user is active
- *
- * @return {Promise<string>} The uid of the active user
- */
- active: function() {
- return call(['user', 'active']);
- },
+ bonafide: {
+ provider: {
+ create: function(domain) {
+ return call(['bonafide', 'provider', 'create', domain]);
+ },
- /**
- * Register a new user
- *
- * @param {string} uid The uid to be created
- * @param {string} password The user password
- */
- create: function(uid, password) {
- return call(['user', 'create', uid, password]);
- },
+ read: function(domain) {
+ return call(['bonafide', 'provider', 'read', domain]);
+ },
- /**
- * Login
- *
- * @param {string} uid The uid to log in
- * @param {string} password The user password
- */
- auth: function(uid, password) {
- return call(['user', 'authenticate', uid, password]);
+ delete: function(domain) {
+ return call(['bonafide', 'provider', 'delete', domain]);
+ },
+
+ list: function(seeded) {
+ if (typeof seeded !== 'boolean') {
+ seeded = false;
+ }
+ return call(['bonafide', 'provider', 'list', seeded]);
+ }
},
/**
- * Logout
- *
- * @param {string} uid The uid to log out.
- * If no uid is provided the active user will be used
+ * uids are of the form user@provider.net
*/
- logout: function(uid) {
- if (typeof uid !== 'string') {
- uid = "";
+ user: {
+ /**
+ * Check wich user is active
+ *
+ * @return {Promise<string>} The uid of the active user
+ */
+ active: function() {
+ return call(['bonafide', 'user', 'active']);
+ },
+
+ /**
+ * Register a new user
+ *
+ * @param {string} uid The uid to be created
+ * @param {string} password The user password
+ * @param {boolean} autoconf If the provider should be autoconfigured if it's not allready known
+ * If it's not provided it will default to false
+ */
+ create: function(uid, password, autoconf) {
+ if (typeof autoconf !== 'boolean') {
+ autoconf = false;
+ }
+ return call(['bonafide', 'user', 'create', uid, password, autocnof]);
+ },
+
+ /**
+ * Login
+ *
+ * @param {string} uid The uid to log in
+ * @param {string} password The user password
+ * @param {boolean} autoconf If the provider should be autoconfigured if it's not allready known
+ * If it's not provided it will default to false
+ */
+ auth: function(uid, password, autoconf) {
+ if (typeof autoconf !== 'boolean') {
+ autoconf = false;
+ }
+ return call(['bonafide', 'user', 'authenticate', uid, password, autoconf]);
+ },
+
+ /**
+ * Logout
+ *
+ * @param {string} uid The uid to log out.
+ * If no uid is provided the active user will be used
+ */
+ logout: function(uid) {
+ if (typeof uid !== 'string') {
+ uid = "";
+ }
+ return call(['bonafide', 'user', 'logout', uid]);
}
- return call(['user', 'logout', uid]);
}
},
@@ -153,7 +191,7 @@ var bitmask = function(){
* @return {Promise<string>} The token
*/
get_token: function() {
- return call(['mail', 'get-token']);
+ return call(['mail', 'get_token']);
}
},
diff --git a/src/leap/bitmask/core/web/index.html b/src/leap/bitmask/core/web/index.html
index 7ffbb3f..9951a9b 100644
--- a/src/leap/bitmask/core/web/index.html
+++ b/src/leap/bitmask/core/web/index.html
@@ -12,10 +12,57 @@
bitmask.events.register("KEYMANAGER_KEY_FOUND", event_handler);
};
+ function configure() {
+ var domain = document.getElementById('domain').value;
+ bitmask.bonafide.provider.create(domain).then(function(response) {
+ log("Provider configured: ");
+ for (k in response) {
+ log(" " + k + ": " + response[k]);
+ }
+ }, function(error) {
+ log("Some error ocurred: " + error);
+ });
+ };
+
+ function read() {
+ var domain = document.getElementById('domain').value;
+ bitmask.bonafide.provider.read(domain).then(function(response) {
+ log("Provider configuration: ");
+ for (k in response) {
+ log(" " + k + ": " + response[k]);
+ }
+ }, function(error) {
+ log("Some error ocurred: " + error);
+ });
+ };
+
+ function del() {
+ var domain = document.getElementById('domain').value;
+ bitmask.bonafide.provider.delete(domain).then(function(response) {
+ log("Provider deleted: ");
+ for (k in response) {
+ log(" " + k + ": " + response[k]);
+ }
+ }, function(error) {
+ log("Some error ocurred: " + error);
+ });
+ };
+
+ function list() {
+ bitmask.bonafide.provider.list().then(function(response) {
+ log("List providers: ");
+ for (k in response) {
+ log(" domain: " + response[k]["domain"]);
+ }
+ }, function(error) {
+ log("Some error ocurred: " + error);
+ });
+ };
+
function login() {
var email = document.getElementById('email').value;
var password = document.getElementById('password').value;
- bitmask.user.auth(email, password).then(function(response) {
+ bitmask.bonafide.user.auth(email, password).then(function(response) {
log("We are logged in: ");
for (k in response) {
log(" " + k + ": " + response[k]);
@@ -26,7 +73,7 @@
};
function logout() {
- bitmask.user.logout().then(function(response) {
+ bitmask.bonafide.user.logout().then(function(response) {
log("We are logged out: ");
for (k in response) {
log(" " + k + ": " + response[k]);
@@ -36,6 +83,19 @@
});
};
+ function user() {
+ bitmask.bonafide.user.active().then(function(response) {
+ log("The active user is: " + response);
+ }, function(error) {
+ log("Some error ocurred: " + error);
+ });
+ bitmask.mail.get_token().then(function(response) {
+ log("The token is: " + response);
+ }, function(error) {
+ log("Some error ocurred: " + error);
+ });
+ };
+
function event_handler(evnt, content) {
log("Event: " + evnt);
for (i in content) {
@@ -53,11 +113,19 @@
<h1>Bitmask Control Panel</h1>
<noscript>You must enable JavaScript</noscript>
<form>
+ <p>Provider: <input id="domain" type="text" size="50" maxlength="50" value="mail.bitmask.net"></p>
+ </form>
+ <button onclick='configure();'>Configure provider</button>
+ <button onclick='read();'>Read providers</button>
+ <button onclick='del();'>Delete providers</button>
+ <button onclick='list();'>List providers</button>
+ <form>
<p>Email address: <input id="email" type="text" size="50" maxlength="50" value="user@mail.bitmask.net"></p>
<p>Password: <input id="password" type="password" size="50" maxlength="50" ></p>
</form>
<button onclick='login();'>Log In</button>
<button onclick='logout();'>Log Out</button>
+ <button onclick='user();'>User</button>
<pre id="log" style="height: 20em; overflow-y: scroll; background-color: #faa;"></pre>
</body>
</html>