AccessRulesService.java
/*
* Copyright (c) 2002-2014, Mairie de Paris
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice
* and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* License 1.0
*/
package fr.paris.lutece.plugins.myluteceaccessrules.service;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import fr.paris.lutece.plugins.mylutece.service.MyLuteceUserService;
import fr.paris.lutece.plugins.mylutece.service.MyluteceExternalRoleService;
import fr.paris.lutece.plugins.myluteceaccessrules.business.Rule;
import fr.paris.lutece.plugins.myluteceaccessrules.business.RuleHome;
import fr.paris.lutece.portal.service.security.SecurityService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPathService;
import fr.paris.lutece.util.url.UrlItem;
/**
* This class provides an Access Rules Service
*/
public final class AccessRulesService
{
/** Parameter NB REDIRECT */
public static final String PARAMETER_NB_REDIRECT = "accessrules_nb_redirect";
/** Session attribute that stores the LuteceUser object attached to the session. */
public static final String I18N_DEFAULT_MESSAGE_UNAUTHORIZED="myluteceaccessrules.message.unauthorized";
/** The Constant I18N_DEFAULT_TITLE_MESSAGE_UNAUTHORIZED. */
public static final String I18N_DEFAULT_TITLE_MESSAGE_UNAUTHORIZED="myluteceaccessrules.titlemessage.unauthorized";
/** The Constant MARK_PUBLIC_LIST_URL. */
public static final String MARK_PUBLIC_LIST_URL = "public_list_url";
/** The Constant PUBLIC_URL_PREFIX. */
public static final String PUBLIC_URL_PREFIX = "mylutece.security.public_url.";
/** The Constant MARK_PORTAL_AUTHENTICATION_REQUIRED. */
public static final String MARK_PORTAL_AUTHENTICATION_REQUIRED = "portal_authentication_required";
/** Parameter NB REDIRECT */
public static final String PROPERTY_MAX_REDIRECT= "myluteceaccessrules.maxRedirect";
/** The Constant MARK_LOCALE. */
private static final String MARK_LOCALE = "locale";
/** The Constant MARK_WEBAPP_URL. */
private static final String MARK_WEBAPP_URL = "webapp_url";
/** The Constant CACHE_KEY_LIST_RULES_ENABLED. */
private static final String CACHE_KEY_LIST_RULES_ENABLED = "list_access_rules_enabled";
/** The Constant URL_INTERROGATIVE. */
private static final String URL_INTERROGATIVE = "?";
/** The Constant URL_AMPERSAND. */
private static final String URL_AMPERSAND = "&";
/** The Constant URL_EQUAL. */
private static final String URL_EQUAL = "=";
/** The Constant URL_STAR. */
private static final String URL_STAR = "*";
/** The Constant MARKER_BASE_URL. */
private static final String MARKER_BASE_URL = "$baseurl";
/** The Constant MARKER_BACK_URL. */
private static final String MARKER_BACK_URL = "$backurl";
/** The singleton. */
private static AccessRulesService _singleton = null;
/** The cache. */
private AccessRulesCacheService _cache;
/**
* Private constructor.
*/
private AccessRulesService( )
{
}
/**
* Get the unique instance of the AccessRulesService
*
* @return The instance
*/
public static AccessRulesService getInstance( )
{
if(_singleton==null)
{
_singleton=new AccessRulesService( );
_singleton._cache=new AccessRulesCacheService( );
_singleton._cache.enableCache(true);
}
return _singleton;
}
/**
* Checks if is access rules enabled.
*
* @return true, if is access rules enabled
*/
public boolean isAccessRulesEnabled()
{
return SecurityService.isAuthenticationEnable( ) && getRulesEnabled().size()>0;
}
/**
* Gets the cache.
*
* @return the cache
*/
public AccessRulesCacheService getCache()
{
return _cache;
}
/**
* Gets the list of enabled rules
*
* @return the list of enabled rules
*/
public List<Rule> getRulesEnabled()
{
List<Rule> listRulesEnable = (List<Rule>) getCache().getFromCache( CACHE_KEY_LIST_RULES_ENABLED );
if ( listRulesEnable == null )
{
listRulesEnable=RuleHome.getRulesList().stream().filter(x->x.getEnable()).collect(Collectors.toList());
getCache().putInCache( CACHE_KEY_LIST_RULES_ENABLED, listRulesEnable.stream().filter(Rule::getEnable).collect(Collectors.toList()) );
}
return listRulesEnable;
}
/**
* Ge the first rule triggered.
*
* @param request the request
* @return the first Rule triggered
*/
public Rule geFirstRuleTriggered(HttpServletRequest request)
{
return getRulesEnabled().stream().filter(x-> isRuleMatchingRequest(x, request) && isUserTriggerRule(x, request)).findFirst().orElse(null);
}
/**
* Checks if this rule match the request.
*
* @param rule the rule
* @param request the request
* @return if this rule match the request
*/
public boolean isRuleMatchingRequest(Rule rule,HttpServletRequest request)
{
return (rule.getProtectedUrls() ==null || rule.getProtectedUrls().isEmpty()||rule.getProtectedUrls().stream().anyMatch(x->matchUrl(request, x.getName())) )&& (rule.getPublicUrls()==null || rule.getPublicUrls().isEmpty()|| !rule.getPublicUrls().stream().anyMatch(x->matchUrl(request, x.getName())));
}
/**
* Checks if the authenticated user trigger the rule
*
* @param rule the rule
* @param request the request
* @return true, if is user trigger rule
*/
private boolean isUserTriggerRule(Rule rule,HttpServletRequest request)
{
return rule.getRoles()!=null?rule.getRoles().stream().anyMatch(x-> !SecurityService.getInstance().isUserInRole(request, x.getName())):false;
}
/**
* method to test if the URL matches the pattern.
*
* @param request the request
* @param strUrlPatern the pattern
* @return true if the URL matches the pattern
*/
public boolean matchUrl( HttpServletRequest request, String strUrlPatern )
{
boolean bMatch = false;
if ( strUrlPatern != null )
{
UrlItem url = new UrlItem( getResquestedUrl( request ) );
if ( strUrlPatern.contains( URL_INTERROGATIVE ) )
{
for ( String strParamPatternValue : strUrlPatern.substring( strUrlPatern.indexOf( URL_INTERROGATIVE ) +
1 ).split( URL_AMPERSAND ) )
{
String[] arrayPatternParamValue = strParamPatternValue.split( URL_EQUAL );
if ( ( arrayPatternParamValue != null ) &&
( request.getParameter( arrayPatternParamValue[0] ) != null ) )
{
url.addParameter( arrayPatternParamValue[0], request.getParameter( arrayPatternParamValue[0] ) );
}
}
}
if ( strUrlPatern.contains( URL_STAR ) )
{
String strUrlPaternLeftEnd = strUrlPatern.substring( 0, strUrlPatern.indexOf( URL_STAR ) );
String strAbsoluteUrlPattern = getAbsoluteUrl( request, strUrlPaternLeftEnd );
bMatch = url.getUrl( ).startsWith( strAbsoluteUrlPattern );
}
else
{
String strAbsoluteUrlPattern = getAbsoluteUrl( request, strUrlPatern );
bMatch = url.getUrl( ).equals( strAbsoluteUrlPattern );
}
}
return bMatch;
}
/**
* Returns the absolute url corresponding to the given one, if the later was
* found to be relative. An url starting with "http://" is absolute. A
* relative url should be given relatively to the webapp root.
*
* @param request
* the http request (provides the base path if needed)
* @param strUrl
* the url to transform
* @return the corresonding absolute url
*
* */
private String getAbsoluteUrl( HttpServletRequest request, String strUrl )
{
if ( ( strUrl != null ) && !strUrl.startsWith( "http://" ) && !strUrl.startsWith( "https://" ) )
{
return AppPathService.getBaseUrl( request ) + strUrl;
}
else
{
return strUrl;
}
}
/**
* Return the absolute representation of the requested url.
*
* @param request the http request (provides the base path if needed)
* @return the requested url has a string
*/
private String getResquestedUrl( HttpServletRequest request )
{
return AppPathService.getBaseUrl( request ) + request.getServletPath( ).substring( 1 );
}
/**
* Builds the redirect url depending to the rule specification
*
* @param rule the rule
* @param request the request
* @param nbRedirect
* @return the redirect url
*/
public String buildRedirectUrl(Rule rule, HttpServletRequest request,int nbRedirect) {
String strRedirectUrl = getAbsoluteUrl( request,rule.getRedirecturl());
String strBackUrl = rule.getBackUrl();
if (StringUtils.isEmpty(strBackUrl)) {
strBackUrl = getDefaultServiceBackUrl(request,nbRedirect);
} else if (strBackUrl.contains(MARKER_BASE_URL)) {
String strBaseUrl = AppPathService.getBaseUrl(request);
if (strBaseUrl.endsWith("/") && strBackUrl.startsWith("/")) {
strBackUrl = strBackUrl.substring(1);
}
if (!strBaseUrl.endsWith("/") && !strBackUrl.startsWith("/")) {
strBackUrl = strBackUrl + "/";
}
strBackUrl = strBackUrl.replaceAll("\\" + MARKER_BASE_URL, strBaseUrl);
}
if(!rule.isEncodeBackUrl())
{
try {
strBackUrl = URLEncoder.encode(strBackUrl, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
AppLogService.error("An error appear during Url encode the back url {}", strBackUrl, e);
}
}
else
{
strBackUrl=encodeUrl(strBackUrl);
}
strRedirectUrl = strRedirectUrl.replaceAll("\\" + MARKER_BACK_URL, strBackUrl);
return strRedirectUrl;
}
private String getDefaultServiceBackUrl( HttpServletRequest request,int nbRedirect)
{
String strDefaultBackUrl="";
String strNextUrl = request.getServletPath();
UrlItem url = new UrlItem( strNextUrl );
Enumeration<String> enumParams = request.getParameterNames( );
try
{
while ( enumParams.hasMoreElements( ) )
{
String strParamName = enumParams.nextElement( );
url.addParameter( strParamName, URLEncoder.encode( request.getParameter( strParamName ), "UTF-8" ) );
}
url.addParameter(AccessRulesService.PARAMETER_NB_REDIRECT, nbRedirect+1);
strDefaultBackUrl=url.getUrl( ) ;
if(strDefaultBackUrl.startsWith("/"))
{
strDefaultBackUrl=strDefaultBackUrl.substring(1);
}
strDefaultBackUrl= AppPathService.getAbsoluteUrl(request, strDefaultBackUrl);
}
catch( UnsupportedEncodingException ex )
{
AppLogService.error( "Redirection error while encoding URL in Access Rule Service: {}", ex.getMessage( ), ex );
strDefaultBackUrl=AppPathService.getBaseUrl(request);
}
return strDefaultBackUrl;
}
/**
* encode url in base64
* @param strUrl the url to encode
* @return an url encode in base64
*/
private String encodeUrl(String strUrl)
{
return !StringUtils.isEmpty(strUrl) ? new String(Base64.getUrlEncoder().encode(strUrl.getBytes( StandardCharsets.UTF_8 ))):"";
}
}