1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2026-03-05 15:57:23 +01:00

Merge remote-tracking branch 'fspoettel/admin-panel-2fa' into totp

# Conflicts:
#	management/auth.py
#	management/daemon.py
#	management/mailconfig.py
#	setup/mail-users.sh
This commit is contained in:
downtownallday
2020-09-28 23:25:16 -04:00
21 changed files with 602 additions and 366 deletions

View File

@@ -575,7 +575,7 @@ apply_access_control() {
# service accounts (except management):
# can bind but not change passwords, including their own
# can read all attributes of all users but not userPassword,
# totpSecret, or totpMruToken
# totpSecret, totpMruToken, or totpLabel
# can read config subtree (permitted-senders, domains)
# no access to services subtree, except their own dn
# management service account:
@@ -584,8 +584,8 @@ apply_access_control() {
# users:
# can bind and change their own password
# can read and change their own shadowLastChange
# cannot read or modify totpSecret, totpMruToken
# can read attributess of other users except mailaccess, totpSecret, totpMruToken
# cannot read or modify totpSecret, totpMruToken, totpLabel
# can read attributess of other users except mailaccess, totpSecret, totpMruToken, totpLabel
# no access to config subtree
# no access to services subtree
#
@@ -607,7 +607,7 @@ olcAccess: to attrs=userPassword
by self =wx
by anonymous auth
by * none
olcAccess: to attrs=totpSecret,totpMruToken
olcAccess: to attrs=totpSecret,totpMruToken,totpLabel
by dn.exact="cn=management,${LDAP_SERVICES_BASE}" write
by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
by * none

View File

@@ -17,7 +17,6 @@ source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars
source ${STORAGE_ROOT}/ldap/miab_ldap.conf # user-data specific vars
# ### User Authentication
# Have Dovecot query our database, and not system users, for authentication.

View File

@@ -183,9 +183,11 @@ def migration_12(env):
conn.close()
def migration_13(env):
# Add a table for `totp_credentials`
# Add the "mfa" table for configuring MFA for login to the control panel.
db = os.path.join(env["STORAGE_ROOT"], 'mail/users.sqlite')
shell("check_call", ["sqlite3", db, "CREATE TABLE IF NOT EXISTS totp_credentials (id INTEGER PRIMARY KEY AUTOINCREMENT, user_email TEXT NOT NULL UNIQUE, secret TEXT NOT NULL, mru_token TEXT, FOREIGN KEY (user_email) REFERENCES users(email) ON DELETE CASCADE);"])
shell("check_call", ["sqlite3", db, "CREATE TABLE mfa (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL UNIQUE, type TEXT NOT NULL, secret TEXT NOT NULL, mru_token TEXT, label TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE);"])
###########################################################
def migration_miabldap_1(env):
@@ -352,7 +354,8 @@ def run_miabldap_migrations():
print(e)
print()
print("Your system may be in an inconsistent state now. We're terribly sorry. A re-install from a backup might be the best way to continue.")
sys.exit(1)
#sys.exit(1)
raise e
ourver = next_ver

View File

@@ -8,7 +8,7 @@
import uuid, os, sqlite3, ldap3, hashlib
def add_user(env, ldapconn, search_base, users_base, domains_base, email, password, privs, totp_secret, totp_mru_token, cn=None):
def add_user(env, ldapconn, search_base, users_base, domains_base, email, password, privs, totp, cn=None):
# Add a sqlite user to ldap
# env are the environment variables
# ldapconn is the bound ldap connection
@@ -18,8 +18,7 @@ def add_user(env, ldapconn, search_base, users_base, domains_base, email, passwo
# email is the user's email
# password is the user's current sqlite password hash
# privs is an array of privilege names for the user
# totp_secret is the TOTP secret or None
# totp_mru_token is the TOP most recently used token or None
# totp contains the list of secrets, mru tokens, and labels
# cn is the user's common name [optional]
#
# the email address should be as-is from sqlite (encoded as
@@ -77,11 +76,11 @@ def add_user(env, ldapconn, search_base, users_base, domains_base, email, passwo
attrs["sn"] = cn[cn.find(' ')+1:]
# add TOTP, if enabled
if totp_secret:
if totp:
objectClasses.append('totpUser')
attrs['totpSecret'] = totp_secret
if totp_mru_token:
attrs['totpMruToken'] = totp_mru_token
attrs['totpSecret'] = totp["secret"]
attrs['totpMruToken'] = totp["mru_token"]
attrs['totpLabel'] = totp["label"]
# Add user
dn = "uid=%s,%s" % (uid, users_base)
@@ -105,23 +104,33 @@ def create_users(env, conn, ldapconn, ldap_base, ldap_users_base, ldap_domains_b
# iterate through sqlite 'users' table and create each user in
# ldap. returns a map of email->dn
try:
c = conn.cursor()
c.execute("select users.email, users.password, users.privileges, totp_credentials.secret, totp_credentials.mru_token from users left join totp_credentials on users.email = totp_credentials.user_email")
except:
# old version of miab
c = conn.cursor()
c.execute("SELECT email, password, privileges, NULL as secret, NULL as mru_token from users")
# select users
c = conn.cursor()
c.execute("SELECT id, email, password, privileges from users")
users = {}
for row in c:
email=row[0]
password=row[1]
privs=row[2]
totp_secret=row[3]
totp_mru_token=row[4]
dn = add_user(env, ldapconn, ldap_base, ldap_users_base, ldap_domains_base, email, password, privs.split("\n"), totp_secret, totp_mru_token)
user_id=row[0]
email=row[1]
password=row[2]
privs=row[3]
totp = None
c2 = conn.cursor()
c2.execute("SELECT secret, mru_token, label from mfa where user_id=? and type='totp'", (user_id,));
rowidx = 0
for row2 in c2:
if totp is None:
totp = {
"secret": [],
"mru_token": [],
"label": []
}
totp["secret"].append("{%s}%s" % (rowidx, row2[0]))
totp["mru_token"].append("{%s}%s" % (rowidx, row2[1] or ''))
totp["label"].append("{%s}%s" % (rowidx, row2[2] or ''))
dn = add_user(env, ldapconn, ldap_base, ldap_users_base, ldap_domains_base, email, password, privs.split("\n"), totp)
users[email] = dn
return users