001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.userdata; 029 030import org.opencms.ade.detailpage.CmsDetailPageInfo; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProperty; 033import org.opencms.file.CmsPropertyDefinition; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsUser; 036import org.opencms.i18n.CmsEncoder; 037import org.opencms.i18n.CmsMessages; 038import org.opencms.jsp.util.CmsJspStandardContextBean; 039import org.opencms.main.CmsException; 040import org.opencms.main.CmsLog; 041import org.opencms.main.OpenCms; 042import org.opencms.security.CmsAuthentificationException; 043import org.opencms.security.CmsOrganizationalUnit; 044import org.opencms.util.CmsCollectionsGenericWrapper; 045import org.opencms.util.CmsStringUtil; 046 047import java.util.Collections; 048import java.util.HashMap; 049import java.util.List; 050import java.util.Map; 051import java.util.Optional; 052 053import javax.mail.internet.AddressException; 054 055import org.apache.commons.logging.Log; 056import org.apache.commons.mail.EmailException; 057 058/** 059 * Bean used by the dynamic function JSP for user data requests.<p> 060 * 061 * The dynamic function calls the action() method on this bean, which returns the new state for the JSP to render. 062 * Error/status messages are also available to the JSP via getters. 063 */ 064public class CmsJspUserDataRequestBean { 065 066 /** 067 * Represents the UI state which should be shown by the dynamic function JSP. 068 */ 069 enum State { 070 /** Generic error occurred that can't be meaningfully displayed in another state. */ 071 error, 072 073 /** State showing the data request form with user / email / password fields, and possibly an error message. */ 074 form, 075 /** Success state after submitting the form. */ 076 formOk, 077 078 /** State which is shown when the link from the email is opened. */ 079 view, 080 081 /** State which is shown when the user confirms their credentials after clicking on the email link. */ 082 viewOk 083 } 084 085 /** Action id. */ 086 public static final String ACTION_REQUEST = "request"; 087 088 /** Action id. */ 089 public static final String ACTION_VIEW = "view"; 090 091 /** Action id. */ 092 public static final String ACTION_VIEWAUTH = "viewauth"; 093 094 /** Request parameter for the action. */ 095 public static final String PARAM_ACTION = "action"; 096 097 /** Request parameter for the authorization code. */ 098 public static final String PARAM_AUTH = "auth"; 099 100 /** Request parameter. */ 101 public static final String PARAM_EMAIL = "email"; 102 103 /** Request parameter. */ 104 public static final String PARAM_PASSWORD = "password"; 105 106 /** Request parameter. */ 107 public static final String PARAM_ROOTPATH = "rootpath"; 108 109 /** Request parameter. */ 110 public static final String PARAM_UDRID = "udrid"; 111 112 /** Request parameter. */ 113 public static final String PARAM_USER = "user"; 114 115 /** The logger instance for the class. */ 116 private static final Log LOG = CmsLog.getLog(CmsJspUserDataRequestBean.class); 117 118 /** The configuration. */ 119 private CmsUserDataRequestConfig m_config; 120 121 /** The download link (only available if credentials have been confirmed). */ 122 private String m_downloadLink; 123 124 /** Error HTML (only shown directly in error state). */ 125 private String m_errorHtml; 126 127 /** The user data request info. */ 128 private CmsUserDataRequestInfo m_info; 129 130 /** The user data HTML (only available if credentials have been confirmed). */ 131 private String m_infoHtml; 132 133 /** The request parameters (duplicate parameters are removed). */ 134 private Map<String, String> m_params; 135 136 /** Lazy map to access messages. */ 137 private Map<String, String> m_texts; 138 139 /** 140 * Creates a new instance. 141 */ 142 public CmsJspUserDataRequestBean() { 143 144 LOG.debug("Creating user data request bean."); 145 } 146 147 /** 148 * Called by the user data request function JSP to handle the user data request logic. 149 * 150 * Returns the next state which should be shown by the JSP. 151 * 152 * @param cms the CMS context 153 * @param reqParameters the request parameters 154 * @return the next state to render 155 * 156 * @throws CmsException if something goes wrong 157 */ 158 public String action(CmsObject cms, Map<String, String[]> reqParameters) throws CmsException { 159 160 init(reqParameters); 161 162 CmsResource page = cms.readResource(cms.getRequestContext().getUri()); 163 List<CmsProperty> props = cms.readPropertyObjects(page, true); 164 165 CmsProperty defaultOuProp = CmsProperty.get(CmsPropertyDefinition.PROPERTY_UDR_DEFAULTOU, props); 166 String configuredOu = null; 167 if (defaultOuProp != null) { 168 169 configuredOu = defaultOuProp.getValue(); 170 } 171 CmsProperty configPathProp = CmsProperty.get(CmsPropertyDefinition.PROPERTY_UDR_CONFIG, props); 172 String configPath = configPathProp.getValue(); 173 174 CmsUserDataRequestManager manager = OpenCms.getUserDataRequestManager(); 175 CmsMessages messages = Messages.get().getBundle(cms.getRequestContext().getLocale()); 176 m_config = manager.loadConfig(cms, configPath).orElse(null); 177 if (m_config == null) { 178 m_errorHtml = messages.key(Messages.ERR_CONFIG_NOT_SET_0); 179 return State.error.toString(); 180 } 181 String functionDetail = CmsJspStandardContextBean.getFunctionDetailLink( 182 cms, 183 CmsDetailPageInfo.FUNCTION_PREFIX, 184 CmsUserDataRequestManager.FUNCTION_NAME, 185 true); 186 if (functionDetail.contains("[")) { 187 m_errorHtml = messages.key(Messages.ERR_FUNCTION_DETAIL_PAGE_NOT_SET_0); 188 return State.error.toString(); 189 } 190 191 if (!CmsUserDataResourceHandler.isInitialized()) { 192 m_errorHtml = messages.key(Messages.ERR_RESOURCE_INIT_HANDLER_NOT_CONFIGURED_0); 193 return State.error.toString(); 194 } 195 String email = m_params.get(PARAM_EMAIL); 196 String user = m_params.get(PARAM_USER); 197 String password = m_params.get(PARAM_PASSWORD); 198 String path = m_params.get(PARAM_ROOTPATH); 199 String udrid = CmsEncoder.escapeXml(m_params.get(PARAM_UDRID)); 200 boolean hasEmail = !CmsStringUtil.isEmptyOrWhitespaceOnly(email); 201 boolean hasUser = !CmsStringUtil.isEmptyOrWhitespaceOnly(user); 202 boolean hasPassword = !CmsStringUtil.isEmptyOrWhitespaceOnly(password); 203 String action = CmsEncoder.escapeXml(m_params.get(PARAM_ACTION)); 204 if (!CmsStringUtil.isEmpty(udrid)) { 205 m_info = manager.getRequestStore().load(udrid).orElse(null); 206 } 207 if (action == null) { 208 return State.form.toString(); 209 } else if (ACTION_REQUEST.equals(action)) { 210 try { 211 if (hasEmail && !hasUser && !hasPassword) { 212 manager.startUserDataRequest(cms, m_config, email); 213 return State.formOk.toString(); 214 } else if (!hasEmail && hasUser && hasPassword) { 215 if (CmsStringUtil.isEmpty(path)) { 216 path = cms.getRequestContext().addSiteRoot(cms.getRequestContext().getUri()); 217 } else { 218 path = path.trim(); 219 } 220 Optional<CmsUser> optUser = lookupUser(cms, configuredOu, path, user, password); 221 if (optUser.isPresent()) { 222 manager.startUserDataRequest(cms, m_config, optUser.get()); 223 return State.formOk.toString(); 224 } else { 225 throw new CmsUserDataRequestException("Could not find user."); 226 } 227 } else { 228 m_errorHtml = "invalid combination of parameters."; 229 return State.form.toString(); 230 } 231 } catch (CmsUserDataRequestException e) { 232 m_errorHtml = e.getLocalizedMessage(); 233 return State.form.toString(); 234 } catch (EmailException | AddressException e) { 235 LOG.warn(e.getLocalizedMessage(), e); 236 m_errorHtml = m_config.getText("EmailError"); 237 return State.error.toString(); 238 } 239 } else if (ACTION_VIEW.equals(action)) { 240 CmsUserDataRequestInfo info = manager.getRequestStore().load(udrid).orElse(null); 241 if ((info == null) || info.isExpired()) { 242 m_errorHtml = m_config.getText("InvalidLink"); 243 return State.error.toString(); 244 } 245 return State.view.toString(); 246 } else if (ACTION_VIEWAUTH.equals(action)) { 247 CmsUserDataRequestStore store = manager.getRequestStore(); 248 CmsUserDataRequestInfo info = store.load(udrid).orElse(null); 249 if ((info == null) || info.isExpired()) { 250 m_errorHtml = m_config.getText("InvalidLink"); 251 return State.error.toString(); 252 } 253 m_infoHtml = info.getInfoHtml(); 254 boolean origForceAbsolute = cms.getRequestContext().isForceAbsoluteLinks(); 255 cms.getRequestContext().setForceAbsoluteLinks(true); 256 try { 257 m_downloadLink = OpenCms.getLinkManager().substituteLinkForUnknownTarget( 258 cms, 259 CmsUserDataResourceHandler.PREFIX + info.getId() + "?" + PARAM_AUTH + "=" + info.getAuthCode()); 260 } finally { 261 cms.getRequestContext().setForceAbsoluteLinks(origForceAbsolute); 262 } 263 CmsUserDataRequestType type = info.getType(); 264 if (CmsUserDataRequestType.email.equals(type)) { 265 if (email == null) { 266 return State.view.toString(); 267 } else if (email.equals(info.getEmail())) { 268 return State.viewOk.toString(); 269 } else { 270 m_errorHtml = "email address error"; 271 return State.view.toString(); 272 } 273 } else if (CmsUserDataRequestType.singleUser.equals(type)) { 274 if (hasUser && hasPassword) { 275 try { 276 CmsObject cloneCms = OpenCms.initCmsObject(cms); 277 String fullName = info.getUserName(); 278 CmsUser readUser = cms.readUser(fullName); 279 if (!readUser.getSimpleName().equals(user)) { 280 m_errorHtml = "login error"; 281 return State.view.toString(); 282 } 283 cloneCms.loginUser(info.getUserName(), password); 284 return State.viewOk.toString(); 285 } catch (CmsException e) { 286 LOG.info(e.getLocalizedMessage(), e); 287 m_errorHtml = "login error"; 288 return State.view.toString(); 289 } 290 291 } 292 m_errorHtml = "authentication error"; 293 return State.view.toString(); 294 } 295 296 } else { 297 m_errorHtml = "Invalid action: " + action; 298 LOG.info("Invalid action: " + action); 299 return State.error.toString(); 300 } 301 return State.form.toString(); 302 303 } 304 305 /** 306 * Gets the user data download link. 307 * 308 * @return the user data download link 309 */ 310 public String getDownloadLink() { 311 312 return m_downloadLink; 313 } 314 315 /** 316 * Gets the error HTML (this is only shown in the error state). 317 * 318 * @return the error HTML 319 */ 320 public String getErrorHtml() { 321 322 return m_errorHtml; 323 } 324 325 /** 326 * Gets the user data HTML.<p> 327 * 328 * @return the user data HTML 329 */ 330 public String getInfoHtml() { 331 332 return m_infoHtml; 333 } 334 335 /** 336 * Gets the lazy map used to access the configurable texts to display by the user data request dynamic function. 337 * 338 * @return the lazy map 339 */ 340 public Map<String, String> getTexts() { 341 342 if (m_texts == null) { 343 m_texts = CmsCollectionsGenericWrapper.createLazyMap(obj -> { 344 String key = (String)obj; 345 return m_config.getText(key); 346 347 }); 348 } 349 return m_texts; 350 351 } 352 353 /** 354 * Checks if the error HTML has been set. 355 * 356 * @return true if the error HTML has been set 357 */ 358 public boolean isError() { 359 360 return m_errorHtml != null; 361 } 362 363 /** 364 * Checks if we need to confirm the user's email rather than their user name and password. 365 * 366 * @return true if we only need the email address 367 */ 368 public boolean isOnlyEmailRequired() { 369 370 return CmsUserDataRequestType.email == m_info.getType(); 371 } 372 373 /** 374 * Initializes the request parameters by throwing out duplicates. 375 * 376 * @param requestParams the request parameters 377 */ 378 private void init(Map<String, String[]> requestParams) { 379 380 m_params = new HashMap<>(); 381 for (Map.Entry<String, String[]> entry : requestParams.entrySet()) { 382 String[] vals = entry.getValue(); 383 if (vals.length > 0) { 384 String val = vals[0]; 385 m_params.put(entry.getKey(), val); 386 } 387 } 388 } 389 390 /** 391 * Looks up the 'best' user for the given user name and password. 392 * 393 * <p>The 'best' in this case is found by walking up the OUs, from the most specific to the root OU, which contain the current URI 394 * and trying to log in the user with the given name and OU in each of them. The first OU+User combination for which the login is successful 395 * is returned. 396 * 397 * @param cms the CMS context 398 * @param configuredOu the configured OU 399 * @param path the path to use for looking up the OU if the OU is not given 400 * @param user the user 401 * @param password the password 402 * 403 * @return the found user 404 * 405 * @throws CmsException if something goes wrong 406 */ 407 private Optional<CmsUser> lookupUser(CmsObject cms, String configuredOu, String path, String user, String password) 408 throws CmsException { 409 410 List<CmsOrganizationalUnit> ous; 411 if (configuredOu != null) { 412 ous = Collections.singletonList(OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, configuredOu)); 413 } else { 414 ous = OpenCms.getOrgUnitManager().getOrgUnitsForResource(cms, path); 415 } 416 CmsObject loginCms = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest()); 417 for (CmsOrganizationalUnit ou : ous) { 418 String fullName = CmsStringUtil.joinPaths(ou.getName(), user); 419 try { 420 loginCms.loginUser(fullName, password); 421 return Optional.of(loginCms.getRequestContext().getCurrentUser()); 422 } catch (CmsAuthentificationException e) { 423 LOG.debug(e.getLocalizedMessage(), e); 424 } 425 } 426 return Optional.empty(); 427 } 428 429}