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

Remove recovery codes for MFA #712

Closed
wants to merge 99 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
dfe23f0
Initial HTML and JS for enabling MFA on user account through dashboard
Nov 16, 2021
baf4fe8
Button and modal for editing MFA settings now appears
Nov 18, 2021
577f5cb
Adding modal for enabling authenticator app
Nov 19, 2021
0a58578
Adding models for IamTotpMfa and IamTotpRecoveryCode
Nov 19, 2021
6fd8ccc
Adding modal for disabling authenticator app
Nov 19, 2021
dccc0b6
Merge branch 'iam-spring-update-oct-2021' of git://github.com/indigo-…
Nov 22, 2021
1a08d05
WIP: Increase the Cookie duration
rmiccoli Sep 2, 2021
fb229df
Allow config option in test client app to avoid disclosing tokens
andreaceccanti Sep 10, 2021
02ac0d2
Dynamic downscoping in test client app
andreaceccanti Sep 11, 2021
a2dcabb
Bumped version to v1.7.1
andreaceccanti Sep 11, 2021
80b3140
v1.7.1 changelog
andreaceccanti Sep 11, 2021
855ee1e
More CHANGELOG tweaks
andreaceccanti Sep 11, 2021
8943042
Initial HTML and JS for enabling MFA on user account through dashboard
Nov 16, 2021
900662c
Button and modal for editing MFA settings now appears
Nov 18, 2021
dc787f8
Adding modal for enabling authenticator app
Nov 19, 2021
474b287
Adding models for IamTotpMfa and IamTotpRecoveryCode
Nov 19, 2021
c7dd5be
Adding modal for disabling authenticator app
Nov 19, 2021
d77143b
Unifying controllers, adding auth app service
Nov 22, 2021
fac0a90
Merge branch 'issue-418-mfa-support' of https://github.com/sam-glende…
Nov 22, 2021
67d80a1
Syncing branch with origin
Nov 22, 2021
af13c9c
Merging changes for updating Spring boot and Java version. Squashed c…
Nov 22, 2021
e6e8b26
Making database schema changes for MySQL and H2
Nov 23, 2021
84c8434
Ignoring .factorypath autogenerated file
Nov 23, 2021
ccbc081
Backend for adding TOTP secret to a user
Nov 24, 2021
409adb4
Merge branch 'iam-spring-update-oct-2021' into issue-418-mfa-support
Nov 25, 2021
47baf5f
Merge branch 'iam-spring-update-oct-2021' of git://github.com/indigo-…
Nov 25, 2021
70469af
Merge branch 'iam-spring-update-oct-2021' into issue-418-mfa-support
Nov 25, 2021
ae8b887
Removing non-existent files
Nov 25, 2021
1be4737
MFA secret and QR code now display on authenticator app enabling modal
Nov 25, 2021
06f4ace
Dynamic mfa settings menu, toaster notifications on successful operation
Nov 26, 2021
3fb7e55
Adding license to files, adding basic input detection for MFA modals
Nov 26, 2021
ea1a536
Verification code now sends through post successfully
Nov 29, 2021
5b23238
Adding TOTP validation for enabling and disabling authenticator app MFA
Nov 29, 2021
e89f821
Autowiring QR generator and code verifier into AuthenticatorAppContro…
Nov 29, 2021
c4be542
Database migrations for totp_mfa now in separate file
Nov 30, 2021
dea8ff5
Adding tests for enabling and disabling TOTP MFA through IamAccountSe…
Nov 30, 2021
b794327
Adding tests for MFA settings and auth app controllers
Dec 1, 2021
0a0557c
Adding ROLE_PRE_AUTHENTICATED
Dec 2, 2021
b1dc5fe
Adding UUID for IamTotpMfa, fixing migration version numbers
Dec 2, 2021
cc77a0b
Removing UUID from IamTotpMfa due to being unnecessary
Dec 2, 2021
27c0e65
Adding java docs for files in iam-login-service
Dec 3, 2021
81b536f
Adding basic mfa login procedure
Dec 6, 2021
f0de8a8
Merge branch 'indigo-iam:iam-spring-update-oct-2021' into iam-spring-…
Dec 6, 2021
fafd97e
Merge branch 'iam-spring-update-oct-2021' into issue-418-mfa-support
Dec 6, 2021
c9feb7c
Improving TOTP input screen post-login, fixing Java 17 Docker upgrade…
Dec 7, 2021
b05cce2
Adding error handling for MFA failure, fixing CSS bugs
Dec 8, 2021
f67d9b7
Separating MFA verification controllers, adding "Back to login" button
Dec 8, 2021
82af215
Adding mechanism for looping through available factors of authenticat…
Dec 9, 2021
3479f43
Adding recovery code login procedure, backend for regenerating recove…
Dec 10, 2021
915c6a9
Fixing css bug that broke the btn-login class
Dec 13, 2021
6a13643
Fixing css bugs with MFA verify button
Dec 13, 2021
e13a169
Adding recovery code regeneration after use and webpage navigation
Dec 13, 2021
ad2b1ad
Adding ability to view and reset recovery codes
Dec 14, 2021
d378875
Fixing error message tests in AuthenticatorAppSettingsTests
Dec 14, 2021
a0d900d
Fixing enabling and disabing totpMfa tests for IamAccount
Dec 15, 2021
79e5248
Adding tests for MfaVerify and AuthenticatorAppVerify controllers
Dec 16, 2021
ffa735a
Adding licence header
Dec 16, 2021
4741225
Adding tests for recovery code management controller endpoints
Dec 17, 2021
7c997a0
Adding tests on account service for adding recovery codes, fixing clo…
Dec 17, 2021
48b89c2
Fixing submit button bug on dashboard modals
Jan 6, 2022
b018fe3
Recovery codes display to the user on MFA enabling
Jan 6, 2022
8a33dc8
Adding amr claim in OIDC id_token
Jan 17, 2022
4efb960
Removing unnecessary database migrations, extending authentication ob…
Jan 20, 2022
6ac8b54
Fixing login bugs with MfaAuthenticationToken, extending authenticati…
Jan 21, 2022
54842ff
Renaming MfaAuthenticationToken to ExtendedAuthenticationToken
Jan 21, 2022
2a03fbb
Renaming MfaAuthenticationFilter to ExtendedAuthenticationFilter
Jan 21, 2022
2c946de
Moving db migrations to correct directory
Jan 24, 2022
98d3c73
Removing unneeded method to regenerate recovery codes
Jan 24, 2022
f095d71
Adding creation time and update time to IamTotpMfa
Jan 24, 2022
f2af341
Fixing migrations from IamTotpMfa creation and update time fields
Jan 27, 2022
a7098a6
Removing YubiKey option and all references in code
Jan 27, 2022
143dfe1
Demonstrating reading of amr from request parameters to /token
Feb 3, 2022
a062373
Authentication method reference claim now successfully appears in id_…
Feb 17, 2022
666037b
OAuth2 client login flow now works for MFA users
Feb 24, 2022
11cbc2c
Fixing login credential failure handling
Feb 25, 2022
dc01d78
Fixing TOTP verification process
Feb 25, 2022
3553f26
Merging base branch and fixing conflicts
Feb 28, 2022
a231f5e
Adding recovery code verification to login flow
Mar 4, 2022
e34f016
Merge branch mfa-db-migrations into issue-418-mfa-support
Mar 4, 2022
bc39b71
Merge branch 'v1.8.0' into issue-418-mfa-support
Mar 4, 2022
6513b09
Adding comments, fixing assigned authorities bug
Mar 8, 2022
d9b461e
Merge branch 'issue-418-mfa-support' of https://github.com/sam-glende…
Mar 8, 2022
e6750ec
Fixing license header in verify JSP
Mar 8, 2022
566b1f3
Fixing existing MFA tests, improving controller functionality
Mar 10, 2022
958082c
Adding license header for IamTotpMfaServiceTests
Mar 10, 2022
469ac8e
Fixing bugs with MFA verification
Mar 11, 2022
6f5ceb4
Merge remote-tracking branch 'sam/issue-418-mfa-support' into issue-418
rmiccoli Oct 19, 2023
13f27b4
Fix MFA settings view
rmiccoli Oct 19, 2023
bd9764d
Fix tests
rmiccoli Oct 23, 2023
bd69d32
Fix some test
federicaagostini Oct 24, 2023
47c7246
Fix other tests
rmiccoli Oct 24, 2023
d39fa6f
Update Spring Security OAuth2 Client dependency
rmiccoli Oct 27, 2023
5314ec9
Add download recovery codes button
Sae126V Nov 21, 2023
447f1be
Delete recovery codes from the interface
rmiccoli Feb 9, 2024
bcc4e29
Remove recovery codes generator and verifier
rmiccoli Feb 12, 2024
4f026bb
Fix test
rmiccoli Feb 12, 2024
9e31402
Try to fix infinite recursion problem
rmiccoli Feb 12, 2024
fb7be0b
Add jackson dependency
rmiccoli Feb 13, 2024
82f6943
Implement the Serializable interface
rmiccoli Feb 13, 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
Prev Previous commit
Next Next commit
Authentication method reference claim now successfully appears in id_…
…token for non-MFA users

