Merge pull request #208 from dBucik/ice
feat: IsEligible authproc filter and claim sourcepull/1580/head
commit
7d5dc7abcd
|
@ -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.<br/><a class="mt-2 cw btn btn-primary btn-lg btn-block" href="%%TARGET%%">Znovu p\u0159ihl\u00E1sit</a>
|
||||
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.<br/><a class="mt-2 cw btn btn-lg btn-primary btn-block" href="%%TARGET%%">Znovu p\u0159ihl\u00E1sit</a>
|
||||
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
|
||||
|
|
|
@ -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.<a class="mt-2 cw btn btn-primary btn-lg btn-block" href="%%TARGET%%">Log in again</a>
|
||||
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.<a class="mt-2 cw btn btn-primary btn-lg btn-block" href="%%TARGET%%">Log in again</a>
|
||||
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
|
||||
|
|
|
@ -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" %>
|
||||
|
||||
<ls:header />
|
||||
|
||||
<div class="error_message" style="word-wrap: break-word;">
|
||||
<h1><spring:message code="${outHeader}"/></h1>
|
||||
<p><spring:message code="${outMessage}"/></p>
|
||||
<c:if test="${hasTarget}">
|
||||
<form method="POST" action="" class="mb-4">
|
||||
<button class="btn btn-primary btn-block"><spring:message code="${outButton}"/></button>
|
||||
</form>
|
||||
</c:if>
|
||||
<p><spring:message code="${outContactP}"/>${" "}<a href="mailto:${contactMail}">${contactMail}</a></p>
|
||||
</div>
|
||||
|
||||
<ls:footer/>
|
|
@ -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<String> cssLinks = new ArrayList<>();
|
||||
|
||||
pageContext.setAttribute("cssLinks", cssLinks);
|
||||
|
||||
%>
|
||||
|
||||
<t:header title="${title}" reqURL="${reqURL}" baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
|
||||
|
||||
</div> <%-- header --%>
|
||||
|
||||
<div id="content">
|
||||
<div class="error_message" style="word-wrap: break-word;">
|
||||
<h1><spring:message code="${outHeader}"/></h1>
|
||||
<p><spring:message code="${outMessage}"/></p>
|
||||
<c:if test="${hasTarget}">
|
||||
<form method="POST" action="" class="mb-4">
|
||||
<button class="btn btn-primary btn-block"><spring:message code="${outButton}"/></button>
|
||||
</form>
|
||||
</c:if>
|
||||
<p><spring:message code="${outContactP}"/>${" "}<a href="mailto:${contactMail}">${contactMail}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- ENDWRAP -->
|
||||
|
||||
<t:footer baseURL="${baseURL}" theme="${theme}"/>
|
|
@ -65,7 +65,7 @@
|
|||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_MAPPING}**" />
|
||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_AUTHORIZATION}**" />
|
||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_ENSURE_VO_MAPPING}**" />
|
||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING}**" />
|
||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_IS_ELIGIBLE_MAPPING}**" />
|
||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS}**" />
|
||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_PROD_VOS_GROUPS}**" />
|
||||
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_TEST_VOS_GROUPS}**" />
|
||||
|
|
|
@ -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<String> list) {
|
||||
ArrayNode res = JsonNodeFactory.instance.arrayNode();
|
||||
if (list != null && !list.isEmpty()) {
|
||||
|
|
|
@ -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):
|
||||
* <ul>
|
||||
* <li><b>custom.claim.[claimName].source.attribute</b> - attribute containing the eligibility last seen timestamp passed via SAML authentication</li>
|
||||
* <li><b>custom.claim.[claimName].source.validityPeriodMonths</b> - 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</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Pavol Pluta <pavol.pluta1@gmail.com>
|
||||
* @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
|
||||
*/
|
||||
@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<String> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, String> params = new HashMap<>();
|
||||
|
|
|
@ -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):
|
||||
* <ul>
|
||||
* <li><b>filter.[name].samlAttribute</b> - mapping to isCesnetEligible attribute</li>
|
||||
* <li><b>filter.[name].triggerScope</b> - scope that has to be requested to apply this filter</li>
|
||||
* <li><b>filter.[name].validityPeriodMonths</b> - specify in months, how long the value can be old, if no value
|
||||
* or invalid value has been provided, defaults to 12 months</li>
|
||||
* </ul>
|
||||
* @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
|
||||
*/
|
||||
@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<String, String> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, Object> model,
|
||||
|
@ -134,36 +147,45 @@ public class PerunUnapprovedController {
|
|||
return "unapproved_spec";
|
||||
}
|
||||
|
||||
@GetMapping(value = UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING)
|
||||
public String showUnapprovedIsCesnetEligible(HttpServletRequest req, Map<String, Object> 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<String, Object> 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<String, Object> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue