001/* 002 * Copyright 2015 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.serverdirectory; 017 018import java.util.Arrays; 019import java.util.Comparator; 020import java.util.HashSet; 021import java.util.Set; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import org.apache.cocoon.ProcessingException; 026import org.apache.commons.io.FilenameUtils; 027import org.apache.commons.lang.ArrayUtils; 028import org.apache.commons.lang.StringUtils; 029import org.apache.excalibur.source.Source; 030import org.apache.excalibur.source.SourceResolver; 031 032import org.ametys.core.user.UserIdentity; 033import org.ametys.runtime.authentication.AccessDeniedException; 034import org.ametys.runtime.authentication.AuthorizationRequiredException; 035import org.ametys.runtime.config.Config; 036 037/** 038 * Helper gathering utility methods for server directories 039 */ 040public final class ServerDirectoryHelper 041{ 042 // Pattern for the dynamic path variables : matches "${g1}" and "${g1[g2=g3]}", with g1, g2 and g3 the capturing groups 043 private static final Pattern DYNAMIC_PATH_PATTERN = Pattern.compile("\\$\\{([^}\\[]+)(?:\\[([^=\\[\\]\\}\\{]+)=([^=\\[\\]\\}\\{]+)\\])?\\}"); 044 045 private ServerDirectoryHelper() 046 { 047 // empty constructor 048 } 049 050 /** 051 * Get the sources corresponding to the roots of the defined server directories 052 * @param sourceResolver the source resolver 053 * @return the sources of the server directories' roots 054 * @throws ProcessingException if the sever directory's location wasn't found 055 */ 056 public static Set<Source> getRootServerSources(SourceResolver sourceResolver) throws ProcessingException 057 { 058 Set<String> locations = ServerDirectoryHelper.getRootServerDirectoryPaths(); 059 060 Set<Source> sources = new HashSet<>(); 061 062 for (String location : locations) 063 { 064 Source src = null; 065 try 066 { 067 src = sourceResolver.resolveURI(location.trim(), "file://", null); 068 if (src.exists()) 069 { 070 sources.add(src); 071 } 072 } 073 catch (Exception e) 074 { 075 throw new ProcessingException("Unable to retrieve server directory to location: <" + location + ">", e); 076 } 077 finally 078 { 079 sourceResolver.release(src); 080 } 081 } 082 083 return sources; 084 } 085 086 /** 087 * Get the paths of the server directories' roots 088 * @return the paths of the server directories' roots 089 */ 090 public static Set<String> getRootServerDirectoryPaths () 091 { 092 String authorizedDirectories = Config.getInstance().getValue("org.ametys.plugins.server.directory.authorized"); 093 String[] locations = StringUtils.split(authorizedDirectories, "\n"); 094 095 Comparator<String> c = new Comparator<String>() 096 { 097 @Override 098 public int compare(String s1, String s2) 099 { 100 101 int l1 = s1.length(); 102 int l2 = s2.length(); 103 104 return ((Integer) l1).compareTo(l2); 105 } 106 }; 107 Arrays.sort(locations, c); 108 109 Set<String> distinctLocations = new HashSet<>(); 110 for (String location : locations) 111 { 112 boolean found = false; 113 for (String distinctLocation : distinctLocations) 114 { 115 if (location.startsWith(distinctLocation)) 116 { 117 found = true; 118 break; 119 } 120 } 121 122 if (!found) 123 { 124 distinctLocations.add(location.trim()); 125 } 126 } 127 128 return distinctLocations; 129 } 130 131 /** 132 * Normalizes a path, removing double and single dot path steps and replace '\' by '/' 133 * @param path The path 134 * @return The normalized path 135 */ 136 public static String normalize (String path) 137 { 138 String normalizedPath = FilenameUtils.normalize(path); 139 return normalizedPath.replace("\\", "/"); 140 } 141 142 /** 143 * Check if the given path is a valid path for a root of a server directory 144 * @param path the path to check 145 * @param rootSources the sources corresponding to the server directories' roots 146 * @return true if the path is valid, false otherwise 147 */ 148 public static boolean isValidPath(String path, Set<Source> rootSources) 149 { 150 String normalizedPath = normalize(path); 151 for (Source rootSource : rootSources) 152 { 153 if (normalizedPath.startsWith(rootSource.getURI())) 154 { 155 return true; 156 } 157 } 158 return false; 159 } 160 161 /** 162 * Evaluate a dynamic path with contextual variables 163 * @param path The dynamic path 164 * @param siteName The current site name 165 * @param language The current site language 166 * @param currentUser The current user 167 * @return The path resolved 168 * @throws AuthorizationRequiredException If the current user is null and the path required the login 169 * @throws AccessDeniedException If the connected user does not belong to required population 170 * @throws IllegalArgumentException If a dynamic variable can not be evaluated 171 */ 172 public static String evaluateDynamicPath(String path, String siteName, String language, UserIdentity currentUser) throws AuthorizationRequiredException, AccessDeniedException, IllegalArgumentException 173 { 174 Matcher matcher = DYNAMIC_PATH_PATTERN.matcher(path); 175 176 StringBuffer newPath = new StringBuffer(); 177 while (matcher.find()) 178 { 179 String variable = matcher.group(1); 180 String param = matcher.group(2); 181 String paramValue = matcher.group(3); 182 183 if ("login".equals(variable)) 184 { 185 if (currentUser == null) 186 { 187 throw new AuthorizationRequiredException(null); 188 } 189 if (param != null && paramValue != null) 190 { 191 if ("population".equals(param)) 192 { 193 String[] populations = paramValue.split(","); 194 if (!ArrayUtils.contains(populations, currentUser.getPopulationId())) 195 { 196 throw new AccessDeniedException("The user " + currentUser + " is not authorized to access file " + path); 197 } 198 } 199 else 200 { 201 // unknown parameter 202 throw new IllegalArgumentException("Unable to evaluate the current site"); 203 } 204 } 205 206 matcher.appendReplacement(newPath, currentUser.getLogin()); 207 } 208 else if ("population".equals(variable)) 209 { 210 if (currentUser == null) 211 { 212 throw new AuthorizationRequiredException(null); 213 } 214 215 matcher.appendReplacement(newPath, currentUser.getPopulationId()); 216 } 217 else if ("site".equals(variable)) 218 { 219 if (siteName == null) 220 { 221 throw new IllegalArgumentException("Unable to evaluate the current site for service directory path " + path); 222 } 223 224 matcher.appendReplacement(newPath, siteName); 225 } 226 else if ("lang".equals(variable)) 227 { 228 if (language == null) 229 { 230 throw new IllegalArgumentException("Unable to evaluate the current language for service directory path " + path); 231 } 232 233 matcher.appendReplacement(newPath, language); 234 } 235 else 236 { 237 matcher.appendReplacement(newPath, "$0"); 238 } 239 } 240 241 matcher.appendTail(newPath); 242 243 return newPath.toString(); 244 } 245}