This required extending the HttpServletRequest to include additional information and a filter to determine when to do this. This filter has to execute after authentication has taken place so that the ExtendedAuthenticationToken can be retrieved from the security context.

This currently only works for non-MFA users, as the current implementation redirects all MFA users to the dashboard after authenticating (demo purposes).
  • Loading branch information
Sam Glendenning committed Feb 17, 2022
commit a062373751e56a2cede4af5b130991277b96ba2f
5 changes: 5 additions & 0 deletions iam-login-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@
<artifactId>spring-security-oauth2</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
Expand All @@ -29,10 +27,19 @@

import it.infn.mw.iam.core.ExtendedAuthenticationToken;

/**
* This replaces the default {@code UsernamePasswordAuthenticationFilter}. It is used to store a new
* {@code ExtendedAuthenticationToken} into the security context instead of a
* {@code UsernamePasswordAuthenticationToken}.
*
* <p>
* Ultimately, we want to store information about the methods of authentication used for every login
* attempt. This is useful for registered clients, who may wish to restrict access to certain users
* based on the type or quantity of authentication methods used. The authentication methods are
* passed to the OAuth2 authorization endpoint and stored in the id_token returned to the client.
*/
public class ExtendedAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

public static final Logger LOG = LoggerFactory.getLogger(ExtendedAuthenticationFilter.class);

