Example #1
0
    def check_user_auth(self, email, pw, request, env):
        # Validate a user's login email address and password. If MFA is enabled,
        # check the MFA token in the X-Auth-Token header.
        #
        # On login failure, raises a ValueError with a login error message. On
        # success, nothing is returned.

        # Authenticate.
        try:
            # Get the hashed password of the user. Raise a ValueError if the
            # email address does not correspond to a user. But wrap it in the
            # same exception as if a password fails so we don't easily reveal
            # if an email address is valid.
            pw_hash = get_mail_password(email, env)

            # Use 'doveadm pw' to check credentials. doveadm will return
            # a non-zero exit status if the credentials are no good,
            # and check_call will raise an exception in that case.
            utils.shell('check_call', [
                "/usr/bin/doveadm",
                "pw",
                "-p",
                pw,
                "-t",
                pw_hash,
            ])
        except:
            # Login failed.
            raise ValueError("Incorrect email address or password.")

        # If MFA is enabled, check that MFA passes.
        status, hints = validate_auth_mfa(email, request, env)
        if not status:
            # Login valid. Hints may have more info.
            raise ValueError(",".join(hints))
Example #2
0
 def create_user_key(self, email, env):
     # Store an HMAC with the client. The hashed message of the HMAC will be the user's
     # email address & hashed password and the key will be the master API key. The user of
     # course has their own email address and password. We assume they do not have the master
     # API key (unless they are trusted anyway). The HMAC proves that they authenticated
     # with us in some other way to get the HMAC. Including the password means that when
     # a user's password is reset, the HMAC changes and they will correctly need to log
     # in to the control panel again. This method raises a ValueError if the user does
     # not exist, due to get_mail_password.
     msg = b"AUTH:" + email.encode("utf8") + b" " + get_mail_password(email, env).encode("utf8")
     return hmac.new(self.key.encode("ascii"), msg, digestmod="sha256").hexdigest()
Example #3
0
	def create_user_key(self, email, env):
		# Store an HMAC with the client. The hashed message of the HMAC will be the user's
		# email address & hashed password and the key will be the master API key. The user of
		# course has their own email address and password. We assume they do not have the master
		# API key (unless they are trusted anyway). The HMAC proves that they authenticated
		# with us in some other way to get the HMAC. Including the password means that when
		# a user's password is reset, the HMAC changes and they will correctly need to log
		# in to the control panel again. This method raises a ValueError if the user does
		# not exist, due to get_mail_password.
		msg = b"AUTH:" + email.encode("utf8") + b" " + get_mail_password(email, env).encode("utf8")
		return hmac.new(self.key.encode('ascii'), msg, digestmod="sha256").hexdigest()
Example #4
0
    def check_user_auth(self, email, pw, request, env):
        # Validate a user's login email address and password. If MFA is enabled,
        # check the MFA token in the X-Auth-Token header.
        #
        # On success returns a list of privileges (e.g. [] or ['admin']). On login
        # failure, raises a ValueError with a login error message.

        # Sanity check.
        if email == "" or pw == "":
            raise ValueError("Enter an email address and password.")

        # The password might be a user-specific API key. create_user_key raises
        # a ValueError if the user does not exist.
        if hmac.compare_digest(self.create_user_key(email, env), pw):
            # OK.
            pass
        else:
            # Get the hashed password of the user. Raise a ValueError if the
            # email address does not correspond to a user.
            pw_hash = get_mail_password(email, env)

            # Authenticate.
            try:
                # Use 'doveadm pw' to check credentials. doveadm will return
                # a non-zero exit status if the credentials are no good,
                # and check_call will raise an exception in that case.
                utils.shell('check_call', [
                    "/usr/bin/doveadm",
                    "pw",
                    "-p",
                    pw,
                    "-t",
                    pw_hash,
                ])
            except:
                # Login failed.
                raise ValueError("Invalid password.")

            # If MFA is enabled, check that MFA passes.
            status, hints = validate_auth_mfa(email, request, env)
            if not status:
                # Login valid. Hints may have more info.
                raise ValueError(",".join(hints))

        # Get privileges for authorization. This call should never fail because by this
        # point we know the email address is a valid user. But on error the call will
        # return a tuple of an error message and an HTTP status code.
        privs = get_mail_user_privileges(email, env)
        if isinstance(privs, tuple): raise ValueError(privs[0])

        # Return a list of privileges.
        return privs
