Skip to content

Commit

Permalink
2FA seems to be workinggit statusgit status
Browse files Browse the repository at this point in the history
  • Loading branch information
amontenegro committed Feb 12, 2025
1 parent 6846137 commit 46d2e2c
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 88 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.orcid.authorization.authentication;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

public class MFAAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, MFAWebAuthenticationDetails> {

@Override
public MFAWebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new MFAWebAuthenticationDetails(context);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.orcid.authorization.authentication;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.thrift.TSerializable;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

import java.io.Serializable;
import java.util.Objects;

public class MFAWebAuthenticationDetails implements Serializable {

public static final String VERIFICATION_CODE_PARAMETER = "verificationCode";

public static final String RECOVERY_CODE_PARAMETER = "recoveryCode";

private final String verificationCode;

private final String recoveryCode;

private final String remoteAddress;

private final String sessionId;

public MFAWebAuthenticationDetails(HttpServletRequest request) {
verificationCode = getParameterOrAttribute(request, VERIFICATION_CODE_PARAMETER);
recoveryCode = getParameterOrAttribute(request, RECOVERY_CODE_PARAMETER);
remoteAddress = request.getRemoteAddr();
HttpSession session = request.getSession(false);
sessionId = session != null ? session.getId() : null;
}

public MFAWebAuthenticationDetails(String remoteAddress, String sessionId, String verificationCode, String recoveryCode) {
this.verificationCode = verificationCode;
this.recoveryCode = recoveryCode;
this.remoteAddress = remoteAddress;
this.sessionId = sessionId;
}

private String getParameterOrAttribute(HttpServletRequest request, String name) {
String value = request.getParameter(name);
if (value == null) {
value = (String) request.getAttribute(name);
}
return value;
}

public String getVerificationCode() {
return verificationCode;
}

public String getRecoveryCode() {
return recoveryCode;
}

public String getRemoteAddress() {
return remoteAddress;
}

public String getSessionId() {
return sessionId;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MFAWebAuthenticationDetails that = (MFAWebAuthenticationDetails) o;
return Objects.equals(verificationCode, that.verificationCode) && Objects.equals(recoveryCode, that.recoveryCode) && Objects.equals(remoteAddress, that.remoteAddress) && Objects.equals(sessionId, that.sessionId);
}

@Override
public int hashCode() {
return Objects.hash(verificationCode, recoveryCode, remoteAddress, sessionId);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import java.time.Instant;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.orcid.authorization.authentication.MFAWebAuthenticationDetails;
import org.orcid.core.manager.BackupCodeManager;
import org.orcid.core.manager.ProfileEntityCacheManager;
import org.orcid.core.manager.TwoFactorAuthenticationManager;
Expand Down Expand Up @@ -130,13 +130,13 @@ public Authentication authenticate(Authentication auth) throws AuthenticationExc
}

if (profile.getUsing2FA()) {
String recoveryCode = ((OrcidWebAuthenticationDetails) auth.getDetails()).getRecoveryCode();
String recoveryCode = ((MFAWebAuthenticationDetails) auth.getDetails()).getRecoveryCode();
if (recoveryCode != null && !recoveryCode.isEmpty()) {
if (!backupCodeManager.verify(profile.getId(), recoveryCode)) {
throw new Bad2FARecoveryCodeException();
}
} else {
String verificationCode = ((OrcidWebAuthenticationDetails) auth.getDetails()).getVerificationCode();
String verificationCode = ((MFAWebAuthenticationDetails) auth.getDetails()).getVerificationCode();
if (verificationCode == null || verificationCode.isEmpty()) {
throw new VerificationCodeFor2FARequiredException();
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.orcid.authorization.authentication.MFAWebAuthenticationDetails;
import org.orcid.frontend.web.util.MFAWebAuthenticationDetailsDeserializer;
import org.orcid.frontend.web.util.SwitchUserGrantedAuthorityDeserializer;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.annotation.Bean;
Expand Down Expand Up @@ -34,6 +36,7 @@ private ObjectMapper objectMapper() {
mapper.registerModules(new CoreJackson2Module());
mapper.addMixIn(String[].class, StringArrayMixin.class);
mapper.addMixIn(SwitchUserGrantedAuthority.class, SwitchUserGrantedAuthorityMixin.class);
mapper.addMixIn(MFAWebAuthenticationDetails.class, MFAWebAuthenticationDetailsMixin.class);
return mapper;
}

Expand Down Expand Up @@ -67,4 +70,15 @@ public SwitchUserGrantedAuthorityMixin(@JsonProperty("authority") String role, @
}

}

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,
getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
@JsonDeserialize(using = MFAWebAuthenticationDetailsDeserializer.class)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class MFAWebAuthenticationDetailsMixin {
@JsonCreator
MFAWebAuthenticationDetailsMixin(@JsonProperty("remoteAddress") String remoteAddress, @JsonProperty("sessionId") String sessionId, @JsonProperty("verificationCode") String verificationCode, @JsonProperty("recoveryCode") String recoveryCode) {
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package org.orcid.frontend.spring.configuration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.orcid.core.manager.impl.OrcidUrlManager;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Service("orcidRequestCache")
public class OrcidRequestCache extends HttpSessionRequestCache {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,14 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.data.redis.RedisSessionRepository;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.session.web.http.SessionRepositoryFilter;

import javax.servlet.ServletContext;
import java.time.Duration;

@Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,17 @@ public class OrcidRedisIndexedSessionRepository implements FindByIndexNameSessio
private FlushMode flushMode;
private SaveMode saveMode;
private final String PUBLIC_ORCID_PAGE_REGEX = "/(\\d{4}-){3,}\\d{3}[\\dX](/.+)";
private final List<String> urisToSkip = List.of("/2FA/status.json", "/account/", "/account/biographyForm.json", "/account/countryForm.json", "/account/delegates.json", "/account/emails.json",
private final List<String> urisToSkipOnGet = List.of("/2FA/status.json", "/account/", "/account/biographyForm.json", "/account/countryForm.json", "/account/delegates.json", "/account/emails.json",
"/account/get-trusted-orgs.json", "/account/nameForm.json", "/account/preferences.json", "/account/socialAccounts.json", "/affiliations/affiliationDetails.json", "/affiliations/affiliationGroups.json",
"/assets/vectors/orcid.logo.icon.svg", "/config.json", "/delegators/delegators-and-me.json", "/fundings/fundingDetails.json", "/fundings/fundingGroups.json", "/inbox/notifications.json",
"/inbox/totalCount.json", "/inbox/unreadCount.json", "/my-orcid/externalIdentifiers.json", "/my-orcid/keywordsForms.json", "/my-orcid/otherNamesForms.json", "/my-orcid/websitesForms.json",
"/ng-cli-ws", "/not-found", "/notifications/frequencies/view", "/orgs/disambiguated/FUNDREF", "/orgs/disambiguated/GRID", "/orgs/disambiguated/LEI", "/orgs/disambiguated/RINGGOLD",
"/orgs/disambiguated/ROR", "/peer-reviews/peer-review.json", "/peer-reviews/peer-reviews-by-group-id.json", "/peer-reviews/peer-reviews-minimized.json", "/qr-code.png",
"/research-resources/researchResource.json", "/research-resources/researchResourcePage.json", "/works/getWorkInfo.json", "/works/groupingSuggestions.json", "/works/idTypes.json", "/works/work.json",
"/works/worksExtendedPage.json");
private final Set<String> SKIP_SAVE_SESSION = new HashSet<>(urisToSkip);
private final List<String> urisToSkipAlways = List.of("/oauth/custom/register/validatePassword.json");
private final Set<String> GET_SKIP_SAVE_SESSION = new HashSet<>(urisToSkipOnGet);
private final Set<String> ALWAYS_SKIP_SAVE_SESSION = new HashSet<>(urisToSkipAlways);

public OrcidRedisIndexedSessionRepository(RedisOperations<Object, Object> sessionRedisOperations) {
this.flushMode = FlushMode.ON_SAVE;
Expand Down Expand Up @@ -363,11 +365,10 @@ private BoundHashOperations<Object, Object, Object> getSessionBoundHashOperation
private boolean updateSession() {
ServletRequestAttributes att = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = att.getRequest();
if(request.getMethod().equals("GET")) {
String url = request.getRequestURI().substring(request.getContextPath().length());
if(SKIP_SAVE_SESSION.contains(url) || url.matches(PUBLIC_ORCID_PAGE_REGEX)) {
return false;
}
String url = request.getRequestURI().substring(request.getContextPath().length());
if((request.getMethod().equals("GET") && (GET_SKIP_SAVE_SESSION.contains(url) || url.matches(PUBLIC_ORCID_PAGE_REGEX)))
|| ALWAYS_SKIP_SAVE_SESSION.contains(url)) {
return false;
}
return true;
}
Expand Down Expand Up @@ -409,7 +410,7 @@ public void setLastAccessedTime(Instant lastAccessedTime) {
// TODO: REMOVE THIS BEFORE GOING LIVE!!!!
ServletRequestAttributes att = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = att.getRequest();
System.out.println("REDIS_SESSION: setLastAccessedTime: " + request.getRequestURI().toString() + " - " + request.getMethod());
logger.info("REDIS_SESSION: setLastAccessedTime: " + request.getRequestURI().toString() + " - " + request.getMethod());

this.cached.setLastAccessedTime(lastAccessedTime);
this.delta.put("lastAccessedTime", this.getLastAccessedTime().toEpochMilli());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import org.orcid.core.manager.v3.read_only.RecordNameManagerReadOnly;
import org.orcid.core.oauth.service.OrcidAuthorizationEndpoint;
import org.orcid.core.oauth.service.OrcidOAuth2RequestValidator;
import org.orcid.frontend.spring.OrcidWebAuthenticationDetails;
import org.orcid.authorization.authentication.MFAWebAuthenticationDetails;
import org.orcid.persistence.jpa.entities.ClientDetailsEntity;
import org.orcid.pojo.ajaxForm.PojoUtil;
import org.orcid.pojo.ajaxForm.RequestInfoForm;
Expand Down Expand Up @@ -139,7 +139,7 @@ protected void copy(Map<String, String[]> savedParams, Map<String, String> param
****************************/
protected Authentication authenticateUser(HttpServletRequest request, String email, String password) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(email, password);
token.setDetails(new OrcidWebAuthenticationDetails(request));
token.setDetails(new MFAWebAuthenticationDetails(request));
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import org.orcid.core.manager.v3.ProfileEntityManager;
import org.orcid.core.security.UnclaimedProfileExistsException;
import org.orcid.core.utils.OrcidRequestUtil;
import org.orcid.frontend.spring.OrcidWebAuthenticationDetails;
import org.orcid.authorization.authentication.MFAWebAuthenticationDetails;
import org.orcid.frontend.web.controllers.helper.OauthHelper;
import org.orcid.frontend.web.exception.Bad2FARecoveryCodeException;
import org.orcid.frontend.web.exception.Bad2FAVerificationCodeException;
Expand Down Expand Up @@ -195,11 +195,11 @@ public ModelAndView loginGetHandler(HttpServletRequest request, HttpServletRespo

private void copy2FAFields(OauthAuthorizeForm form, HttpServletRequest request) {
if (form.getVerificationCode() != null) {
request.setAttribute(OrcidWebAuthenticationDetails.VERIFICATION_CODE_PARAMETER, form.getVerificationCode().getValue());
request.setAttribute(MFAWebAuthenticationDetails.VERIFICATION_CODE_PARAMETER, form.getVerificationCode().getValue());
}

if (form.getRecoveryCode() != null) {
request.setAttribute(OrcidWebAuthenticationDetails.RECOVERY_CODE_PARAMETER, form.getRecoveryCode().getValue());
request.setAttribute(MFAWebAuthenticationDetails.RECOVERY_CODE_PARAMETER, form.getRecoveryCode().getValue());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.orcid.frontend.web.util;

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.orcid.authorization.authentication.MFAWebAuthenticationDetails;

import java.io.IOException;

public class MFAWebAuthenticationDetailsDeserializer extends JsonDeserializer<MFAWebAuthenticationDetails> {
@Override
public MFAWebAuthenticationDetails deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
JsonNode jsonNode = mapper.readTree(jsonParser);
JsonNode verificationCodeNode = jsonNode.get("verificationCode");
JsonNode recoveryCodeNode = jsonNode.get("recoveryCode");
JsonNode remoteAddressNode = jsonNode.get("remoteAddress");
JsonNode sessionIdNode = jsonNode.get("sessionId");

String verificationCode = (verificationCodeNode != null && verificationCodeNode.isTextual()) ? verificationCodeNode.asText() : null;
String recoveryCode = (recoveryCodeNode != null && recoveryCodeNode.isTextual()) ? recoveryCodeNode.asText() : null;
String remoteAddress = (remoteAddressNode != null && remoteAddressNode.isTextual()) ? remoteAddressNode.asText() : null;
String sessionId = (sessionIdNode != null && sessionIdNode.isTextual()) ? sessionIdNode.asText() : null;

return new MFAWebAuthenticationDetails(remoteAddress, sessionId, verificationCode, recoveryCode);
}
}
2 changes: 1 addition & 1 deletion orcid-web/src/main/resources/orcid-frontend-security.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
class="org.orcid.frontend.spring.AjaxAuthenticationFailureHandler" />

<bean id="authenticationDetailsSource"
class="org.orcid.frontend.spring.OrcidAuthenticationDetailsSource" />
class="org.orcid.authorization.authentication.MFAAuthenticationDetailsSource" />

<sec:authentication-manager id="authenticationManager">
<sec:authentication-provider ref="authenticationProvider" />
Expand Down

0 comments on commit 46d2e2c

Please sign in to comment.