5555import cz .metacentrum .perun .core .api .exceptions .MfaInvalidRolesException ;
5656import cz .metacentrum .perun .core .api .exceptions .MfaPrivilegeException ;
5757import cz .metacentrum .perun .core .api .exceptions .MfaRolePrivilegeException ;
58+ import cz .metacentrum .perun .core .api .exceptions .MfaRoleTimeoutException ;
5859import cz .metacentrum .perun .core .api .exceptions .MfaTimeoutException ;
5960import cz .metacentrum .perun .core .api .exceptions .PolicyNotExistsException ;
6061import cz .metacentrum .perun .core .api .exceptions .ResourceNotExistsException ;
@@ -4272,7 +4273,11 @@ private static void checkMfaForHavingRole(PerunSession sess, AuthzRoles roles) {
42724273 }
42734274
42744275 if (!requireMfaRoles .isEmpty () && !sess .getPerunPrincipal ().getRoles ().hasRole (Role .MFA )) {
4275- throw new MfaRolePrivilegeException (sess , requireMfaRoles .get (0 ));
4276+ if (checkAuthValidityForMFA (sess )) {
4277+ throw new MfaRolePrivilegeException (sess , requireMfaRoles .get (0 ));
4278+ } else {
4279+ throw new MfaRoleTimeoutException ("Your MFA timestamp is not valid anymore, you'll need to reauthenticate" );
4280+ }
42764281 }
42774282 }
42784283
@@ -4368,6 +4373,27 @@ private static boolean isAuthorizedByMfa(PerunSession sess, boolean throwError)
43684373 return false ;
43694374 }
43704375
4376+ if (checkAuthValidityForMFA (sess )) {
4377+ // true if user has MFA and it is still valid
4378+ return sessionHasMfa (sess );
4379+ } else {
4380+ if (!throwError ) return false ;
4381+ if (sessionHasMfa (sess )) {
4382+ // MFA is no longer valid
4383+ throw new MfaTimeoutException ("Your MFA timestamp is not valid anymore, you'll need to reauthenticate" );
4384+ } else {
4385+ // user is authenticated by SFA but the mfa timeout would cause an error, so we need to reauthenticate this user
4386+ throw new MfaTimeoutException ("Your single factor authentication timestamp is not valid anymore, you'll need to reauthenticate" );
4387+ }
4388+ }
4389+ }
4390+
4391+ /**
4392+ * Check if the auth time + mfa timeout (reduced by percentage from config) > the current time
4393+ * @param sess session
4394+ * @return true if the auth timestamp is not too old to perform step-up
4395+ */
4396+ private static boolean checkAuthValidityForMFA (PerunSession sess ) {
43714397 String returnedAuthTime = sess .getPerunPrincipal ().getAdditionalInformations ().get (AUTH_TIME );
43724398 Instant parsedReturnedAuthTime ;
43734399 try {
@@ -4380,21 +4406,27 @@ private static boolean isAuthorizedByMfa(PerunSession sess, boolean throwError)
43804406 }
43814407
43824408 long mfaTimeoutInSec = Duration .ofMinutes (BeansUtils .getCoreConfig ().getMfaAuthTimeout ()).getSeconds ();
4383- Instant mfaValidUntil = parsedReturnedAuthTime .plusSeconds (mfaTimeoutInSec );
4384-
4385- // check if the auth time + mfa timeout > the current time
4386- if (mfaValidUntil .isAfter (Instant .now ())) {
4387- // if user has MFA and it is still valid
4388- return sess .getPerunPrincipal ().getAdditionalInformations ().containsKey (ACR_MFA );
4389- } else {
4390- if (!throwError ) return false ;
4391- if (sess .getPerunPrincipal ().getAdditionalInformations ().containsKey (ACR_MFA )) {
4392- // MFA is no longer valid
4393- throw new MfaTimeoutException ("Your MFA timestamp " + returnedAuthTime + " is not valid anymore, you'll need to reauthenticate" );
4394- } else {
4395- // user is authenticated by SFA but the mfa timeout would cause an error, so we need to reauthenticate this user
4396- throw new MfaTimeoutException ("Your single factor authentication timestamp " + returnedAuthTime + " is not valid anymore, you'll need to reauthenticate" );
4409+ double mfaTimeoutPercentage = 1 ;
4410+ // if the current session is SFA, we want to force log in with both factors earlier (e.g. 75% of mfaAuthTimeout) due to the first executed MFA since authentication time
4411+ // -> we want to avoid situation when the validity is e.g. 60 minutes, user executes MFA (just second factor) after 59 minutes and after one minute he/she would need to log in again with both factors
4412+ if (!sessionHasMfa (sess )) {
4413+ mfaTimeoutPercentage = (double ) BeansUtils .getCoreConfig ().getMfaAuthTimeoutPercentageForceLogIn () / 100 ;
4414+ if (mfaTimeoutPercentage < 0 || mfaTimeoutPercentage > 1 ) {
4415+ throw new InternalErrorException ("MFA auth timestamp percentage force logout " + mfaTimeoutPercentage + " is not between 0 and 100" );
43974416 }
43984417 }
4418+
4419+ Instant mfaValidUntil = parsedReturnedAuthTime .plusSeconds ((long ) (mfaTimeoutInSec * mfaTimeoutPercentage ));
4420+
4421+ return mfaValidUntil .isAfter (Instant .now ());
4422+ }
4423+
4424+ /**
4425+ * Check if the perun principal contains acr_mfa. It means that user has been authenticated by MFA.
4426+ * @param sess session
4427+ * @return true if principal contains acr_mfa
4428+ */
4429+ private static boolean sessionHasMfa (PerunSession sess ) {
4430+ return sess .getPerunPrincipal ().getAdditionalInformations ().containsKey (ACR_MFA );
43994431 }
44004432}
0 commit comments