Example #5
0
    def create_user_password_state_token(self, email, env):
        # Create a token that changes if the user's password or MFA options change
        # so that sessions become invalid if any of that information changes.
        msg = get_mail_password(email, env).encode("utf8")

        # Add to the message the current MFA state, which is a list of MFA information.
        # Turn it into a string stably.
        msg += b" " + json.dumps(get_hash_mfa_state(email, env),
                                 sort_keys=True).encode("utf8")

        # Make a HMAC using the system API key as a hash key.
        hash_key = self.key.encode('ascii')
        return hmac.new(hash_key, msg, digestmod="sha256").hexdigest()
Example #6
0
    def get_user_credentials(self, email, pw, env):
        # Validate a user's credentials. On success returns a list of
        # privileges (e.g. [] or ['admin']). On failure raises a ValueError
        # with a login error message.

        # Sanity check.
        if email == "" or pw == "":
            raise ValueError("Enter an email address and password.")

        # The password might be a user-specific API key.
        if hmac.compare_digest(self.create_user_key(email), pw):
            # OK.
            pass
        else:
            # Get the hashed password of the user. Raise a ValueError if the
            # email address does not correspond to a user.
            pw_hash = get_mail_password(email, env)

            # Authenticate.
            try:
                # Use 'doveadm pw' to check credentials. doveadm will return
                # a non-zero exit status if the credentials are no good,
                # and check_call will raise an exception in that case.
                utils.shell('check_call', [
                    "/usr/bin/doveadm",
                    "pw",
                    "-p",
                    pw,
                    "-t",
                    pw_hash,
                ])
            except:
                # Login failed.
                raise ValueError("Invalid password.")

        # Get privileges for authorization.

        # (This call should never fail on a valid user. But if it did fail, it would
        # return a tuple of an error message and an HTTP status code.)
        privs = get_mail_user_privileges(email, env)
        if isinstance(privs, tuple):
            raise Exception("Error getting privileges.")

        # Return a list of privileges.
        return privs
Example #7
0
	def get_user_credentials(self, email, pw, env):
		# Validate a user's credentials. On success returns a list of
		# privileges (e.g. [] or ['admin']). On failure raises a ValueError
		# with a login error message. 

		# Sanity check.
		if email == "" or pw == "":
			raise ValueError("Enter an email address and password.")

		# The password might be a user-specific API key.
		if hmac.compare_digest(self.create_user_key(email), pw):
			# OK.
			pass
		else:
			# Get the hashed password of the user. Raise a ValueError if the
			# email address does not correspond to a user.
			pw_hash = get_mail_password(email, env)

			# Authenticate.
			try:
				# Use 'doveadm pw' to check credentials. doveadm will return
				# a non-zero exit status if the credentials are no good,
				# and check_call will raise an exception in that case.
				utils.shell('check_call', [
					"/usr/bin/doveadm", "pw",
					"-p", pw,
					"-t", pw_hash,
					])
			except:
				# Login failed.
				raise ValueError("Invalid password.")

		# Get privileges for authorization.

		# (This call should never fail on a valid user. But if it did fail, it would
		# return a tuple of an error message and an HTTP status code.)
		privs = get_mail_user_privileges(email, env)
		if isinstance(privs, tuple): raise Exception("Error getting privileges.")

		# Return a list of privileges.
		return privs
Example #8
0
    def create_user_key(self, email, env):
        # Create a user API key, which is a shared secret that we can re-generate from
        # static information in our database. The shared secret contains the user's
        # email address, current hashed password, and current MFA state, so that the
        # key becomes invalid if any of that information changes.
        #
        # Use an HMAC to generate the API key using our master API key as a key,
        # which also means that the API key becomes invalid when our master API key
        # changes --- i.e. when this process is restarted.
        #
        # Raises ValueError via get_mail_password if the user doesn't exist.

        # Construct the HMAC message from the user's email address and current password.
        msg = b"AUTH:" + email.encode("utf8") + b" " + get_mail_password(
            email, env).encode("utf8")

        # Add to the message the current MFA state, which is a list of MFA information.
        # Turn it into a string stably.
        msg += b" " + json.dumps(get_hash_mfa_state(email, env),
                                 sort_keys=True).encode("utf8")

        # Make the HMAC.
        hash_key = self.key.encode('ascii')
        return hmac.new(hash_key, msg, digestmod="sha256").hexdigest()