Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: email-based authentication codes for MFA/2FA #19008

Merged
merged 88 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 86 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
117d117
feat: email-based authentication codes for MFA/2FA
netroms Nov 1, 2024
488b24c
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 4, 2024
7a1304d
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 4, 2024
e3e923a
feat: email-based authentication codes for MFA/2FA
netroms Nov 4, 2024
12f2bb8
feat: email-based authentication codes for MFA/2FA
netroms Nov 4, 2024
d1331ef
feat: email-based authentication codes for MFA/2FA
netroms Nov 4, 2024
cdc4234
feat: email-based authentication codes for MFA/2FA
netroms Nov 4, 2024
e12819d
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 4, 2024
8512ce0
feat: email-based authentication codes for MFA/2FA
netroms Nov 4, 2024
7ba0daf
feat: email-based authentication codes for MFA/2FA
netroms Nov 4, 2024
48b510b
feat: email-based authentication codes for MFA/2FA
netroms Nov 5, 2024
65c3541
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 5, 2024
661bcab
feat: email-based authentication codes for MFA/2FA
netroms Nov 5, 2024
2c482f6
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 6, 2024
86b3d33
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 6, 2024
0f6e7e5
feat: email-based authentication codes for MFA/2FA
netroms Nov 6, 2024
924ca40
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 14, 2024
809abd2
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 15, 2024
72e26bb
feat: email-based authentication codes for MFA/2FA
netroms Nov 18, 2024
f2b1acd
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 18, 2024
9ba3e45
feat: email-based authentication codes for MFA/2FA
netroms Nov 19, 2024
7452165
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 19, 2024
547a270
feat: email-based authentication codes for MFA/2FA
netroms Nov 19, 2024
2b55d4b
feat: email-based authentication codes for MFA/2FA
netroms Nov 19, 2024
772cef3
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 19, 2024
0ec1698
feat: email-based authentication codes for MFA/2FA
netroms Nov 19, 2024
f0cef75
feat: email-based authentication codes for MFA/2FA
netroms Nov 19, 2024
3e9267e
feat: email-based authentication codes for MFA/2FA
netroms Nov 20, 2024
6dac6df
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 21, 2024
77ff287
feat: email-based authentication codes for MFA/2FA
netroms Nov 22, 2024
044d158
feat: email-based authentication codes for MFA/2FA
netroms Nov 22, 2024
e5463e3
feat: email-based authentication codes for MFA/2FA
netroms Nov 22, 2024
d4ddafd
feat: email-based authentication codes for MFA/2FA
netroms Nov 22, 2024
644e5b5
feat: email-based authentication codes for MFA/2FA
netroms Nov 22, 2024
4141d4b
feat: email-based authentication codes for MFA/2FA
netroms Nov 22, 2024
a1898c1
feat: email-based authentication codes for MFA/2FA
netroms Nov 25, 2024
bc48fd3
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 25, 2024
79c27fa
feat: email-based authentication codes for MFA/2FA
netroms Nov 25, 2024
d551766
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 25, 2024
79ded5b
feat: email-based authentication codes for MFA/2FA
netroms Nov 25, 2024
bb28129
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 25, 2024
78994c8
feat: email-based authentication codes for MFA/2FA
netroms Nov 25, 2024
b1ec97d
feat: email-based authentication codes for MFA/2FA
netroms Nov 25, 2024
3913daa
feat: email-based authentication codes for MFA/2FA
netroms Nov 25, 2024
f260fd8
feat: email-based authentication codes for MFA/2FA
netroms Nov 26, 2024
77bebde
feat: email-based authentication codes for MFA/2FA
netroms Nov 26, 2024
0d3e80b
feat: email-based authentication codes for MFA/2FA
netroms Nov 26, 2024
7bdbb3a
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 26, 2024
42bbb68
feat: email-based authentication codes for MFA/2FA
netroms Nov 26, 2024
863feb5
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
d4c621c
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 27, 2024
7bf1e2f
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
c1916b2
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
c4bdb52
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 27, 2024
18c45f1
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
eec00cd
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
444b372
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 27, 2024
42f0adb
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
e40718f
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
35f5bab
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
89dcdc2
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
1b43056
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Nov 27, 2024
681e39d
feat: email-based authentication codes for MFA/2FA
netroms Nov 27, 2024
607c910
feat: email-based authentication codes for MFA/2FA
netroms Dec 2, 2024
ae2f970
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Dec 2, 2024
e3aae1d
feat: email-based authentication codes for MFA/2FA
netroms Dec 2, 2024
894db0d
feat: email-based authentication codes for MFA/2FA
netroms Dec 2, 2024
e581ca1
feat: email-based authentication codes for MFA/2FA
netroms Dec 2, 2024
2e5dac6
feat: email-based authentication codes for MFA/2FA
netroms Dec 2, 2024
9a0ac8c
feat: email-based authentication codes for MFA/2FA
netroms Dec 3, 2024
ec389c2
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Dec 3, 2024
7048dcc
feat: email-based authentication codes for MFA/2FA
netroms Dec 3, 2024
0da7839
feat: email-based authentication codes for MFA/2FA
netroms Dec 4, 2024
7ea96a3
feat: email-based authentication codes for MFA/2FA
netroms Dec 4, 2024
2701cd2
feat: email-based authentication codes for MFA/2FA
netroms Dec 4, 2024
4d05836
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Dec 4, 2024
5f1d327
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Dec 5, 2024
5153fb5
feat: email-based authentication codes for MFA/2FA
netroms Dec 5, 2024
e6c811d
feat: email-based authentication codes for MFA/2FA
netroms Dec 5, 2024
06c720b
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Dec 5, 2024
93f6a57
feat: email-based authentication codes for MFA/2FA
netroms Dec 5, 2024
01b4d63
feat: email-based authentication codes for MFA/2FA
netroms Dec 5, 2024
7b5863c
feat: email-based authentication codes for MFA/2FA
netroms Dec 6, 2024
090f543
feat: email-based authentication codes for MFA/2FA
netroms Dec 6, 2024
62bd809
Merge branch 'master' of github.com:dhis2/dhis2-core into DHIS2-13334_2
netroms Dec 6, 2024
57ba93e
feat: email-based authentication codes for MFA/2FA
netroms Dec 6, 2024
b167d2a
feat: email-based authentication codes for MFA/2FA
netroms Dec 9, 2024
7c33223
feat: email-based authentication codes for MFA/2FA
netroms Dec 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,28 @@ public static char[] generateSecureRandomCode(int codeSize) {
return generateRandomAlphanumericCode(codeSize, sr);
}

