001/* 002 * Copyright 2016 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.site; 017 018import java.io.ByteArrayInputStream; 019import java.io.ByteArrayOutputStream; 020import java.nio.charset.StandardCharsets; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.Enumeration; 025import java.util.List; 026import java.util.Locale; 027import java.util.Map; 028import java.util.Set; 029import java.util.regex.Pattern; 030 031import org.apache.avalon.framework.configuration.Configuration; 032import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 033import org.apache.avalon.framework.parameters.Parameters; 034import org.apache.cocoon.environment.ObjectModelHelper; 035import org.apache.cocoon.environment.Redirector; 036import org.apache.cocoon.environment.Request; 037import org.apache.cocoon.environment.Session; 038import org.apache.cocoon.environment.http.HttpCookie; 039import org.apache.commons.lang3.StringUtils; 040import org.apache.http.NameValuePair; 041import org.apache.http.client.entity.UrlEncodedFormEntity; 042import org.apache.http.client.methods.CloseableHttpResponse; 043import org.apache.http.client.methods.HttpGet; 044import org.apache.http.client.methods.HttpPost; 045import org.apache.http.impl.client.CloseableHttpClient; 046import org.apache.http.message.BasicNameValuePair; 047 048import org.ametys.core.authentication.AuthenticateAction; 049import org.ametys.core.authentication.CredentialProvider; 050import org.ametys.core.user.UserIdentity; 051import org.ametys.plugins.core.user.UserDAO; 052import org.ametys.plugins.site.Site; 053import org.ametys.runtime.config.Config; 054import org.ametys.runtime.workspace.WorkspaceMatcher; 055 056/** 057 * The authenticate action for front side 058 */ 059public class FrontAuthenticateAction extends AuthenticateAction 060{ 061 /** url requires for authentication */ 062 protected Collection<Pattern> _acceptedSiteUrlPatterns = Arrays.asList(new Pattern[]{Pattern.compile("^plugins/site/authenticate/[0-9]+$")}); 063 064 @Override 065 protected boolean _acceptedUrl(Request request) 066 { 067 // URL without server context and leading slash. 068 String url = (String) request.getAttribute(WorkspaceMatcher.IN_WORKSPACE_URL); 069 for (Pattern pattern : _acceptedSiteUrlPatterns) 070 { 071 if (pattern.matcher(url).matches()) 072 { 073 // Anonymous request 074 request.setAttribute(REQUEST_ATTRIBUTE_GRANTED, true); 075 076 return true; 077 } 078 } 079 080 return false; 081 } 082 083 @Override 084 protected void _setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode) 085 { 086 setUserIdentityInSession(request, userIdentity, credentialProvider, blockingMode); 087 } 088 089 /** 090 * Save user identity in request 091 * @param request The request 092 * @param userIdentity The useridentity to save 093 * @param credentialProvider The credential provider used to connect 094 * @param blockingMode The mode used for the credential provider 095 */ 096 public static void setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode) 097 { 098 Site site = (Site) request.getAttribute("site"); 099 String siteName = site.getName(); 100 101 Session session = renewSession(request); 102 _resetConnectingStateToSession(request); 103 session.setAttribute(SESSION_USERIDENTITY + "-" + siteName, userIdentity); 104 session.setAttribute(SESSION_CREDENTIALPROVIDER + "-" + siteName, credentialProvider); 105 session.setAttribute(SESSION_CREDENTIALPROVIDER_MODE + "-" + siteName, blockingMode); 106 } 107 108 @Override 109 protected UserIdentity _getUserIdentityFromSession(Request request) 110 { 111 UserIdentity userIdentityFromSession = getUserIdentityFromSession(request); 112 if (userIdentityFromSession != null) 113 { 114 return userIdentityFromSession; 115 } 116 117 Session session = request.getSession(false); 118 if (session != null) 119 { 120 Set<String> availableUserPopulationsIds = _getAvailableUserPopulationsIds(request, _getContexts(request, null)); 121 122 // check if connected in the site application, not only on this site 123 userIdentityFromSession = super._getUserIdentityFromSession(request); 124 if (userIdentityFromSession != null && availableUserPopulationsIds.contains(userIdentityFromSession.getPopulationId())) 125 { 126 _setUserIdentityInSession(request, userIdentityFromSession, new UserDAO.ImpersonateCredentialProvider(), true); 127 return userIdentityFromSession; 128 } 129 130 Enumeration<String> attributeNames = session.getAttributeNames(); 131 while (attributeNames.hasMoreElements()) 132 { 133 String attributeName = attributeNames.nextElement(); 134 if (attributeName.startsWith(SESSION_USERIDENTITY + "-")) 135 { 136 UserIdentity userIdentity = (UserIdentity) session.getAttribute(attributeName); 137 138 if (availableUserPopulationsIds.contains(userIdentity.getPopulationId())) 139 { 140 _setUserIdentityInSession(request, userIdentity, new UserDAO.ImpersonateCredentialProvider(), true); 141 return userIdentity; 142 } 143 } 144 } 145 } 146 147 return null; 148 } 149 150 @Override 151 protected void _handleWeakPassord(Request request, Redirector redirector, UserIdentity userIdentity) throws Exception 152 { 153 Site site = (Site) request.getAttribute("site"); 154 155 String defaultLanguage = "en"; 156 Locale local = request.getLocale(); 157 158 String prefLanguage = local != null ? local.getLanguage() : defaultLanguage; 159 160 String weakPasswordUrl = site.getWeakPasswordUrl(prefLanguage); 161 if (weakPasswordUrl == null) 162 { 163 // try to get weak password from local language 164 weakPasswordUrl = site.getWeakPasswordUrl(prefLanguage); 165 } 166 167 if (weakPasswordUrl == null) 168 { 169 // try to get weak password from default language 170 weakPasswordUrl = site.getWeakPasswordUrl(defaultLanguage); 171 } 172 173 if (weakPasswordUrl == null) 174 { 175 for (String lang : site.getLanguages()) 176 { 177 weakPasswordUrl = site.getWeakPasswordUrl(lang); 178 if (weakPasswordUrl != null) 179 { 180 break; 181 } 182 } 183 } 184 185 if (weakPasswordUrl != null) 186 { 187 // Force redirect to weak password url to force change password 188 getLogger().info("Password of user " + userIdentity + " does not meet the security requirements. Force to change password."); 189 redirector.redirect(false, weakPasswordUrl + "&userIdentity=" + UserIdentity.userIdentityToString(userIdentity)); 190 } 191 else 192 { 193 // No weak password url is available for this site, user will be authenticated 194 super._handleWeakPassord(request, redirector, userIdentity); 195 } 196 } 197 198 /** 199 * Get the user identity of the connected user from the session 200 * @param request The request 201 * @return The connected useridentity or null 202 */ 203 public static UserIdentity getUserIdentityFromSession(Request request) 204 { 205 Site site = (Site) request.getAttribute("site"); 206 String siteName = site.getName(); 207 208 return getUserIdentityFromSession(request, siteName); 209 } 210 211 /** 212 * Get the user identity of the connected user from the session 213 * @param request The request 214 * @param siteName The current site name 215 * @return The connected useridentity or null 216 */ 217 public static UserIdentity getUserIdentityFromSession(Request request, String siteName) 218 { 219 Session session = request.getSession(false); 220 if (session != null) 221 { 222 return (UserIdentity) session.getAttribute(SESSION_USERIDENTITY + "-" + siteName); 223 } 224 return null; 225 } 226 227 @Override 228 protected CredentialProvider _getCredentialProviderFromSession(Request request) 229 { 230 return getCredentialProviderFromSession(request); 231 } 232 233 /** 234 * Get the credential provider used for the current connection 235 * @param request The request 236 * @return The credential provider used or null 237 */ 238 public static CredentialProvider getCredentialProviderFromSession(Request request) 239 { 240 Site site = (Site) request.getAttribute("site"); 241 242 if (site == null) 243 { 244 return null; 245 } 246 247 String siteName = site.getName(); 248 249 return getCredentialProviderFromSession(request, siteName); 250 } 251 252 /** 253 * Get the credential provider used for the current connection 254 * @param request The request 255 * @param siteName The current site name 256 * @return The credential provider used or null 257 */ 258 public static CredentialProvider getCredentialProviderFromSession(Request request, String siteName) 259 { 260 Session session = request.getSession(false); 261 if (session != null) 262 { 263 return (CredentialProvider) session.getAttribute(SESSION_CREDENTIALPROVIDER + "-" + siteName); 264 } 265 return null; 266 } 267 268 @Override 269 protected Boolean _getCredentialProviderModeFromSession(Request request) 270 { 271 return getCredentialProviderModeFromSession(request); 272 } 273 274 /** 275 * Get the credential provider mode used for the current connection 276 * @param request The request 277 * @return The credential provider mode used or null 278 */ 279 public static Boolean getCredentialProviderModeFromSession(Request request) 280 { 281 Site site = (Site) request.getAttribute("site"); 282 String siteName = site.getName(); 283 284 return getCredentialProviderModeFromSession(request, siteName); 285 } 286 287 /** 288 * Get the credential provider mode used for the current connection 289 * @param request The request 290 * @param siteName The current site name 291 * @return The credential provider mode used or null 292 */ 293 public static Boolean getCredentialProviderModeFromSession(Request request, String siteName) 294 { 295 Session session = request.getSession(false); 296 if (session != null) 297 { 298 return (Boolean) session.getAttribute(SESSION_CREDENTIALPROVIDER_MODE + "-" + siteName); 299 } 300 return null; 301 } 302 303 @Override 304 protected List<String> _getContexts(Request request, Parameters parameters) 305 { 306 Site site = (Site) request.getAttribute("site"); 307 String siteName = site.getName(); 308 return Arrays.asList("/sites/" + siteName, "/sites-fo/" + siteName); 309 } 310 311 @Override 312 protected String getLoginURL(Request request) 313 { 314 Site site = (Site) request.getAttribute("site"); 315 String siteName = site.getName(); 316 317 return getLoginURLParameters(request, "cocoon://_generate/plugins/web/frontoffice-formbasedauthentication/login/login/" + siteName); 318 } 319 320 @Override 321 protected String getLogoutURL(Request request) 322 { 323 Site site = (Site) request.getAttribute("site"); 324 String siteName = site.getName(); 325 return "cocoon://_generate/plugins/web/frontoffice-formbasedauthentication/login/logout/" + siteName; 326 } 327 328 @Override 329 protected boolean _handleLogout(Redirector redirector, Map objectModel, String source, Parameters parameters) throws Exception 330 { 331 boolean logout = super._handleLogout(redirector, objectModel, source, parameters); 332 if (logout) 333 { 334 // Additionally we need to destroy potential server side session 335 Request request = ObjectModelHelper.getRequest(objectModel); 336 HttpCookie sessionId = (HttpCookie) request.getCookieMap().get(GeneratePageAction.__BACKOFFICE_JSESSION_ID); 337 if (sessionId != null) 338 { 339 try (CloseableHttpClient httpClient = BackOfficeRequestHelper.getHttpClient()) 340 { 341 String cmsURL = Config.getInstance().getValue("org.ametys.site.bo"); 342 HttpGet httpGet = new HttpGet(cmsURL + "/logout.html"); 343 httpGet.addHeader("Cookie", "JSESSIONID=" + sessionId.getValue()); 344 httpClient.execute(httpGet); 345 } 346 } 347 } 348 return logout; 349 } 350 351 @Override 352 protected UserIdentity _validateToken(String token, String context) 353 { 354 // Get site names and URLs from the CMS 355 String cmsURL = Config.getInstance().getValue("org.ametys.site.bo"); 356 357 try (CloseableHttpClient httpClient = BackOfficeRequestHelper.getHttpClient()) 358 { 359 HttpPost httpPost = new HttpPost(cmsURL + "/_validate_token.xml"); 360 httpPost.addHeader("X-Ametys-FO", "true"); 361 362 List<NameValuePair> nvps = new ArrayList<>(); 363 nvps.add(new BasicNameValuePair("token", token)); 364 nvps.add(new BasicNameValuePair("tokenContext", context)); 365 httpPost.setEntity(new UrlEncodedFormEntity(nvps, StandardCharsets.UTF_8)); 366 367 try (CloseableHttpResponse response = httpClient.execute(httpPost); ByteArrayOutputStream os = new ByteArrayOutputStream()) 368 { 369 switch (response.getStatusLine().getStatusCode()) 370 { 371 case 200: 372 break; 373 374 case 403: 375 throw new IllegalStateException("The CMS back-office refused the connection"); 376 377 case 500: 378 default: 379 throw new IllegalStateException("The CMS back-office returned an error"); 380 } 381 382 response.getEntity().writeTo(os); 383 try (ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray())) 384 { 385 Configuration conf = new DefaultConfigurationBuilder().build(is); 386 387 String login = conf.getChild("login").getValue(null); 388 String populationId = conf.getChild("populationId").getValue(null); 389 390 if (StringUtils.isNoneBlank(login, populationId)) 391 { 392 return new UserIdentity(login, populationId); 393 } 394 else 395 { 396 return null; 397 } 398 } 399 } 400 } 401 catch (Exception e) 402 { 403 throw new RuntimeException("Unable to synchronize site data", e); 404 } 405 } 406}