From af27100e35f30f91f3c8f3eb4b8fcef978d11eae Mon Sep 17 00:00:00 2001 From: "Kali Kaneko (leap communications)" Date: Mon, 3 Oct 2016 17:40:09 -0400 Subject: [feature] handle invite codes In the command line, --invitecode is a new optional parameter to the command "user create". bonafide service handles the invite codes. javascript library should be updated accordingly - Resolves: #7550 --- docs/changelog-next.rst | 1 + src/leap/bitmask/bonafide/_protocol.py | 8 +++---- src/leap/bitmask/bonafide/_srp.py | 24 +++++++++++++++++++-- src/leap/bitmask/bonafide/service.py | 5 +++-- src/leap/bitmask/bonafide/session.py | 4 ++-- src/leap/bitmask/cli/user.py | 39 ++++++++++++++++++++++++++++------ src/leap/bitmask/core/dispatcher.py | 13 ++++++++---- 7 files changed, 74 insertions(+), 20 deletions(-) diff --git a/docs/changelog-next.rst b/docs/changelog-next.rst index 41414d8f..2337df5e 100644 --- a/docs/changelog-next.rst +++ b/docs/changelog-next.rst @@ -10,6 +10,7 @@ I've added a new category `Misc` so we can track doc/style/packaging stuff. Features ~~~~~~~~ +- `#7550 `_: Add ability to use invite codes during signup - `#7965 `_: Add basic keymanagement to the cli. - `#8265 `_: Add a REST API and bitmask.js library for it. - `#8400 `_: Add manual provider registration. diff --git a/src/leap/bitmask/bonafide/_protocol.py b/src/leap/bitmask/bonafide/_protocol.py index 3572cbf6..1112550f 100644 --- a/src/leap/bitmask/bonafide/_protocol.py +++ b/src/leap/bitmask/bonafide/_protocol.py @@ -77,16 +77,16 @@ class BonafideProtocol(object): # Service public methods - def do_signup(self, full_id, password, autoconf=False): + def do_signup(self, full_id, password, invite=None, autoconf=False): log.msg('SIGNUP for %s' % full_id) _, provider_id = config.get_username_and_provider(full_id) provider = config.Provider(provider_id, autoconf=autoconf) d = provider.callWhenReady( - self._do_signup, provider, full_id, password) + self._do_signup, provider, full_id, password, invite) return d - def _do_signup(self, provider, full_id, password): + def _do_signup(self, provider, full_id, password, invite): # XXX check it's unauthenticated def return_user(result, _session): @@ -97,7 +97,7 @@ class BonafideProtocol(object): 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 = session.signup(username, password, invite) d.addCallback(return_user, session) d.addErrback(self._del_session_errback, full_id) return d diff --git a/src/leap/bitmask/bonafide/_srp.py b/src/leap/bitmask/bonafide/_srp.py index 34a75a56..3f69b33b 100644 --- a/src/leap/bitmask/bonafide/_srp.py +++ b/src/leap/bitmask/bonafide/_srp.py @@ -19,12 +19,17 @@ SRP Authentication. """ +from twisted.logger import Logger + import binascii import json import srp +log = Logger() + + class SRPAuthMechanism(object): """ @@ -100,19 +105,34 @@ class SRPSignupMechanism(object): Implement a protocol-agnostic SRP Registration mechanism. """ - def get_signup_params(self, username, password): + def get_signup_params(self, username, password, invite=None): salt, verifier = _get_salt_verifier(username, password) user_data = { 'user[login]': username, 'user[password_salt]': binascii.hexlify(salt), 'user[password_verifier]': binascii.hexlify(verifier)} + if invite is not None: + user_data.update({'user[invite_code]': invite}) 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] + errmsg = json.dumps(errors) + log.error('Oops! Errors during signup: {data!r}', data=errmsg) + msg = errors.get('invite_code') + if msg: + msg = msg[0] + else: + msg = errors.get('login') + if msg: + # there is a bug https://leap.se/code/issues/8504 + # the server tells us 'has already been taken' several + # times + msg = 'username ' + msg[0] + else: + msg = 'unknown signup error' raise SRPRegistrationError(msg) else: username = signup.get('login') diff --git a/src/leap/bitmask/bonafide/service.py b/src/leap/bitmask/bonafide/service.py index fbe6846e..4e25172c 100644 --- a/src/leap/bitmask/bonafide/service.py +++ b/src/leap/bitmask/bonafide/service.py @@ -85,8 +85,9 @@ class BonafideService(HookableService): 'srp_token': response[0], 'uuid': response[1]}) return d - def do_signup(self, username, password, autoconf=False): - d = self._bonafide.do_signup(username, password, autoconf) + def do_signup(self, username, password, invite=None, autoconf=False): + d = self._bonafide.do_signup( + username, password, invite=invite, autoconf=autoconf) d.addCallback(lambda response: {'signup': 'ok', 'user': response}) return d diff --git a/src/leap/bitmask/bonafide/session.py b/src/leap/bitmask/bonafide/session.py index abb697ac..213e7731 100644 --- a/src/leap/bitmask/bonafide/session.py +++ b/src/leap/bitmask/bonafide/session.py @@ -162,13 +162,13 @@ class Session(object): # User management @defer.inlineCallbacks - def signup(self, username, password): + def signup(self, username, password, invite=None): # 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) + username, password, invite) signup = yield self._request(self._agent, uri, values=params, method=met) diff --git a/src/leap/bitmask/cli/user.py b/src/leap/bitmask/cli/user.py index 8d3484cc..d1014ee7 100644 --- a/src/leap/bitmask/cli/user.py +++ b/src/leap/bitmask/cli/user.py @@ -20,6 +20,7 @@ Bitmask Command Line interface: user import argparse import getpass import sys +from copy import copy from colorama import Fore @@ -50,9 +51,27 @@ SUBCOMMANDS: self.data.append('user') def create(self, raw_args): - username = self.username(raw_args) - passwd = self._getpass_twice() - self.data += ['create', username, passwd, 'true'] + args = tuple([command.appname] + sys.argv[1:4]) + parser = argparse.ArgumentParser( + description='Bitmask user', + prog='%s %s %s %s' % args) + parser.add_argument('--invitecode', **_invitecode_kw) + parser.add_argument('username', **_username_kw) + + subargs = parser.parse_args(raw_args) + + # username parsing is factored out, but won't + # accept the optional parameters. so strip them. + args = copy(raw_args) + for (index, item) in enumerate(args): + if item.startswith('--'): + args.pop(index + 1) + args.pop(index) + + username = self.username(args) + passwd = self.getpass_twice() + self.data += ['create', username, passwd, + subargs.invite, 'true'] return self._send(printer=command.default_dict_printer) def auth(self, raw_args): @@ -82,11 +101,10 @@ SUBCOMMANDS: parser = argparse.ArgumentParser( description='Bitmask user', prog='%s %s %s' % args) - parser.add_argument('username', nargs=1, - help='username ID, in the form ') + parser.add_argument('username', **_username_kw) subargs = parser.parse_args(raw_args) - username = subargs.username[0] + username = subargs.username if not username: self._error("Missing username ID but needed for this command") if '@' not in username: @@ -110,3 +128,12 @@ SUBCOMMANDS: if u['authenticated']: color = Fore.GREEN print(color + u['userid'] + Fore.RESET) + +_username_kw = { + 'nargs': '?', + 'help': 'username ID, in the form '} + +_invitecode_kw = { + 'dest': 'invite', + 'default': 'none', 'action': 'store', 'nargs': '?', 'type': str, + 'help': 'invite code, if needed to register with this provider'} diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py index a2fd6387..5b7b836e 100644 --- a/src/leap/bitmask/core/dispatcher.py +++ b/src/leap/bitmask/core/dispatcher.py @@ -107,12 +107,17 @@ class UserCmd(SubCommand): @register_method("{'signup': 'ok', 'user': str}") def do_CREATE(self, bonafide, *parts): - user, password = parts[2], parts[3] + # params are: [user, create, full_id, password, invite, autoconf] + user, password, invite = parts[2], parts[3], parts[4] + + # TODO factor out null/bool conversion to a util function. + if invite == 'none': + invite = None autoconf = False - if len(parts) > 4: - if parts[4] == 'true': + if len(parts) > 5: + if parts[5] == 'true': autoconf = True - return bonafide.do_signup(user, password, autoconf) + return bonafide.do_signup(user, password, invite, autoconf) @register_method("{'logout': 'ok'}") def do_LOGOUT(self, bonafide, *parts): -- cgit v1.2.3