From 7a6e187e0a970ad6fb722fc9dfd3be784b254c06 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 6 Sep 2016 19:27:31 +0200 Subject: [feat] Add manual provider registration - Resolves: #8400 --- docs/next-changelog.rst | 1 + src/leap/bitmask/bonafide/_protocol.py | 22 +++++-- src/leap/bitmask/bonafide/config.py | 54 ++++++++++++--- src/leap/bitmask/bonafide/errors.py | 23 +++++++ src/leap/bitmask/bonafide/service.py | 23 +++++-- src/leap/bitmask/cli/user.py | 10 ++- src/leap/bitmask/core/dispatcher.py | 62 ++++++++++++++++-- src/leap/bitmask/core/web/bitmask.js | 116 ++++++++++++++++++++++----------- src/leap/bitmask/core/web/index.html | 72 +++++++++++++++++++- 9 files changed, 312 insertions(+), 71 deletions(-) create mode 100644 src/leap/bitmask/bonafide/errors.py 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 `_: Add basic keymanagement to the cli. - `#8265 `_: Add a REST API and bitmask.js library for it. +- `#8400 `_: Add manual provider registration. - Use mail_auth token in the core instead of imap/smtp tokens. - `#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 . +""" +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 . + """ 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 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} 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} 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} 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) { @@ -52,12 +112,20 @@

Bitmask Control Panel

+
+

Provider:

+
+ + + +

Email address:

Password:

+

    
 
-- 
cgit v1.2.3