mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2024-11-22 02:17:26 +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:
parent
023b38df50
commit
1039a08be6
@ -1,4 +1,4 @@
|
|||||||
import base64, os, os.path
|
import base64, os, os.path, hmac
|
||||||
|
|
||||||
from flask import make_response
|
from flask import make_response
|
||||||
|
|
||||||
@ -43,8 +43,9 @@ class KeyAuthService:
|
|||||||
def authenticate(self, request, env):
|
def authenticate(self, request, env):
|
||||||
"""Test if the client key passed in HTTP Authorization header matches the service key
|
"""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.
|
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
|
Returns a tuple of the user's email address and list of user privileges (e.g.
|
||||||
login failure."""
|
('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):
|
def decode(s):
|
||||||
return base64.b64decode(s.encode('ascii')).decode('ascii')
|
return base64.b64decode(s.encode('ascii')).decode('ascii')
|
||||||
@ -72,11 +73,11 @@ class KeyAuthService:
|
|||||||
raise ValueError("Authorization header invalid.")
|
raise ValueError("Authorization header invalid.")
|
||||||
elif username == self.key:
|
elif username == self.key:
|
||||||
# The user passed the API key which grants administrative privs.
|
# The user passed the API key which grants administrative privs.
|
||||||
return ["admin"]
|
return (None, ["admin"])
|
||||||
else:
|
else:
|
||||||
# The user is trying to log in with a username and password.
|
# The user is trying to log in with a username and user-specific
|
||||||
# Raises or returns privs.
|
# API key or password. Raises or returns privs.
|
||||||
return self.get_user_credentials(username, password, env)
|
return (username, self.get_user_credentials(username, password, env))
|
||||||
|
|
||||||
def get_user_credentials(self, email, pw, env):
|
def get_user_credentials(self, email, pw, env):
|
||||||
# Validate a user's credentials. On success returns a list of
|
# Validate a user's credentials. On success returns a list of
|
||||||
@ -87,6 +88,11 @@ class KeyAuthService:
|
|||||||
if email == "" or pw == "":
|
if email == "" or pw == "":
|
||||||
raise ValueError("Enter an email address and password.")
|
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
|
# Get the hashed password of the user. Raise a ValueError if the
|
||||||
# email address does not correspond to a user.
|
# email address does not correspond to a user.
|
||||||
pw_hash = get_mail_password(email, env)
|
pw_hash = get_mail_password(email, env)
|
||||||
@ -115,6 +121,9 @@ class KeyAuthService:
|
|||||||
# Return a list of privileges.
|
# Return a list of privileges.
|
||||||
return privs
|
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):
|
def _generate_key(self):
|
||||||
raw_key = os.urandom(32)
|
raw_key = os.urandom(32)
|
||||||
return base64.b64encode(raw_key).decode('ascii')
|
return base64.b64encode(raw_key).decode('ascii')
|
||||||
|
@ -31,7 +31,7 @@ def authorized_personnel_only(viewfunc):
|
|||||||
# Authenticate the passed credentials, which is either the API key or a username:password pair.
|
# Authenticate the passed credentials, which is either the API key or a username:password pair.
|
||||||
error = None
|
error = None
|
||||||
try:
|
try:
|
||||||
privs = auth_service.authenticate(request, env)
|
email, privs = auth_service.authenticate(request, env)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
# Authentication failed.
|
# Authentication failed.
|
||||||
privs = []
|
privs = []
|
||||||
@ -95,7 +95,7 @@ def index():
|
|||||||
def me():
|
def me():
|
||||||
# Is the caller authorized?
|
# Is the caller authorized?
|
||||||
try:
|
try:
|
||||||
privs = auth_service.authenticate(request, env)
|
email, privs = auth_service.authenticate(request, env)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return json_response({
|
return json_response({
|
||||||
"status": "invalid",
|
"status": "invalid",
|
||||||
@ -104,12 +104,13 @@ def me():
|
|||||||
|
|
||||||
resp = {
|
resp = {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
|
"email": email,
|
||||||
"privileges": privs,
|
"privileges": privs,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Is authorized as admin?
|
# Is authorized as admin? Return an API key for future use.
|
||||||
if "admin" in privs:
|
if "admin" in privs:
|
||||||
resp["api_key"] = auth_service.key
|
resp["api_key"] = auth_service.create_user_key(email)
|
||||||
|
|
||||||
# Return.
|
# Return.
|
||||||
return json_response(resp)
|
return json_response(resp)
|
||||||
|
@ -85,7 +85,7 @@ function do_login() {
|
|||||||
// Login succeeded.
|
// Login succeeded.
|
||||||
|
|
||||||
// Save the new credentials.
|
// Save the new credentials.
|
||||||
api_credentials = [response.api_key, ""];
|
api_credentials = [response.email, response.api_key];
|
||||||
|
|
||||||
// Try to wipe the username/password information.
|
// Try to wipe the username/password information.
|
||||||
$('#loginEmail').val('');
|
$('#loginEmail').val('');
|
||||||
|
Loading…
Reference in New Issue
Block a user