/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
|
||||
|
||||
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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('');
|
||||
|
|
Loading…
Reference in New Issue