public static byte[] generateSecureRandomBytes(int length) {
SecureRandom sr = SecureRandomHolder.GENERATOR;
byte[] bytes = new byte[length];
sr.nextBytes(bytes);
return bytes;
}

/**
* Generates a string of random numeric characters.
*
* @param length the number of characters in the code.
* @return the code.
*/
public static char[] generateSecureRandomNumber(int length) {
netroms marked this conversation as resolved.
Show resolved Hide resolved
char[] digits = new char[length];
SecureRandom sr = SecureRandomHolder.GENERATOR;
for (int i = 0; i < length; i++) {
digits[i] = (char) ('0' + sr.nextInt(10));
}
return digits;
}

/**
* Generates a random secure token.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,21 +199,31 @@ public enum ErrorCode {
E3020(
"You must have permissions to create user, or ability to manage at least one user group for the user"),
E3021("Not allowed to disable 2FA for current user"),
E3022("User has two factor authentication enabled, disable 2FA before you create a new QR code"),
E3022("User has 2FA enabled already, disable 2FA before you try to enroll again"),
E3023("Invalid 2FA code"),
E3024("Not allowed to disable 2FA"),
E3025("No current user"),
E3026("Could not generate QR code"),
E3027("No currentUser available"),
E3028("User must have a secret"),
E3029("User must call the /qrCode endpoint first"),
E3028("User must have a 2FA secret"),
E3029("User must start 2FA enrollment first"),
E3030(
"User cannot update their own user's 2FA settings via this API endpoint, must use /2fa/enable or disable API"),
E3031("Two factor authentication is not enabled"),
"User cannot update their own user's 2FA settings via this API endpoint, must use /2fa/enable or /2fa/disable API"),
E3031("User has not enabled 2FA"),
E3032("User `{0}` does not have access to user role"),
E3040("Could not resolve JwsAlgorithm from the JWK. Can not write a valid JWKSet"),
E3041("User `{0}` is not allowed to change a user having the ALL authority"),
E3042("Too many failed disable attempts. Please try again later"),
E3043(
"User does not have a verified email, please verify your email before you try to enable 2FA"),
E3044("TOTP 2FA is not enabled"),
E3045("Email based 2FA is not enabled in the system settings"),
E3046("TOTP 2FA is not enabled in the system settings"),
E3047("User is not in TOTP 2FA enrollment mode"),
E3048("User does not have email 2FA enabled"),
E3049("Sending 2FA code with email failed"),
E3050("2FA code can not be null or empty"),
E3051("2FA code was sent to the user's email"),

/* Metadata Validation */
E4000("Missing required property `{0}`"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2004-2024, University of Oslo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of the HISP project nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hisp.dhis.security.twofa;

import lombok.Getter;

@Getter
public enum TwoFactorType {
NOT_ENABLED,
TOTP_ENABLED,
EMAIL_ENABLED,
ENROLLING_TOTP, // User is in the process of enrolling in TOTP 2FA
ENROLLING_EMAIL; // User is in the process of enrolling in email-based 2FA

public boolean isEnrolling() {
return this == ENROLLING_TOTP || this == ENROLLING_EMAIL;
}

public TwoFactorType getEnabledType() {
if (this == ENROLLING_TOTP) {
return TOTP_ENABLED;
} else if (this == ENROLLING_EMAIL) {
return EMAIL_ENABLED;
} else {
return this;
}
}

public boolean isEnabled() {
return this == TOTP_ENABLED || this == EMAIL_ENABLED;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,14 @@ default String getGlobalShellAppName() {
return asString("globalShellAppName", "global-app-shell");
}

default boolean getEmail2FAEnabled() {
return asBoolean("email2FAEnabled", false);
}

default boolean getTOTP2FAEnabled() {
return asBoolean("totp2FAEnabled", true);
}

/**
* @return true if email verification is enforced for all users.
*/
Expand Down
11 changes: 11 additions & 0 deletions dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/SystemUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import javax.annotation.Nonnull;
import org.hisp.dhis.common.CodeGenerator;
import org.hisp.dhis.security.Authorities;
import org.hisp.dhis.security.twofa.TwoFactorType;
import org.springframework.security.core.GrantedAuthority;

/**
Expand Down Expand Up @@ -88,6 +89,11 @@ public boolean isSuper() {
return true;
}

@Override
public String getSecret() {
return "";
}

@Override
public String getUid() {
return "XXXXXSystem";
Expand Down Expand Up @@ -184,6 +190,11 @@ public boolean isTwoFactorEnabled() {
return false;
}

@Override
public TwoFactorType getTwoFactorType() {
return TwoFactorType.NOT_ENABLED;
}

@Override
public boolean isEmailVerified() {
return true;
Expand Down
19 changes: 14 additions & 5 deletions dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import org.hisp.dhis.schema.annotation.Property;
import org.hisp.dhis.schema.annotation.PropertyRange;
import org.hisp.dhis.security.Authorities;
import org.hisp.dhis.security.twofa.TwoFactorType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

Expand All @@ -72,7 +73,6 @@
*/
@JacksonXmlRootElement(localName = "user", namespace = DxfNamespaces.DXF_2_0)
public class User extends BaseIdentifiableObject implements MetadataObject {
public static final int USERNAME_MAX_LENGTH = 255;

/** Globally unique identifier for User. */
private UUID uuid;
Expand All @@ -95,9 +95,10 @@ public class User extends BaseIdentifiableObject implements MetadataObject {
/** Required. Will be stored as a hash. */
private String password;

/** Required. Automatically set in constructor */
private String secret;

private TwoFactorType twoFactorType;

/** Date when password was changed. */
private Date passwordLastUpdated;

Expand Down Expand Up @@ -440,10 +441,9 @@ public void setPassword(String password) {
this.password = password;
}

@JsonProperty
@JacksonXmlProperty(namespace = DxfNamespaces.DXF_2_0)
@JsonIgnore
public boolean isTwoFactorEnabled() {
return this.secret != null && !this.secret.isEmpty();
return this.twoFactorType != null && this.twoFactorType.isEnabled();
}

@JsonIgnore
Expand All @@ -455,6 +455,15 @@ public void setSecret(String secret) {
this.secret = secret;
}

@JsonIgnore
public TwoFactorType getTwoFactorType() {
return this.twoFactorType == null ? TwoFactorType.NOT_ENABLED : this.twoFactorType;
}

public void setTwoFactorType(TwoFactorType twoFactorType) {
this.twoFactorType = twoFactorType;
}

@JsonProperty
@JacksonXmlProperty(namespace = DxfNamespaces.DXF_2_0)
public boolean isExternalAuth() {
Expand Down
15 changes: 11 additions & 4 deletions dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,19 @@
import org.hisp.dhis.common.IdentifiableObject;
import org.hisp.dhis.common.UidObject;
import org.hisp.dhis.security.Authorities;
import org.hisp.dhis.security.twofa.TwoFactorType;
import org.hisp.dhis.user.UserDetailsImpl.UserDetailsImplBuilder;
import org.springframework.security.core.GrantedAuthority;

public interface UserDetails
extends org.springframework.security.core.userdetails.UserDetails, UidObject {

// TODO MAS: This is a workaround and usually indicated a design flaw, and that we should refactor
// to use UserDetails higher up in the layers.

/**
* Create UserDetails from User
*
* <p>TODO MAS: This is a workaround and usually indicated a design flaw, and that we should
* refactor // to use UserDetails higher up in the layers.
*
* @param user user to convert
* @return UserDetails
*/
Expand Down Expand Up @@ -115,12 +116,14 @@ static UserDetails createUserDetails(
UserDetailsImpl.builder()
.id(user.getId())
.uid(user.getUid())
.code(user.getCode())
.username(user.getUsername())
.password(user.getPassword())
.externalAuth(user.isExternalAuth())
.isTwoFactorEnabled(user.isTwoFactorEnabled())
.twoFactorType(user.getTwoFactorType())
.secret(user.getSecret())
.isEmailVerified(user.isEmailVerified())
.code(user.getCode())
.firstName(user.getFirstName())
.surname(user.getSurname())
.enabled(user.isEnabled())
Expand Down Expand Up @@ -199,6 +202,8 @@ static UserDetails createUserDetails(

boolean isSuper();

String getSecret();

@Override
String getUid();

Expand Down Expand Up @@ -245,6 +250,8 @@ static UserDetails createUserDetails(

boolean isTwoFactorEnabled();

TwoFactorType getTwoFactorType();

boolean isEmailVerified();

boolean hasAnyRestrictions(Collection<String> restrictions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hisp.dhis.security.Authorities;
import org.hisp.dhis.security.twofa.TwoFactorType;
import org.springframework.security.core.GrantedAuthority;

@Getter
Expand All @@ -53,6 +54,8 @@
private final String password;
private final boolean externalAuth;
private final boolean isTwoFactorEnabled;
private final TwoFactorType twoFactorType;

Check notice

Code scanning / CodeQL

Missing Override annotation Note

This method overrides
UserDetails.getTwoFactorType
; it is advisable to add an Override annotation.
private final String secret;

Check notice

Code scanning / CodeQL

Missing Override annotation Note

This method overrides
UserDetails.getSecret
; it is advisable to add an Override annotation.
private final boolean isEmailVerified;
private final boolean enabled;
private final boolean accountNonExpired;
Expand Down
Loading
Loading