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