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