major
#29092
Multi-factor authentication and self-service account management
Overview
Add platform-level support for multi-factor authentication (MFA), self-service account management, and invitation-based self-registration to the TopLogic framework.
Proposed New Modules
| Module | Artifact | Description |
| com.top_logic.security.auth.totp | tl-security-auth-totp | TOTP second-factor device (RFC 6238) |
| com.top_logic.security.otp | tl-security-otp | One-time password email verification service |
| com.top_logic.security.selfservice | tl-security-selfservice | Password and TOTP self-service reset flows |
| com.top_logic.security.invite | tl-security-invite | Invitation tokens and self-registration framework |
Changes to Existing Code (tl-core)
- New SecondFactorDevice interface alongside existing AuthenticationDevice
- New secondFactorDeviceID attribute on Person
- Extended LoginPageServlet / Login for two-step MFA login flow
- New SecondFactorDevice accessor in TLSecurityDeviceManager
Specification
See specs/multi-factor-auth-spec.md in the repository for the full specification document.
Motivation
Applications increasingly require:
- Two-factor authentication (password + TOTP) for users without external identity providers
- Self-service password and TOTP reset without administrator intervention
- Invitation-based self-registration for external users
Migration
The new modules (tl-security-auth-totp, tl-security-otp, tl-security-selfservice, tl-security-invite) are optional dependencies and need no action. The login mechanism of the core, however, was rewritten for the two-step flow, and applications that customised the login page, extend the account management classes or configure authentication devices have to adapt.
Login page replaced by an in-app dialog
LoginPageServlet (mapping /servlet/login), login.jsp, login.banner.inc, doOnload.inc, jsp/main/loginError.jsp, jsp/util/administration/changePwd.jsp and script/tl/loginError.js are deleted. A request without a session now gets an anonymous session, and the layout offers the LoginViewDialog through the new <login-hooks> of MainLayout$GlobalConfig (framework default: OpenLoginDialogHook).
- Delete application overlays of the removed JSPs and includes, and web.xml entries for LoginPageServlet. Bookmarks and monitoring probes pointing to /login.jsp or /servlet/login must use /servlet/LayoutServlet.
- Remove login, loginRetryPage, loginErrorPage and changePassword from an ApplicationPages$Config override; the properties no longer exist (unknown property = startup error). loginPage, logoutPage and loginRetrySSO default to /servlet/LayoutServlet.
- Login-messages addon: LoginMessagesMainLayout and its GlobalConfig are replaced by the login hook LoginMessagesHook. Remove a MAIN_LAYOUT_CLASS theme setting naming the old class and the LoginMessagesMainLayout$GlobalConfig block; the addon's loginMessagesConf.config.xml registers the hook:
{{{#!xml <config config:interface="com.top_logic.mig.html.layout.MainLayout$GlobalConfig">
<login-hooks>
<login-hook class="com.top_logic.addons.loginmessages.layout.LoginMessagesHook" showLoginMessages="true"/>
</login-hooks>
</config> }}}
- Resource keys: tl.logout is now class.com.top_logic.layout.component.configuration.I18NConstants.LOGOUT; layouts.admin.persons.changePassword.* moved to class.com.top_logic.knowledge.gui.layout.person.I18NConstants.CHANGE_PASSWORD_FORM.*; MAX_USERS_EXCEEDED is gone.
- A session may now belong to the anonymous account (PersonManager.getAnonymous(), TLContext.isAnonymous()), and Person.all() contains that account. Commands that must not be offered to anonymous users use the executability rule AnonymousAccountDisabled.
Java API
- Login.login(userName, request, response) is replaced by Login.checkUserPassword(userName, char[] password, request, response), and Login.login(request, response, credentials) by Login.checkLoginCredentials(credentials, request, response). Both only check the credentials; the caller creates the session with SessionService.getInstance().loginUser(request, response, person). MaxUsersExceededException is replaced by LoginHookFailedException. LoginCredentials is no longer AutoCloseable: call clearPassword() in a finally block.
- ExternalAuthenticationServlet (base class of SSO servlets) extends NoContextServlet instead of LoginPageServlet: forwardPage(...) is forwardToPage(...), forwardToStartPage(...) is redirectToStartPage(...), checkRequest(...) and forwardToTarget(...) cannot be overridden any more, and forwardToSSOLoginFailed(...) declares IOException, ServletException. Subclasses that only implement retrieveLoginCredentials(...) need no change. LoginPageServlet.appendCustomParameters(...) and PARAM_START_PAGE moved to AbstractTopLogicServlet.
- TLPersonManager is deleted: configure and extend com.top_logic.knowledge.wrap.person.PersonManager (as ContactPersonManager does; its Config is no longer generic). PersonManager extends KBBasedManagedClass, so subclasses call super(context, config). Replacement for getAllAliveFullPersons(): Person.all().stream().filter(Person.FULL_USER_FILTER).
- Person.create(kb, name, String deviceId) is Person.create(kb, name, AuthenticationDevice device).
- AuthenticationDevice.getMFARequirement() is a new abstract method: custom devices implement it (e.g. returning MfaRequirement.DISABLED).
- FormMember.setLabel(ResKey), setTooltip(ResKey) and setTooltipCaption(ResKey) were added next to the String variants; a call with a literal null is ambiguous and must be cast (setLabel((String) null)).
- ListStorage.listConfig(...), SetStorage.setConfig(...) and SingletonLinkStorage.singletonLinkConfig(...) take a trailing boolean unversioned (pass false for the previous behaviour).
- The deprecated MetaElementUtil.getAllInstancesOf(TLClass) and getAllDirectInstancesOf(TLClass) are removed; use the overloads with the expected class (getAllInstancesOf(type, Wrapper.class)).
- Base32 is renamed to EncodeTypable (same static method names).
Configuration
- mfa-requirement is mandatory on DBAuthenticationAccessDevice.Config and LDAPAuthenticationAccessDevice.Config. The core sets optional for dbSecurity and %LDAP_MFA_REQUIREMENT% in ldapConf.config.xml; an application declaring its own <security-device> of one of these classes (or a copied ldapConf) must add mfa-requirement="optional|required|disabled".
- Applications that maintain their own <modules> list must enable LoginFailuresModule$Module.
- The migration Ticket_29092_multi_factor_authentication (tl-element) adds Person#mfaSecret and Person#mfaRequirement automatically. The tl-layout-formeditor migration of the same name replaces the form definition annotation of tl.accounts:Person: an in-app customisation of the account form is lost and must be re-applied after the update.