diff --git a/perun-oidc-server-webapp/src/main/resources/localization/messages_cs.properties b/perun-oidc-server-webapp/src/main/resources/localization/messages_cs.properties
index 3ef721f53..dc80a800c 100644
--- a/perun-oidc-server-webapp/src/main/resources/localization/messages_cs.properties
+++ b/perun-oidc-server-webapp/src/main/resources/localization/messages_cs.properties
@@ -75,10 +75,6 @@ contact_p=V p\u0159\u00EDpad\u011B nejasnost\u00ED n\u00E1s kontaktujte na
403_informationPage=Pro v\u00EDce informac\u00ED o slu\u017Eb\u011B nav\u0161tivte
403_contactSupport=Pokud si mysl\u00EDte \u017Ee m\u00E1te m\u00EDt p\u0159\u00EDstup, kontaktujte administr\u00E1tora:
403_subject=Probl\u00E9m s p\u0159ihl\u00E1\u0161en\u00EDm do slu\u017Eby
-403_isCesnetEligible_notSet_hdr=P\u0159\u00EDstup zam\u00EDtnut
-403_isCesnetEligible_notSet_msg=P\u0159\u00EDstup ke slu\u017Eb\u011B zam\u00EDtnut, proto\u017Ee V\u00E1\u0161 \u00FA\u010Det nen\u00ED z \u010Desk\u00E9 akademick\u00E9 instituce. P\u0159ihlaste se, pros\u00EDm, pomoc\u00ED sv\u00E9ho \u00FA\u010Dtu u akademick\u00E9 instituce.
Znovu p\u0159ihl\u00E1sit
-403_isCesnetEligible_expired_hdr=P\u0159\u00EDstup zam\u00EDtnut
-403_isCesnetEligible_expired_msg=P\u0159\u00EDstup ke slu\u017Eb\u011B zam\u00EDtnut, proto\u017Ee plynula doba 12 m\u011Bs\u00EDc\u016F od Va\u0161eho posledn\u00EDho p\u0159ihl\u00E1\u0161en\u00ED \u00FA\u010Dtem z \u010Desk\u00E9 akademick\u00E9 instituce. P\u0159ihlaste se, pros\u00EDm, pomoc\u00ED sv\u00E9ho \u00FA\u010Dtu u akademick\u00E9 instituce.
Znovu p\u0159ihl\u00E1sit
403_ensure_vo_hdr=P\u0159\u00EDstup zam\u00EDtnut
403_ensure_vo_msg=Nem\u00E1te dostate\u010Dn\u00E1 pr\u00E1va pro p\u0159\u00EDstup ke slu\u017Eb\u011B
403_authorization_hdr=P\u0159\u00EDstup zam\u00EDtnut
@@ -92,6 +88,11 @@ contact_p=V p\u0159\u00EDpad\u011B nejasnost\u00ED n\u00E1s kontaktujte na
403_not_logged_in_hdr=P\u0159\u00EDstup zam\u00EDtnut
403_not_logged_in_msg=Zd\u00E1 se, \u017Ee p\u0159ihl\u00E1\u0161en\u00ED selhalo. Zkuste, pros\u00EDm, zav\u0159\u00EDt V\u00E1\u0161 prohl\u00ED\u017Ee\u010D a p\u0159ihl\u00E1sit se znovu.
+403_is_eligible_default_header_text=P\u0159\u00EDstup zam\u00EDtnut
+403_is_eligible_default_text=P\u0159\u00EDstup ke slu\u017Eb\u011B byl zam\u00EDtnut, proto\u017Ee V\u00E1\u0161 \u00FA\u010Det nespl\u0148uje pomd\u00EDnky p\u0159\u00EDstupu. P\u0159ihlaste se, pros\u00EDme, pomoc\u00ED jin\u00E9ho \u00FA\u010Dtu.
+403_is_eligible_default_button_text=Pokra\u010Dovat
+403_is_eligible_default_contact_text=Pokud si mysl\u00EDte, \u017Ee pou\u017E\u00EDv\u00E1te spr\u00E1vn\u00FD \u00FA\u010Det a p\u0159\u00EDstup je V\u00E1m odm\u00EDtnut nepr\u00E1vem, pros\u00EDme kontakujte n\u00E1s na
+
#GO TO REGISTRATION
go_to_registration_title=Je vy\u017Eadov\u00E1na Va\u0161e aktivita
go_to_registration_header1=Pro p\u0159\u00EDstup ke slu\u017Eb\u011B
diff --git a/perun-oidc-server-webapp/src/main/resources/localization/messages_en.properties b/perun-oidc-server-webapp/src/main/resources/localization/messages_en.properties
index 057597046..9ee693a3e 100644
--- a/perun-oidc-server-webapp/src/main/resources/localization/messages_en.properties
+++ b/perun-oidc-server-webapp/src/main/resources/localization/messages_en.properties
@@ -74,10 +74,6 @@ contact_p=In case of any questions, do not hesitate to contact us at
403_informationPage=For more information about this service please visit this
403_contactSupport=If you think you should have an access contact service operator at
403_subject=Problem with login to service:
-403_isCesnetEligible_notSet_hdr=Access denied
-403_isCesnetEligible_notSet_msg=Your account is not from Czech academic institution. Please log in with your account from academic institution.Log in again
-403_isCesnetEligible_expired_hdr=Access denied
-403_isCesnetEligible_expired_msg=Your last login, from Czech academic institution, has been registered 12 months ago. Please sign in with your account from academic institution.Log in again
403_ensure_vo_hdr=Access denied
403_ensure_vo_msg=You don't meet the prerequisites to access the service.
403_authorization_hdr=Access denied
@@ -91,6 +87,11 @@ contact_p=In case of any questions, do not hesitate to contact us at
403_not_logged_in_hdr=Access denied
403_not_logged_in_msg=It appears the login process has failed. Please close your browser and try to log in again.
+403_is_eligible_default_header_text=Access denied
+403_is_eligible_default_text=Your account does not meet the criteria for accessing the service. Please log in with other account.
+403_is_eligible_default_button_text=Continue with other account.
+403_is_eligible_default_contact_text=If you think you have used an account which meets the criteria, and you are still prevented from logging in to the service, please contact us at
+
#GO TO REGISTRATION
go_to_registration_title=Your activity is necessary
go_to_registration_header1=Your activity is necessary to access the
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/lsaai/unapproved_is_eligible.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/lsaai/unapproved_is_eligible.jsp
new file mode 100644
index 000000000..e44920617
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/lsaai/unapproved_is_eligible.jsp
@@ -0,0 +1,21 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
+<%@ taglib prefix="ls" tagdir="/WEB-INF/tags/lsaai" %>
+
+
+
+
+
+
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/unapproved_is_eligible.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/unapproved_is_eligible.jsp
new file mode 100644
index 000000000..02dc5db2a
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/unapproved_is_eligible.jsp
@@ -0,0 +1,35 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
+
+<%
+
+List cssLinks = new ArrayList<>();
+
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+
+
+ <%-- header --%>
+
+
+
+
+
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/web-context.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/web-context.xml
index cec77325e..f7792e795 100644
--- a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/web-context.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/web-context.xml
@@ -65,7 +65,7 @@
-
+
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimUtils.java
index 9c4b41d97..423d23104 100644
--- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimUtils.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimUtils.java
@@ -65,6 +65,23 @@ public class ClaimUtils {
}
}
+ public static int fillIntegerPropertyOrDefaultVal(String suffix, ClaimSourceInitContext ctx, int defaultVal) {
+ return fillIntegerPropertyOrDefaultVal(ctx.getProperty(suffix, NO_VALUE), defaultVal);
+ }
+
+ private static int fillIntegerPropertyOrDefaultVal(String prop, int defaultVal) {
+ if (StringUtils.hasText(prop)) {
+ try {
+ return Integer.parseInt(prop);
+ } catch (NumberFormatException e) {
+ log.warn("Caught {}", e.getClass().getSimpleName(), e);
+ return defaultVal;
+ }
+ } else {
+ return defaultVal;
+ }
+ }
+
public static ArrayNode listToArrayNode(List list) {
ArrayNode res = JsonNodeFactory.instance.arrayNode();
if (list != null && !list.isEmpty()) {
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/IsEligibleClaimSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/IsEligibleClaimSource.java
new file mode 100644
index 000000000..ed81b184c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/IsEligibleClaimSource.java
@@ -0,0 +1,98 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import cz.muni.ics.oauth2.model.SamlAuthenticationDetails;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import cz.muni.ics.oidc.server.claims.ClaimUtils;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Collections;
+import java.util.Set;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+
+/**
+ * This source checks if the timestamp is within defined months subtracted from the current timestamp.
+ * If so, returns TRUE, FALSE otherwise.
+ *
+ * Configuration (replace [claimName] with the name of the claim):
+ *
+ * - custom.claim.[claimName].source.attribute - attribute containing the eligibility last seen timestamp passed via SAML authentication
+ * - custom.claim.[claimName].source.validityPeriodMonths - amount of months that we subtract from the current timestamp and compare it with the eligibility timestamp. If not provided, default 12 months is used
+ *
+ *
+ * @author Pavol Pluta
+ * @author Dominik Frantisek Bucik
+ */
+@Slf4j
+public class IsEligibleClaimSource extends ClaimSource {
+
+ private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss";
+ private static final int DEFAULT_VALIDITY_PERIOD = 12; // 12 months
+
+ private static final String SOURCE_ATTR_NAME = "attribute";
+ private static final String VALIDITY_PERIOD_MONTHS = "validityPeriodMonths";
+
+ private final String sourceAttr;
+ private final int validityPeriodMonths;
+
+ public IsEligibleClaimSource(ClaimSourceInitContext ctx) {
+ super(ctx);
+
+ this.sourceAttr = ClaimUtils.fillStringMandatoryProperty(SOURCE_ATTR_NAME, ctx, getClaimName());
+ this.validityPeriodMonths = ClaimUtils.fillIntegerPropertyOrDefaultVal(VALIDITY_PERIOD_MONTHS, ctx, DEFAULT_VALIDITY_PERIOD);
+ log.debug("{} - SAML attribute: '{}', validity period in months: '{}'", getClaimName(), sourceAttr, validityPeriodMonths);
+ }
+
+ @Override
+ public Set getAttrIdentifiers() {
+ return Collections.singleton(sourceAttr);
+ }
+
+ @Override
+ public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+ SamlAuthenticationDetails details = pctx.getSamlAuthenticationDetails();
+ if (details == null || details.getAttributes() == null || details.getAttributes().isEmpty()) {
+ log.warn("{} - no attribute set to get the source attribute from, returning FALSE", getClaimName());
+ return JsonNodeFactory.instance.booleanNode(false);
+ }
+ String[] attrValue = details.getAttributes().getOrDefault(sourceAttr, new String[] {});
+ if (attrValue == null || attrValue.length == 0) {
+ log.warn("{} - no attribute to construct value from, returning FALSE", getClaimName());
+ return JsonNodeFactory.instance.booleanNode(false);
+ } else {
+ if (attrValue.length > 1) {
+ log.warn("{} - configured source attribute '{}' has more than one value, will use just the first one", getClaimName(), sourceAttr);
+ }
+ String timestamp = attrValue[0];
+ return JsonNodeFactory.instance.booleanNode(isEligible(timestamp));
+ }
+ }
+
+ private boolean isEligible(String eligibleLastSeenValue) {
+ if (!StringUtils.hasText(eligibleLastSeenValue)) {
+ return false;
+ }
+ LocalDate eligibleLastSeenTimestamp;
+ try {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(TIMESTAMP_FORMAT);
+ eligibleLastSeenTimestamp = LocalDate.parse(eligibleLastSeenValue, formatter);
+ } catch (DateTimeParseException e) {
+ log.warn("{} - could not parse value '{}' as timestamp in format '{}'. Returning NOT ELIGIBLE.",
+ getClaimName(), eligibleLastSeenValue, TIMESTAMP_FORMAT);
+ return false;
+ }
+
+ LocalDate now = LocalDateTime.now().toLocalDate();
+ boolean isValid = !eligibleLastSeenTimestamp.isBefore(now.minusMonths(validityPeriodMonths));
+ log.debug("{} - timestamp '{}' is time {} the defined period of '{} months'",
+ getClaimName(), eligibleLastSeenTimestamp, isValid ? "within" : "out of", DEFAULT_VALIDITY_PERIOD);
+ return isValid;
+ }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java
index cad8be75d..adc2cc6c0 100644
--- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java
@@ -357,15 +357,23 @@ public class FiltersUtils {
public static String fillStringMandatoryProperty(String propertyName,
String filterName,
AuthProcFilterInitContext params) {
- String filled = params.getProperty(propertyName);
+ String filled = fillStringProperty(propertyName, params, null);
- if (!StringUtils.hasText(filled)) {
+ if (filled == null) {
throw new IllegalArgumentException("No value configured for '" + propertyName + "' in filter " + filterName);
}
return filled;
}
+ public static String fillStringProperty(String propertyName, AuthProcFilterInitContext params, String defaultValue) {
+ String filled = params.getProperty(propertyName);
+ if (!StringUtils.hasText(filled)) {
+ return defaultValue;
+ }
+ return filled;
+ }
+
private static void redirectToRegistrationForm(String base, HttpServletResponse response,
String clientIdentifier, Facility facility, PerunUser user) {
Map params = new HashMap<>();
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/IsEligibleFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/IsEligibleFilter.java
new file mode 100644
index 000000000..7562fcabf
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/IsEligibleFilter.java
@@ -0,0 +1,206 @@
+package cz.muni.ics.oidc.server.filters.impl;
+
+import cz.muni.ics.oidc.BeanUtil;
+import cz.muni.ics.oidc.exceptions.ConfigurationException;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.server.filters.AuthProcFilter;
+import cz.muni.ics.oidc.server.filters.AuthProcFilterCommonVars;
+import cz.muni.ics.oidc.server.filters.AuthProcFilterConstants;
+import cz.muni.ics.oidc.server.filters.AuthProcFilterInitContext;
+import cz.muni.ics.oidc.server.filters.FiltersUtils;
+import cz.muni.ics.oidc.web.controllers.ControllerUtils;
+import cz.muni.ics.oidc.web.controllers.PerunUnapprovedController;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpHeaders;
+import org.springframework.security.saml.SAMLCredential;
+import org.springframework.util.StringUtils;
+
+/**
+ * This filter verifies that user attribute isCesnetEligible is not older than given time frame.
+ * In case the value is older, denies access to the service and forces user to use verified identity.
+ * Otherwise, user can to access the service.
+ *
+ * Configuration (replace [name] part with the name defined for the filter):
+ *
+ * - filter.[name].samlAttribute - mapping to isCesnetEligible attribute
+ * - filter.[name].triggerScope - scope that has to be requested to apply this filter
+ * - filter.[name].validityPeriodMonths - specify in months, how long the value can be old, if no value
+ * or invalid value has been provided, defaults to 12 months
+ *
+ * @author Dominik Frantisek Bucik
+ */
+@Slf4j
+public class IsEligibleFilter extends AuthProcFilter {
+
+ public static final String APPLIED = "APPLIED_" + IsEligibleFilter.class.getSimpleName();
+
+ public final static String DEFAULT_HEADER_TRANSLATION_KEY = "403_is_eligible_default_header_text";
+ public final static String DEFAULT_TEXT_TRANSLATION_KEY = "403_is_eligible_default_text";
+ public final static String DEFAULT_BUTTON_TRANSLATION_KEY = "403_is_eligible_default_button_text";
+ public final static String DEFAULT_CONTACT_TRANSLATION_KEY = "403_is_eligible_default_contact_text";
+
+ public static final String HEADER_TRANSLATION = "header_translation";
+ public static final String TEXT_TRANSLATION = "text_translation";
+ public static final String BUTTON_TRANSLATION = "button_translation";
+ public static final String CONTACT_TRANSLATION = "contact_translation";
+
+ /* CONFIGURATION PROPERTIES */
+ private static final String SAML_ATTRIBUTE = "samlAttribute";
+ private static final String TRIGGER_SCOPE = "triggerScope";
+ private static final String VALIDITY_PERIOD = "validityPeriodMonths";
+ private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ private static final String OLD_VALUE_HEADER_TRANSLATION_KEY = "old_value_header_translation_key";
+ private static final String OLD_VALUE_TEXT_TRANSLATION_KEY = "old_value_text_translation_key";
+ private static final String OLD_VALUE_BUTTON_TRANSLATION_KEY = "old_value_button_translation_key";
+ private static final String OLD_VALUE_CONTACT_TRANSLATION_KEY = "old_value_contact_translation_key";
+
+ private static final String NO_VALUE_HEADER_TRANSLATION_KEY = "no_value_header_translation_key";
+ private static final String NO_VALUE_TEXT_TRANSLATION_KEY = "no_value_text_translation_key";
+ private static final String NO_VALUE_BUTTON_TRANSLATION_KEY = "no_value_button_translation_key";
+ private static final String NO_VALUE_CONTACT_TRANSLATION_KEY = "no_value_contact_translation_key";
+
+ /* END OF CONFIGURATION PROPERTIES */
+
+ private final String eligibleLastSeenSAMLAttributeName;
+ private final String triggerScope;
+ private final int validityPeriod;
+
+ private final String oldValueHeaderTranslationKey;
+ private final String oldValueTextTranslationKey;
+ private final String oldValueButtonTranslationKey;
+ private final String oldValueContactTranslationKey;
+
+ private final String noValueHeaderTranslationKey;
+ private final String noValueTextTranslationKey;
+ private final String noValueButtonTranslationKey;
+ private final String noValueContactTranslationKey;
+
+ private final PerunOidcConfig config;
+ private final String filterName;
+
+ public IsEligibleFilter(AuthProcFilterInitContext ctx) throws ConfigurationException {
+ super(ctx);
+ this.filterName = ctx.getFilterName();
+
+ BeanUtil beanUtil = ctx.getBeanUtil();
+ this.config = beanUtil.getBean(PerunOidcConfig.class);
+ this.eligibleLastSeenSAMLAttributeName = FiltersUtils.fillStringMandatoryProperty(SAML_ATTRIBUTE, filterName, ctx);
+ this.triggerScope = FiltersUtils.fillStringMandatoryProperty(TRIGGER_SCOPE, filterName, ctx);
+ int validityPeriodParam = 12;
+ if (ctx.hasProperty(VALIDITY_PERIOD)) {
+ try {
+ validityPeriodParam = Integer.parseInt(ctx.getProperty(VALIDITY_PERIOD));
+ } catch (NumberFormatException ignored) {
+ //no problem, we have default value
+ }
+ }
+
+ this.oldValueHeaderTranslationKey = FiltersUtils.fillStringProperty(
+ OLD_VALUE_HEADER_TRANSLATION_KEY, ctx, DEFAULT_HEADER_TRANSLATION_KEY);
+ this.oldValueTextTranslationKey = FiltersUtils.fillStringProperty(
+ OLD_VALUE_TEXT_TRANSLATION_KEY, ctx, DEFAULT_TEXT_TRANSLATION_KEY);
+ this.oldValueButtonTranslationKey = FiltersUtils.fillStringProperty(
+ OLD_VALUE_BUTTON_TRANSLATION_KEY, ctx, DEFAULT_BUTTON_TRANSLATION_KEY);
+ this.oldValueContactTranslationKey = FiltersUtils.fillStringProperty(
+ OLD_VALUE_CONTACT_TRANSLATION_KEY, ctx, DEFAULT_CONTACT_TRANSLATION_KEY);
+
+ this.noValueHeaderTranslationKey = FiltersUtils.fillStringProperty(
+ NO_VALUE_HEADER_TRANSLATION_KEY, ctx, DEFAULT_HEADER_TRANSLATION_KEY);
+ this.noValueTextTranslationKey = FiltersUtils.fillStringProperty(
+ NO_VALUE_TEXT_TRANSLATION_KEY, ctx, DEFAULT_TEXT_TRANSLATION_KEY);
+ this.noValueButtonTranslationKey = FiltersUtils.fillStringProperty(
+ NO_VALUE_BUTTON_TRANSLATION_KEY, ctx, DEFAULT_BUTTON_TRANSLATION_KEY);
+ this.noValueContactTranslationKey = FiltersUtils.fillStringProperty(
+ NO_VALUE_CONTACT_TRANSLATION_KEY, ctx, DEFAULT_CONTACT_TRANSLATION_KEY);
+
+ this.validityPeriod = validityPeriodParam;
+
+ }
+
+ @Override
+ protected String getSessionAppliedParamName() {
+ return APPLIED + filterName;
+ }
+
+ @Override
+ protected boolean process(HttpServletRequest req, HttpServletResponse res, AuthProcFilterCommonVars params) {
+ if (!FiltersUtils.isScopePresent(req.getParameter(AuthProcFilterConstants.PARAM_SCOPE), triggerScope)) {
+ log.debug("{} - skip execution: scope '{}' is not present in request", filterName, triggerScope);
+ return true;
+ }
+
+ SAMLCredential samlCredential = FiltersUtils.getSamlCredential(req);
+ if (samlCredential == null) {
+ log.debug("{} - skip execution: no SAML credential to fetch attribute from is available", filterName);
+ return true;
+ }
+
+ String eligibleLastSeenTimestamp = samlCredential.getAttributeAsString(eligibleLastSeenSAMLAttributeName);
+
+ String headerKey = noValueHeaderTranslationKey;
+ String textKey = noValueTextTranslationKey;
+ String buttonKey = noValueButtonTranslationKey;
+ String contactKey = noValueContactTranslationKey;
+
+ if (StringUtils.hasText(eligibleLastSeenTimestamp)) {
+ LocalDateTime timeStamp;
+ try {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
+ timeStamp = LocalDateTime.parse(eligibleLastSeenTimestamp, formatter);
+ } catch (DateTimeParseException e) {
+ log.warn("{} - could not parse timestamp from attribute '{}' with value '{}'",
+ filterName, eligibleLastSeenSAMLAttributeName, eligibleLastSeenTimestamp);
+ log.debug("{} - skip execution - have no timestamp to compare to", filterName);
+ log.trace("{} - details:", filterName, e);
+ return true;
+ }
+
+ LocalDateTime now = LocalDateTime.now();
+ if (now.minusMonths(validityPeriod).isBefore(timeStamp)) {
+ log.debug("{} - attribute '{}' value is valid", filterName, eligibleLastSeenSAMLAttributeName);
+ return true;
+ } else {
+ headerKey = oldValueHeaderTranslationKey;
+ textKey = oldValueTextTranslationKey;
+ buttonKey = oldValueButtonTranslationKey;
+ contactKey = oldValueContactTranslationKey;
+ }
+ }
+
+ HttpSession sess = req.getSession(true);
+ sess.setAttribute(HEADER_TRANSLATION, headerKey);
+ sess.setAttribute(TEXT_TRANSLATION, textKey);
+ sess.setAttribute(BUTTON_TRANSLATION, buttonKey);
+ sess.setAttribute(CONTACT_TRANSLATION, contactKey);
+
+ log.debug("{} - attribute '{}' value is invalid, stop user at this point", filterName, eligibleLastSeenTimestamp);
+ this.redirect(req, res);
+ return false;
+ }
+
+ private void redirect(HttpServletRequest req, HttpServletResponse res) {
+ Map params = new HashMap<>();
+
+ String targetURL = FiltersUtils.buildRequestURL(req,
+ Collections.singletonMap(AuthProcFilterConstants.PARAM_PROMPT, "login"));
+ params.put(AuthProcFilterConstants.PARAM_TARGET, targetURL);
+
+ String redirectUrl = ControllerUtils.createRedirectUrl(config.getConfigBean().getIssuer(),
+ PerunUnapprovedController.UNAPPROVED_IS_ELIGIBLE_MAPPING, params);
+ log.debug("{} - redirecting user to unapproved: URL '{}'", filterName, redirectUrl);
+ res.reset();
+ res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+ res.setHeader(HttpHeaders.LOCATION, redirectUrl);
+ }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedController.java
index ae1114a99..eed9e1d6b 100644
--- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedController.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedController.java
@@ -3,8 +3,15 @@ package cz.muni.ics.oidc.web.controllers;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_CLIENT_ID;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_HEADER;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_MESSAGE;
-import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_REASON;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_TARGET;
+import static cz.muni.ics.oidc.server.filters.impl.IsEligibleFilter.BUTTON_TRANSLATION;
+import static cz.muni.ics.oidc.server.filters.impl.IsEligibleFilter.CONTACT_TRANSLATION;
+import static cz.muni.ics.oidc.server.filters.impl.IsEligibleFilter.DEFAULT_BUTTON_TRANSLATION_KEY;
+import static cz.muni.ics.oidc.server.filters.impl.IsEligibleFilter.DEFAULT_CONTACT_TRANSLATION_KEY;
+import static cz.muni.ics.oidc.server.filters.impl.IsEligibleFilter.DEFAULT_HEADER_TRANSLATION_KEY;
+import static cz.muni.ics.oidc.server.filters.impl.IsEligibleFilter.DEFAULT_TEXT_TRANSLATION_KEY;
+import static cz.muni.ics.oidc.server.filters.impl.IsEligibleFilter.HEADER_TRANSLATION;
+import static cz.muni.ics.oidc.server.filters.impl.IsEligibleFilter.TEXT_TRANSLATION;
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
@@ -13,13 +20,19 @@ import cz.muni.ics.oidc.web.WebHtmlClasses;
import cz.muni.ics.openid.connect.view.HttpCodeView;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.SessionAttribute;
/**
* Ctonroller for the unapproved page.
@@ -32,7 +45,7 @@ public class PerunUnapprovedController {
public static final String UNAPPROVED_MAPPING = "/unapproved";
public static final String UNAPPROVED_SPECIFIC_MAPPING = "/unapproved_spec";
- public static final String UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING = "/unapprovedIce";
+ public static final String UNAPPROVED_IS_ELIGIBLE_MAPPING = "/unapprovedNotEligible";
public static final String UNAPPROVED_ENSURE_VO_MAPPING = "/unapprovedEnsureVo";
public static final String UNAPPROVED_AUTHORIZATION = "/unapprovedAuthorization";
public static final String UNAPPROVED_NOT_IN_TEST_VOS_GROUPS = "/unapprovedNotInTestVosGroups";
@@ -40,11 +53,9 @@ public class PerunUnapprovedController {
public static final String UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS = "/unapprovedNotInMandatoryVosGroups";
public static final String UNAPPROVED_NOT_LOGGED_IN = "/unapprovedNotLoggedIn";
- public static final String REASON_NOT_SET = "notSet";
- public static final String REASON_EXPIRED = "expired";
-
private static final String OUT_HEADER = "outHeader";
private static final String OUT_MESSAGE = "outMessage";
+ private static final String OUT_BUTTON = "outButton";
private static final String OUT_CONTACT_P = "outContactP";
private static final String ENSURE_VO_HDR = "403_ensure_vo_hdr";
@@ -53,11 +64,6 @@ public class PerunUnapprovedController {
private static final String AUTHORIZATION_HDR = "403_authorization_hdr";
private static final String AUTHORIZATION_MSG = "403_authorization_msg";
- private static final String ICE_NOT_SET_HDR = "403_isCesnetEligible_notSet_hdr";
- private static final String ICE_NOT_SET_MSG = "403_isCesnetEligible_notSet_msg";
- private static final String ICE_EXPIRED_HDR = "403_isCesnetEligible_expired_hdr";
- private static final String ICE_EXPIRED_MSG = "403_isCesnetEligible_expired_msg";
-
private static final String NOT_IN_TEST_VOS_GROUPS_HDR = "403_not_in_test_vos_groups_hdr";
private static final String NOT_IN_TEST_VOS_GROUPS_MSG = "403_not_in_test_vos_groups_msg";
@@ -72,6 +78,10 @@ public class PerunUnapprovedController {
private static final String CONTACT_LANG_PROP_KEY = "contact_p";
private static final String CONTACT_MAIL = "contactMail";
+ private static final String HAS_TARGET = "hasTarget";
+ private static final String REASON = "reason";
+
+ public static final String TARGET = "target";
@Autowired
private ClientDetailsEntityService clientService;
@@ -82,6 +92,9 @@ public class PerunUnapprovedController {
@Autowired
private WebHtmlClasses htmlClasses;
+ @Autowired
+ private SecurityContextLogoutHandler logoutHandler;
+
@GetMapping(value = UNAPPROVED_MAPPING)
public String showUnapproved(HttpServletRequest req,
Map model,
@@ -134,36 +147,45 @@ public class PerunUnapprovedController {
return "unapproved_spec";
}
- @GetMapping(value = UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING)
- public String showUnapprovedIsCesnetEligible(HttpServletRequest req, Map model,
- @RequestParam(value = PARAM_TARGET) String target,
- @RequestParam(value = PARAM_REASON) String reason) {
-
+ @GetMapping(value = UNAPPROVED_IS_ELIGIBLE_MAPPING)
+ public String showUnapprovedIsEligible(HttpServletRequest req,
+ Map model,
+ @RequestParam(value = PARAM_TARGET, required = false) String target)
+ {
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
- String header;
- String message;
-
- if (REASON_EXPIRED.equals(reason)) {
- header = ICE_EXPIRED_HDR;
- message = ICE_EXPIRED_MSG;
- } else if (REASON_NOT_SET.equals(reason)){
- header = ICE_NOT_SET_HDR;
- message = ICE_NOT_SET_MSG;
- } else {
- model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
- return HttpCodeView.VIEWNAME;
- }
+ HttpSession sess = req.getSession();
+ String header = loadSessionTranslationKey(sess, HEADER_TRANSLATION, DEFAULT_HEADER_TRANSLATION_KEY);
+ String message = loadSessionTranslationKey(sess, TEXT_TRANSLATION, DEFAULT_TEXT_TRANSLATION_KEY);
+ String button = loadSessionTranslationKey(sess, BUTTON_TRANSLATION, DEFAULT_BUTTON_TRANSLATION_KEY);
+ String contactP = loadSessionTranslationKey(sess, CONTACT_TRANSLATION, DEFAULT_CONTACT_TRANSLATION_KEY);
model.put(OUT_HEADER, header);
model.put(OUT_MESSAGE, message);
- model.put(OUT_CONTACT_P, CONTACT_LANG_PROP_KEY);
+ model.put(OUT_BUTTON, button);
+ model.put(OUT_CONTACT_P, contactP);
model.put(CONTACT_MAIL, perunOidcConfig.getEmailContact());
+ model.put(HAS_TARGET, StringUtils.hasText(target));
+ req.getSession(true).setAttribute(TARGET, target);
if (perunOidcConfig.getTheme().equalsIgnoreCase("lsaai")) {
- return "lsaai/unapproved_spec";
+ return "lsaai/unapproved_is_eligible";
+ }
+ return "unapproved_is_eligible";
+ }
+
+ @PostMapping(value = UNAPPROVED_IS_ELIGIBLE_MAPPING)
+ public String showUnapprovedIsEligibleHandle(HttpServletRequest req,
+ HttpServletResponse res,
+ Map model,
+ @SessionAttribute(PARAM_TARGET) String target)
+ {
+ if (!StringUtils.hasText(target)) {
+ return showUnapprovedIsEligible(req, model, null);
+ } else {
+ logoutHandler.logout(req, res, null);
+ return "redirect:" + target;
}
- return "unapproved_spec";
}
@GetMapping(value = UNAPPROVED_ENSURE_VO_MAPPING)
@@ -253,4 +275,12 @@ public class PerunUnapprovedController {
return "unapproved_spec";
}
+
+ private String loadSessionTranslationKey(HttpSession sess, String key, String fallbackValue) {
+ if (sess != null && StringUtils.hasText((String) sess.getAttribute(key))) {
+ return (String) sess.getAttribute(key);
+ }
+ return fallbackValue;
+ }
+
}