private boolean postOnly = true;
private AuthenticationManager authenticationManager;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.infn.mw.iam.authn.multi_factor_authentication;

import static it.infn.mw.iam.authn.multi_factor_authentication.IamAuthenticationMethodReference.AUTHENTICATION_METHOD_REFERENCE_CLAIM_STRING;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
* Represents an extended {@code HttpServletRequest} object. This is primarily used for including
* information in an OAuth2 authorization request about the authentication method(s) used by the
* user to sign in. These are ultimately passed to the token endpoint so they may be included in the
* id_token received by the client.
*/
public final class ExtendedHttpServletRequest extends HttpServletRequestWrapper {

private final Map<String, String[]> queryParameterMap;
private final Charset requestEncoding;

public ExtendedHttpServletRequest(HttpServletRequest request, String amrClaim) {
super(request);
Map<String, String[]> queryMap = getCommonQueryParamFromLegacy(request.getParameterMap());
queryMap.put(AUTHENTICATION_METHOD_REFERENCE_CLAIM_STRING, new String[] {amrClaim});
queryParameterMap = Collections.unmodifiableMap(queryMap);

String encoding = request.getCharacterEncoding();
requestEncoding = (encoding != null ? Charset.forName(encoding) : StandardCharsets.UTF_8);
}

private final Map<String, String[]> getCommonQueryParamFromLegacy(
Map<String, String[]> paramMap) {
Objects.requireNonNull(paramMap);

Map<String, String[]> commonQueryParamMap = new LinkedHashMap<>(paramMap);

return commonQueryParamMap;
}

@Override
public String getParameter(String name) {
String[] params = queryParameterMap.get(name);
return params != null ? params[0] : null;
}

@Override
public String[] getParameterValues(String name) {
return queryParameterMap.get(name);
}

@Override
public Map<String, String[]> getParameterMap() {
return queryParameterMap; // unmodifiable to uphold the interface contract.
}

@Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(queryParameterMap.keySet());
}

