From 050a535b834c54c0ff6bad2144c6d2cd55e0880f Mon Sep 17 00:00:00 2001
From: ale <ale@incal.net>
Date: Sun, 1 Jul 2018 09:10:19 +0100
Subject: [PATCH] Properly set the Has2FA bit when U2F is enabled

---
 backend/model.go | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)

diff --git a/backend/model.go b/backend/model.go
index 9a9fa95b..6d3cb457 100644
--- a/backend/model.go
+++ b/backend/model.go
@@ -99,14 +99,26 @@ func newLDAPBackendWithConn(conn ldapConn, base string) (*backend, error) {
 }
 
 func newUser(entry *ldap.Entry) (*accountserver.User, error) {
+	// Note that some user-level attributes related to
+	// authentication are stored on the uid= object, while others
+	// are on the email= object. We set the latter in the GetUser
+	// function later.
+	//
+	// The case of password recovery attributes is more complex:
+	// the current schema has those on email=, but we'd like to
+	// move them to uid=, so we currently have to support both.
 	user := &accountserver.User{
-		Name:   entry.GetAttributeValue("uid"),
-		Lang:   entry.GetAttributeValue(preferredLanguageLDAPAttr),
-		Has2FA: (entry.GetAttributeValue(totpSecretLDAPAttr) != ""),
-		//HasEncryptionKeys: (len(entry.GetAttributeValues("storageEncryptionKey")) > 0),
-		//PasswordRecoveryHint: entry.GetAttributeValue("recoverQuestion"),
-		U2FRegistrations: decodeU2FRegistrations(entry.GetAttributeValues(u2fRegistrationsLDAPAttr)),
+		Name:                 entry.GetAttributeValue("uid"),
+		Lang:                 entry.GetAttributeValue(preferredLanguageLDAPAttr),
+		PasswordRecoveryHint: entry.GetAttributeValue(recoveryHintLDAPAttr),
+		U2FRegistrations:     decodeU2FRegistrations(entry.GetAttributeValues(u2fRegistrationsLDAPAttr)),
 	}
+
+	// The user has 2FA enabled if it has a TOTP secret or U2F keys.
+	if (entry.GetAttributeValue(totpSecretLDAPAttr) != "") || (len(user.U2FRegistrations) > 0) {
+		user.Has2FA = true
+	}
+
 	if user.Lang == "" {
 		user.Lang = "en"
 	}
@@ -231,7 +243,9 @@ func (tx *backendTX) GetUser(ctx context.Context, username string) (*accountserv
 			// object, a shortcoming of the legacy A/I database model. Set
 			// them on the main User object.
 			if isObjectClass(entry, "virtualMailUser") {
-				user.PasswordRecoveryHint = entry.GetAttributeValue(recoveryHintLDAPAttr)
+				if s := entry.GetAttributeValue(recoveryHintLDAPAttr); s != "" {
+					user.PasswordRecoveryHint = s
+				}
 				user.AppSpecificPasswords = getASPInfo(decodeAppSpecificPasswords(entry.GetAttributeValues(aspLDAPAttr)))
 				user.HasEncryptionKeys = (entry.GetAttributeValue(storagePublicKeyLDAPAttr) != "")
 			}
@@ -277,6 +291,8 @@ func (tx *backendTX) GetUserRecoveryEncryptedPassword(ctx context.Context, user
 }
 
 func (tx *backendTX) SetPasswordRecoveryHint(ctx context.Context, user *accountserver.User, hint, response string) error {
+	// Write the password recovery attributes on the uid= object,
+	// as per the new schema.
 	dn := tx.getUserDN(user)
 	tx.setAttr(dn, recoveryHintLDAPAttr, hint)
 	tx.setAttr(dn, recoveryResponseLDAPAttr, response)
-- 
GitLab