1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2024-12-22 07:17:05 +00:00

/admin login now issues a user-specific key for future calls (rather than providing the system-wide API key or passing the password on each request)

This commit is contained in:
Joshua Tauberer 2014-12-01 19:20:46 +00:00
parent 023b38df50
commit 1039a08be6
3 changed files with 38 additions and 28 deletions

View File

@ -1,4 +1,4 @@
import base64, os, os.path
import base64, os, os.path, hmac
from flask import make_response
@ -43,8 +43,9 @@ class KeyAuthService:
def authenticate(self, request, env):
"""Test if the client key passed in HTTP Authorization header matches the service key
or if the or username/password passed in the header matches an administrator user.
Returns a list of user privileges (e.g. [] or ['admin']) raise a ValueError on
login failure."""
Returns a tuple of the user's email address and list of user privileges (e.g.
('my@email', []) or ('my@email', ['admin']); raises a ValueError on login failure.
If the user used an API key, the user's email is returned as None."""
def decode(s):
return base64.b64decode(s.encode('ascii')).decode('ascii')
@ -72,11 +73,11 @@ class KeyAuthService:
raise ValueError("Authorization header invalid.")
elif username == self.key:
# The user passed the API key which grants administrative privs.
return ["admin"]
return (None, ["admin"])
else:
# The user is trying to log in with a username and password.
# Raises or returns privs.
return self.get_user_credentials(username, password, env)
# The user is trying to log in with a username and user-specific
# API key or password. Raises or returns privs.
return (username, self.get_user_credentials(username, password, env))
def get_user_credentials(self, email, pw, env):
# Validate a user's credentials. On success returns a list of
@ -87,23 +88,28 @@ class KeyAuthService:
if email == "" or pw == "":
raise ValueError("Enter an email address and password.")
# 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)
# 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.")
# 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.
@ -115,6 +121,9 @@ class KeyAuthService:
# Return a list of privileges.
return privs
def create_user_key(self, email):
return hmac.new(self.key.encode('ascii'), b"AUTH:" + email.encode("utf8"), digestmod="sha1").hexdigest()
def _generate_key(self):
raw_key = os.urandom(32)
return base64.b64encode(raw_key).decode('ascii')

View File

@ -31,7 +31,7 @@ def authorized_personnel_only(viewfunc):
# Authenticate the passed credentials, which is either the API key or a username:password pair.
error = None
try:
privs = auth_service.authenticate(request, env)
email, privs = auth_service.authenticate(request, env)
except ValueError as e:
# Authentication failed.
privs = []
@ -95,7 +95,7 @@ def index():
def me():
# Is the caller authorized?
try:
privs = auth_service.authenticate(request, env)
email, privs = auth_service.authenticate(request, env)
except ValueError as e:
return json_response({
"status": "invalid",
@ -104,12 +104,13 @@ def me():
resp = {
"status": "ok",
"email": email,
"privileges": privs,
}
# Is authorized as admin?
# Is authorized as admin? Return an API key for future use.
if "admin" in privs:
resp["api_key"] = auth_service.key
resp["api_key"] = auth_service.create_user_key(email)
# Return.
return json_response(resp)

View File

@ -85,7 +85,7 @@ function do_login() {
// Login succeeded.
// Save the new credentials.
api_credentials = [response.api_key, ""];
api_credentials = [response.email, response.api_key];
// Try to wipe the username/password information.
$('#loginEmail').val('');