diff options
| -rw-r--r-- | docs/next-changelog.rst | 1 | ||||
| -rw-r--r-- | src/leap/bitmask/bonafide/_protocol.py | 12 | ||||
| -rw-r--r-- | src/leap/bitmask/bonafide/_srp.py | 23 | ||||
| -rw-r--r-- | src/leap/bitmask/bonafide/service.py | 11 | ||||
| -rw-r--r-- | src/leap/bitmask/bonafide/session.py | 14 | ||||
| -rw-r--r-- | src/leap/bitmask/cli/user.py | 20 | ||||
| -rw-r--r-- | src/leap/bitmask/core/dispatcher.py | 6 | ||||
| -rw-r--r-- | src/leap/bitmask/core/mail_services.py | 11 | ||||
| -rw-r--r-- | src/leap/bitmask/core/service.py | 1 | ||||
| -rw-r--r-- | src/leap/bitmask/core/web/bitmask.js | 11 | ||||
| -rw-r--r-- | 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 e9df07f..f57e2ae 100644 --- a/docs/next-changelog.rst +++ b/docs/next-changelog.rst @@ -15,6 +15,7 @@ Features  - `#8400 <https://leap.se/code/issues/8400>`_: Add manual provider registration.  - `#8435 <https://leap.se/code/issues/8435>`_: Write service tokens to a file for email clients to read.  - `#8486 <https://leap.se/code/issues/8486>`_: Fetch smtp cert automatically if missing. +- `#8487 <https://leap.se/code/issues/8487>`_: Add change password command.  - 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 ff357a1..7917f38 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 b0dd83f..34a75a5 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 5e3b0f1..797e606 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 f25fa05..abb697a 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 9ce4dc6..1c4757e 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 <user@example.org>")          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 4c885ce..2860c88 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 3de4df2..e19b719 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 27853c0..e449a8c 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 fedd5fc..71a34f4 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 fedd5fc..71a34f4 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]); +                },              }          }, | 
