From 2fc85ad7d109ca4304d1fb1515b7087a1bb2ae3e Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 27 Sep 2016 19:28:28 -0500 Subject: [feature]Add change password command - Resolves: #8487 --- docs/next-changelog.rst | 1 + src/leap/bitmask/bonafide/_protocol.py | 12 ++++++++++++ src/leap/bitmask/bonafide/_srp.py | 23 ++++++++++++++++++++--- src/leap/bitmask/bonafide/service.py | 11 +++++++++++ src/leap/bitmask/bonafide/session.py | 14 ++++++++++++++ src/leap/bitmask/cli/user.py | 20 +++++++++++++++++++- src/leap/bitmask/core/dispatcher.py | 6 ++++++ src/leap/bitmask/core/mail_services.py | 11 +++++++++++ src/leap/bitmask/core/service.py | 1 + src/leap/bitmask/core/web/bitmask.js | 11 +++++++++++ ui/app/lib/bitmask.js | 11 +++++++++++ 11 files changed, 117 insertions(+), 4 deletions(-) diff --git a/docs/next-changelog.rst b/docs/next-changelog.rst index e9df07f5..f57e2ae4 100644 --- a/docs/next-changelog.rst +++ b/docs/next-changelog.rst @@ -15,6 +15,7 @@ Features - `#8400 `_: Add manual provider registration. - `#8435 `_: Write service tokens to a file for email clients to read. - `#8486 `_: Fetch smtp cert automatically if missing. +- `#8487 `_: Add change password command. - 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 ff357a14..7917f383 100644 --- a/src/leap/bitmask/bonafide/_protocol.py +++ b/src/leap/bitmask/bonafide/_protocol.py @@ -147,6 +147,18 @@ class BonafideProtocol(object): d.addCallback(lambda _: '%s logged out' % full_id) return d + def do_change_password(self, full_id, current_password, new_password): + log.msg('Change password 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] + + if current_password != session.password: + return fail(RuntimeError("The current password is not valid")) + + return session.change_password(new_password) + def do_get_provider(self, provider_id, autoconf=False): provider = config.Provider(provider_id, autoconf=autoconf) return provider.callWhenMainConfigReady(provider.config) diff --git a/src/leap/bitmask/bonafide/_srp.py b/src/leap/bitmask/bonafide/_srp.py index b0dd83ff..34a75a56 100644 --- a/src/leap/bitmask/bonafide/_srp.py +++ b/src/leap/bitmask/bonafide/_srp.py @@ -101,9 +101,7 @@ class SRPSignupMechanism(object): """ def get_signup_params(self, username, password): - salt, verifier = srp.create_salted_verification_key( - bytes(username), bytes(password), - srp.SHA256, srp.NG_1024) + salt, verifier = _get_salt_verifier(username, password) user_data = { 'user[login]': username, 'user[password_salt]': binascii.hexlify(salt), @@ -121,6 +119,25 @@ class SRPSignupMechanism(object): return username +class SRPPasswordChangeMechanism(object): + + """ + Implement a protocol-agnostic SRP passord change mechanism. + """ + + def get_password_params(self, username, password): + salt, verifier = _get_salt_verifier(username, password) + user_data = { + 'user[password_salt]': binascii.hexlify(salt), + 'user[password_verifier]': binascii.hexlify(verifier)} + return user_data + + +def _get_salt_verifier(username, password): + return srp.create_salted_verification_key(bytes(username), bytes(password), + srp.SHA256, srp.NG_1024) + + def _safe_unhexlify(val): return binascii.unhexlify(val) \ if (len(val) % 2 == 0) else binascii.unhexlify('0' + val) diff --git a/src/leap/bitmask/bonafide/service.py b/src/leap/bitmask/bonafide/service.py index 5e3b0f1c..797e6065 100644 --- a/src/leap/bitmask/bonafide/service.py +++ b/src/leap/bitmask/bonafide/service.py @@ -103,6 +103,17 @@ class BonafideService(HookableService): d.addCallback(lambda response: {'logout': 'ok'}) return d + def do_change_password(self, username, current_password, new_password): + def notify_passphrase_change(_): + data = dict(username=username, password=new_password) + self.trigger_hook('on_passphrase_change', **data) + + d = self._bonafide.do_change_password(username, current_password, + new_password) + d.addCallback(notify_passphrase_change) + d.addCallback(lambda _: {'update': 'ok'}) + return d + def do_provider_create(self, domain): return self._bonafide.do_get_provider(domain, autoconf=True) diff --git a/src/leap/bitmask/bonafide/session.py b/src/leap/bitmask/bonafide/session.py index f25fa05e..abb697ac 100644 --- a/src/leap/bitmask/bonafide/session.py +++ b/src/leap/bitmask/bonafide/session.py @@ -66,6 +66,7 @@ class Session(object): password = self.password or '' self._srp_auth = _srp.SRPAuthMechanism(username, password) self._srp_signup = _srp.SRPSignupMechanism() + self._srp_password = _srp.SRPPasswordChangeMechanism() self._token = None self._uuid = None @@ -123,6 +124,19 @@ class Session(object): self._initialize_session() defer.returnValue(OK) + @_auth_required + @defer.inlineCallbacks + def change_password(self, password): + uri = self._api.get_update_user_uri(uid=self._uuid) + met = self._api.get_update_user_method() + params = self._srp_password.get_password_params( + self.username, password) + update = yield self._request(self._agent, uri, values=params, + method=met) + self.password = password + self._srp_auth = _srp.SRPAuthMechanism(self.username, password) + defer.returnValue(OK) + # User certificates def get_vpn_cert(self): diff --git a/src/leap/bitmask/cli/user.py b/src/leap/bitmask/cli/user.py index 9ce4dc6a..1c4757e1 100644 --- a/src/leap/bitmask/cli/user.py +++ b/src/leap/bitmask/cli/user.py @@ -35,6 +35,7 @@ SUBCOMMANDS: create Registers new user, if possible auth Logs in against the provider logout Ends any active session with the provider + update Update user password active Shows the active user, if any '''.format(name=command.appname) @@ -47,7 +48,7 @@ SUBCOMMANDS: def create(self, raw_args): username = self.username(raw_args) - passwd = getpass.getpass() + passwd = self.getpass_twice() self.data += ['create', username, passwd, 'true'] return self._send(printer=command.default_dict_printer) @@ -62,6 +63,13 @@ SUBCOMMANDS: self.data += ['logout', username] return self._send(printer=command.default_dict_printer) + def update(self, raw_args): + username = self.username(raw_args) + current_passwd = getpass.getpass('Current password: ') + new_passwd = self.getpass_twice('New password: ') + self.data += ['update', username, current_passwd, new_passwd] + return self._send(printer=command.default_dict_printer) + def username(self, raw_args): args = tuple([command.appname] + sys.argv[1:3]) parser = argparse.ArgumentParser( @@ -78,3 +86,13 @@ SUBCOMMANDS: self._error("Username ID must be in the form ") return username + + def getpass_twice(self, prompt='Password: '): + while True: + passwd1 = getpass.getpass(prompt) + passwd2 = getpass.getpass('Retype the password: ') + if passwd1 == passwd2: + return passwd1 + else: + print "The passwords do not match, try again." + print "" diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py index 4c885ce7..2860c88d 100644 --- a/src/leap/bitmask/core/dispatcher.py +++ b/src/leap/bitmask/core/dispatcher.py @@ -119,6 +119,12 @@ class UserCmd(SubCommand): user = parts[2] return bonafide.do_logout(user) + @register_method("{'update': 'ok'}") + def do_UPDATE(self, bonafide, *parts): + user, current_password, new_password = parts[2], parts[3], parts[4] + return bonafide.do_change_password( + user, current_password, new_password) + @register_method('str') def do_ACTIVE(self, bonafide, *parts): return bonafide.do_get_active_user() diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py index 3de4df2e..e19b7192 100644 --- a/src/leap/bitmask/core/mail_services.py +++ b/src/leap/bitmask/core/mail_services.py @@ -210,6 +210,17 @@ class SoledadService(HookableService): container.add_instance( userid, password, uuid=uuid, token=token) + def hook_on_passphrase_change(self, **kw): + # TODO: if bitmask stops before this hook being executed bonafide and + # soledad will end up with different passwords + # https://leap.se/code/issues/8489 + userid = kw['username'] + password = kw['password'] + soledad = self._container.get_instance(userid) + if soledad is not None: + log.msg("Change soledad passphrase for %s" % userid) + soledad.change_passphrase(unicode(password)) + class KeymanagerContainer(Container): diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index 27853c09..e449a8cd 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -89,6 +89,7 @@ class BitmaskBackend(configurable.ConfigurableService): # (2) provider offers this service bf.register_hook('on_passphrase_entry', listener='soledad') bf.register_hook('on_bonafide_auth', listener='soledad') + bf.register_hook('on_passphrase_change', listener='soledad') bf.register_hook('on_bonafide_auth', listener='keymanager') bf.register_hook('on_bonafide_auth', listener='mail') diff --git a/src/leap/bitmask/core/web/bitmask.js b/src/leap/bitmask/core/web/bitmask.js index fedd5fcd..71a34f4a 100644 --- a/src/leap/bitmask/core/web/bitmask.js +++ b/src/leap/bitmask/core/web/bitmask.js @@ -177,6 +177,17 @@ var bitmask = function(){ } return call(['bonafide', 'user', 'logout', uid]); } + + /** + * Change password + * + * @param {string} uid The uid to log in + * @param {string} current_password The current user password + * @param {string} new_password The new user password + */ + update: function(uid, current_password, new_password) { + return call(['bonafide', 'user', 'update', uid, current_password, new_password]); + }, } }, diff --git a/ui/app/lib/bitmask.js b/ui/app/lib/bitmask.js index fedd5fcd..71a34f4a 100644 --- a/ui/app/lib/bitmask.js +++ b/ui/app/lib/bitmask.js @@ -177,6 +177,17 @@ var bitmask = function(){ } return call(['bonafide', 'user', 'logout', uid]); } + + /** + * Change password + * + * @param {string} uid The uid to log in + * @param {string} current_password The current user password + * @param {string} new_password The new user password + */ + update: function(uid, current_password, new_password) { + return call(['bonafide', 'user', 'update', uid, current_password, new_password]); + }, } }, -- cgit v1.2.3