@Override
public String getQueryString() {
// @see : https://stackoverflow.com/a/35831692/9869013
// return queryParameterMap.entrySet().stream().flatMap(entry ->
// Stream.of(entry.getValue()).map(value -> entry.getKey() + "=" +
// value)).collect(Collectors.joining("&")); // without encoding !!
return queryParameterMap.entrySet()
.stream()
.flatMap(entry -> encodeMultiParameter(entry.getKey(), entry.getValue(), requestEncoding))
.collect(Collectors.joining("&"));
}

private Stream<String> encodeMultiParameter(String key, String[] values, Charset encoding) {
return Stream.of(values).map(value -> encodeSingleParameter(key, value, encoding));
}

private String encodeSingleParameter(String key, String value, Charset encoding) {
return urlEncode(key, encoding) + "=" + urlEncode(value, encoding);
}

private String urlEncode(String value, Charset encoding) {
try {
return URLEncoder.encode(value, encoding.name());
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Cannot url encode " + value, e);
}
}

@Override
public ServletInputStream getInputStream() throws IOException {
throw new UnsupportedOperationException("getInputStream() is not implemented in this "
+ HttpServletRequest.class.getSimpleName() + " wrapper");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.infn.mw.iam.authn.multi_factor_authentication;

import java.io.IOException;
import java.util.Iterator;
import java.util.Set;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import it.infn.mw.iam.core.ExtendedAuthenticationToken;

/**
* This filter is applied after authentication has taken place. It is used in the OAuth2 process to
* detect if a set of {@code IamAuthenticationMethodReference} objects are included in the current
* {@code Authentication} object. If so, these are passed to an {@code ExtendedHttpServletRequest}
* so they may be included in the authorization request and passed to OAuth2 clients.
*/
public class ExtendedHttpServletRequestFilter extends GenericFilterBean {

public static final String AUTHORIZATION_REQUEST_INCLUDES_AMR =
"AUTHORIZATION_REQUEST_INCLUDES_AMR";

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

// We fetch the ExtendedAuthenticationToken from the security context. This contains the
// authentication method references we want to include in the authorization request
Authentication auth = SecurityContextHolder.getContext().getAuthentication();

// Checking to see if this filter has been applied already (if so, this attribute will have
// already been set)
Object amrAttribute = request.getAttribute(AUTHORIZATION_REQUEST_INCLUDES_AMR);

if (amrAttribute == null && auth instanceof ExtendedAuthenticationToken) {
Set<IamAuthenticationMethodReference> amrSet =
((ExtendedAuthenticationToken) auth).getAuthenticationMethodReferences();
String amrClaim = parseAuthenticationMethodReferences(amrSet);

ExtendedHttpServletRequest extendedRequest =
new ExtendedHttpServletRequest((HttpServletRequest) request, amrClaim);

extendedRequest.setAttribute(AUTHORIZATION_REQUEST_INCLUDES_AMR, Boolean.TRUE);
request = extendedRequest;
}

chain.doFilter(request, response);
}

/**
* Convert a set of authentication method references into a request parameter string Values are
* separated with a + symbol
*
* @param amrSet the set of authentication method references
* @return the parsed string
*/
private String parseAuthenticationMethodReferences(Set<IamAuthenticationMethodReference> amrSet) {
String amrClaim = "";
Iterator<IamAuthenticationMethodReference> it = amrSet.iterator();
while (it.hasNext()) {
IamAuthenticationMethodReference current = it.next();
amrClaim += current.getName() + "+";
}

// Remove trailing + symbol at end of string
amrClaim = amrClaim.substring(0, amrClaim.length() - 1);
return amrClaim;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@

public class IamAuthenticationMethodReference {

public static final String AUTHENTICATION_METHOD_REFERENCE_CLAIM_STRING = "amr";

public enum AuthenticationMethodReferenceValues {
PASSWORD("pwd"), ONE_TIME_PASSWORD("otp"), HARDWARE_KEY("hwk");
// Add additional values here if new authentication factors get added, e.g. HARDWARE_KEY("hwk")
// Consult here for standardised reference values -
// https://datatracker.ietf.org/doc/html/rfc8176

PASSWORD("pwd"), ONE_TIME_PASSWORD("otp");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import it.infn.mw.iam.authn.MultiFactorAuthenticationSuccessHandler;
import it.infn.mw.iam.authn.RootIsDashboardSuccessHandler;
import it.infn.mw.iam.authn.multi_factor_authentication.ExtendedAuthenticationFilter;
import it.infn.mw.iam.authn.multi_factor_authentication.ExtendedHttpServletRequestFilter;
import it.infn.mw.iam.authn.multi_factor_authentication.MultiFactorAuthenticationProvider;
import it.infn.mw.iam.authn.oidc.OidcAuthenticationProvider;
import it.infn.mw.iam.authn.oidc.OidcClientFilter;
Expand Down Expand Up @@ -181,6 +182,7 @@ protected void configure(final HttpSecurity http) throws Exception {
.and()
.addFilterAt(new ExtendedAuthenticationFilter(this.authenticationManager(), successHandler()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(authorizationRequestFilter, SecurityContextPersistenceFilter.class)
.addFilterAfter(new ExtendedHttpServletRequestFilter(), UsernamePasswordAuthenticationFilter.class)
.logout()
.logoutUrl("/logout")
.and().anonymous()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,6 @@ public TokenRequest createTokenRequest(Map<String, String> requestParameters,
}
}

// These should come from the incoming request anyway
// Ideally will come in application/x-www-form-urlencoded format ?amr=pwd&amr=otp
// Adding in here for demo purposes at the moment
// Need to figure out how to add this to the request from the /authorize endpoint
requestParameters.put("amr", "pwd,otp");

String grantType = requestParameters.get(OAuth2Utils.GRANT_TYPE);

Set<String> scopes = OAuth2Utils.parseParameterList(requestParameters.get(OAuth2Utils.SCOPE));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package it.infn.mw.iam.core.oauth.profile.iam;

import static it.infn.mw.iam.core.oauth.profile.iam.ClaimValueHelper.ADDITIONAL_CLAIMS;
import static it.infn.mw.iam.authn.multi_factor_authentication.IamAuthenticationMethodReference.AUTHENTICATION_METHOD_REFERENCE_CLAIM_STRING;

import java.util.Set;

Expand Down Expand Up @@ -64,10 +65,11 @@ public void customizeIdTokenClaims(Builder idClaims, ClientDetailsEntity client,
.forEach(c -> idClaims.claim(c, claimValueHelper.getClaimValueFromUserInfo(c, info)));

// Add the methods of authentication to the id_token
String amrParam = request.getRequestParameters().get("amr");
String amrParam =
request.getRequestParameters().get(AUTHENTICATION_METHOD_REFERENCE_CLAIM_STRING);
if (amrParam != null) {
String[] amrArr = amrParam.split(",");
idClaims.claim("amr", amrArr);
String[] amrArr = amrParam.split("\\+");
idClaims.claim(AUTHENTICATION_METHOD_REFERENCE_CLAIM_STRING, amrArr);
}

includeLabelsInIdToken(idClaims, client, request, account, accessToken);
Expand Down
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

<mitreid.version>1.3.5.cnaf-spring-update-SNAPSHOT</mitreid.version>
<spring-security-oauth2.version>2.5.1.RELEASE</spring-security-oauth2.version>
<spring-security-oauth2-client.version>5.6.1</spring-security-oauth2-client.version>

<voms.version>3.3.2</voms.version>
<spring-security-saml.version>1.0.10.RELEASE</spring-security-saml.version>
Expand Down Expand Up @@ -249,6 +250,12 @@
<version>${jaxb-runtime.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>${spring-security-oauth2-client.version}</version>
</dependency>

</dependencies>

</dependencyManagement>
Expand Down