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.workplace; 029 030import org.opencms.cache.CmsVfsMemoryObjectCache; 031import org.opencms.db.CmsUserSettings; 032import org.opencms.file.CmsFile; 033import org.opencms.file.CmsPropertyDefinition; 034import org.opencms.file.CmsUser; 035import org.opencms.json.JSONException; 036import org.opencms.json.JSONObject; 037import org.opencms.jsp.CmsJspActionElement; 038import org.opencms.main.CmsException; 039import org.opencms.main.CmsLog; 040import org.opencms.ui.CmsVaadinUtils; 041import org.opencms.util.CmsStringUtil; 042import org.opencms.xml.content.CmsXmlContent; 043import org.opencms.xml.content.CmsXmlContentFactory; 044 045import java.io.IOException; 046 047import javax.servlet.http.HttpServletRequest; 048import javax.servlet.http.HttpServletResponse; 049import javax.servlet.jsp.PageContext; 050 051import org.apache.commons.logging.Log; 052 053/** 054 * Provides methods to show a configurable user agreement dialog after a successful workplace login.<p> 055 * 056 * @since 8.0 057 */ 058public class CmsLoginUserAgreement extends CmsDialog { 059 060 /** Value for the action: accept the user agreement. */ 061 public static final int ACTION_ACCEPT = 100; 062 063 /** The dialog type. */ 064 public static final String DIALOG_TYPE = "useragreement"; 065 066 /** Node name for the element: MessageDeclined. */ 067 public static final String NODE_MESSAGE_DECLINED = "MessageDeclined"; 068 069 /** Node name for the element: Text. */ 070 public static final String NODE_TEXT = "Text"; 071 072 /** Request parameter name for the originally requested resource. */ 073 public static final String PARAM_WPRES = "wpres"; 074 075 /** JSON key name to store the count of the accepted agreement. */ 076 protected static final String KEY_ACCEPTED_COUNT = "count"; 077 078 /** JSON key name to store the version of the accepted agreement. */ 079 protected static final String KEY_ACCEPTED_VERSION = "version"; 080 081 /** Node name for the element: AgreeCount. */ 082 protected static final String NODE_AGREE_COUNT = "AgreeCount"; 083 084 /** Node name for the element: ButtonAccept. */ 085 protected static final String NODE_BUTTON_ACCEPT = "ButtonAccept"; 086 087 /** Node name for the element: ButtonDecline. */ 088 protected static final String NODE_BUTTON_DECLINE = "ButtonDecline"; 089 090 /** Node name for the element: DialogTitle. */ 091 protected static final String NODE_DIALOG_TITLE = "DialogTitle"; 092 093 /** Node name for the element: Version. */ 094 protected static final String NODE_VERSION = "Version"; 095 096 /** The VFS path to the folder containing the user agreement configuration files. */ 097 protected static final String VFS_PATH_CONFIGFOLDER = CmsWorkplace.VFS_PATH_SYSTEM + "login/useragreement/"; 098 099 /** The log object for this class. */ 100 private static final Log LOG = CmsLog.getLog(CmsLoginUserAgreement.class); 101 102 /** The number of times the user accepted the agreement. */ 103 private int m_acceptedCount; 104 105 /** The version of the user accepted agreement. */ 106 private double m_acceptedVersion; 107 108 /** The user agreement configuration content. */ 109 private CmsXmlContent m_configurationContent; 110 111 /** The originally requested workplace resource path parameter. */ 112 private String m_paramWpres; 113 114 /** The required version of the user accepted agreement. */ 115 private double m_requiredVersion; 116 117 /** 118 * Public constructor with JSP action element.<p> 119 * 120 * @param jsp an initialized JSP action element 121 */ 122 public CmsLoginUserAgreement(CmsJspActionElement jsp) { 123 124 super(jsp); 125 } 126 127 /** 128 * Public constructor with JSP variables.<p> 129 * 130 * @param context the JSP page context 131 * @param req the JSP request 132 * @param res the JSP response 133 */ 134 public CmsLoginUserAgreement(PageContext context, HttpServletRequest req, HttpServletResponse res) { 135 136 this(new CmsJspActionElement(context, req, res)); 137 } 138 139 /** 140 * Stores the information about the accepted user agreement in the current users additional info.<p> 141 */ 142 public void acceptAgreement() { 143 144 // store accepted flag in current users settings 145 getSettings().setUserAgreementAccepted(true); 146 // check accepted agreement version 147 if (getAcceptedVersion() < getRequiredVersion()) { 148 // a new (higher) version was accepted, increase version to store and reset accepted count 149 setAcceptedVersion(getRequiredVersion()); 150 setAcceptedCount(0); 151 } 152 // increase accepted count 153 setAcceptedCount(getAcceptedCount() + 1); 154 // create the JSON data structure that is stored in the additional info 155 JSONObject jsonData = new JSONObject(); 156 try { 157 jsonData.put(KEY_ACCEPTED_VERSION, getRequiredVersion()); 158 jsonData.put(KEY_ACCEPTED_COUNT, m_acceptedCount); 159 // store accepted data in users additional information 160 CmsUser user = getCms().getRequestContext().getCurrentUser(); 161 user.setAdditionalInfo(CmsUserSettings.LOGIN_USERAGREEMENT_ACCEPTED, jsonData.toString()); 162 // write the changed user 163 getCms().writeUser(user); 164 } catch (Exception e) { 165 LOG.error(e.getLocalizedMessage(), e); 166 } 167 } 168 169 /** 170 * Performs the the user agreement accept action, will be called by the JSP page.<p> 171 * 172 * @throws IOException if problems while redirecting occur 173 */ 174 public void actionAccept() throws IOException { 175 176 acceptAgreement(); 177 // redirect to the originally requested resource 178 getJsp().getResponse().sendRedirect(getJsp().link(getParamWpres())); 179 } 180 181 /** 182 * Performs the user agreement declined action, will be called by the JSP page.<p> 183 * 184 * @throws IOException if problems while redirecting occur 185 */ 186 public void actionDecline() throws IOException { 187 188 getJsp().getRequest().getSession().invalidate(); 189 getJsp().getResponse().sendRedirect(getJsp().link(getParamWpres())); 190 } 191 192 /** 193 * The standard JavaScript for submitting the dialog is overridden to show an alert in case that 194 * an agreement is declined.<p> 195 * 196 * See also {@link CmsDialog#dialogScriptSubmit()} 197 * 198 * @return the standard JavaScript for submitting the dialog 199 */ 200 @Override 201 public String dialogScriptSubmit() { 202 203 if (useNewStyle()) { 204 return super.dialogScriptSubmit(); 205 } 206 StringBuffer result = new StringBuffer(512); 207 result.append("function submitAction(actionValue, theForm, formName) {\n"); 208 result.append("\tif (theForm == null) {\n"); 209 result.append("\t\ttheForm = document.forms[formName];\n"); 210 result.append("\t}\n"); 211 result.append("\ttheForm." + PARAM_FRAMENAME + ".value = window.name;\n"); 212 result.append("\tif (actionValue == \"" + DIALOG_OK + "\") {\n"); 213 result.append("\t\treturn true;\n"); 214 result.append("\t}"); 215 String declinedMessage = getConfigurationContentStringValue(NODE_MESSAGE_DECLINED); 216 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(declinedMessage)) { 217 // add the alert to show the declined message 218 result.append(" else if (actionValue == \"" + DIALOG_CANCEL + "\") {\n"); 219 result.append("\t\talert(\""); 220 result.append(CmsStringUtil.escapeJavaScript(declinedMessage)); 221 result.append("\");\n"); 222 result.append("\t}\n"); 223 } 224 result.append("\ttheForm." + PARAM_ACTION + ".value = actionValue;\n"); 225 result.append("\ttheForm.submit();\n"); 226 result.append("\treturn false;\n"); 227 result.append("}\n"); 228 229 return result.toString(); 230 } 231 232 /** 233 * Returns the number of times the user accepted the agreement.<p> 234 * 235 * @return the number of times the user accepted the agreement 236 */ 237 public int getAcceptedCount() { 238 239 return m_acceptedCount; 240 } 241 242 /** 243 * Returns the version of the user accepted agreement.<p> 244 * 245 * @return the version of the user accepted agreement 246 */ 247 public double getAcceptedVersion() { 248 249 return m_acceptedVersion; 250 } 251 252 /** 253 * Returns the content value of the given path as String.<p> 254 * 255 * @param path the path to get the content value for 256 * 257 * @return the content value of the given path as String 258 */ 259 public String getConfigurationContentStringValue(String path) { 260 261 if (getConfigurationContent() != null) { 262 return getConfigurationContent().getStringValue(getCms(), path, getLocale()); 263 } 264 return ""; 265 } 266 267 /** 268 * Returns the absolute path in the OpenCms VFS to the user agreement configuration file.<p> 269 * 270 * @return the absolute path in the OpenCms VFS to the user agreement configuration file 271 */ 272 public String getConfigurationVfsPath() { 273 274 return VFS_PATH_CONFIGFOLDER + getLocale().toString() + "/configuration.html"; 275 } 276 277 /** 278 * Returns the originally requested workplace resource path parameter.<p> 279 * 280 * @return the originally requested workplace resource path parameter 281 */ 282 public String getParamWpres() { 283 284 if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_paramWpres) || "null".equals(m_paramWpres)) { 285 return CmsVaadinUtils.getWorkplaceLink(); 286 } 287 return m_paramWpres; 288 } 289 290 /** 291 * Returns the required version of the user accepted agreement.<p> 292 * 293 * @return the required version of the user accepted agreement 294 */ 295 public double getRequiredVersion() { 296 297 if (m_requiredVersion == 0) { 298 String versionStr = getConfigurationContentStringValue(NODE_VERSION); 299 try { 300 m_requiredVersion = Double.parseDouble(versionStr); 301 } catch (Exception e) { 302 // the version number is not in the correct format 303 LOG.error( 304 Messages.get().getBundle().key( 305 Messages.LOG_USERAGREEMENT_WRONG_VERSION_2, 306 versionStr, 307 getConfigurationContent().getFile().getRootPath())); 308 } 309 } 310 return m_requiredVersion; 311 } 312 313 /** 314 * Returns if the user agreement page should be shown for the current user.<p> 315 * 316 * @return <code>true</code> if the user agreement page should be shown for the current user, otherwise <code>false</code> 317 */ 318 public boolean isShowUserAgreement() { 319 320 if (!getSettings().isUserAgreementAccepted() && (getConfigurationContent() != null)) { 321 CmsXmlContent content = getConfigurationContent(); 322 boolean enabled = false; 323 try { 324 // first read the property that contains the information if the agreement is enabled at all 325 enabled = Boolean.valueOf( 326 getCms().readPropertyObject( 327 content.getFile(), 328 CmsPropertyDefinition.PROPERTY_LOGIN_FORM, 329 false).getValue()).booleanValue(); 330 if (enabled) { 331 // user agreement is enabled, now check version and accepted count 332 if (content.hasLocale(getLocale())) { 333 if (getAcceptedVersion() < getRequiredVersion()) { 334 // new user agreement version that has to be shown 335 return true; 336 } else { 337 // check how often the user accepted the user agreement 338 String countStr = content.getStringValue(getCms(), NODE_AGREE_COUNT, getLocale()); 339 int count = Integer.parseInt(countStr); 340 if ((count == -1) || (getAcceptedCount() < count)) { 341 // user still has to accept the user agreement 342 return true; 343 } 344 } 345 } 346 } 347 } catch (Exception e) { 348 // error when trying to determine if user agreement should be shown 349 LOG.error( 350 Messages.get().getBundle().key( 351 Messages.LOG_USERAGREEMENT_SHOW_1, 352 getConfigurationContent().getFile().getRootPath()), 353 e); 354 } 355 356 } 357 // store information that nothing has to be accepted in users session for better performance 358 if (!getSettings().isUserAgreementAccepted()) { 359 getSettings().setUserAgreementAccepted(true); 360 } 361 return false; 362 } 363 364 /** 365 * Sets the number of times the user accepted the agreement.<p> 366 * 367 * @param acceptedCount the number of times the user accepted the agreement 368 */ 369 public void setAcceptedCount(int acceptedCount) { 370 371 m_acceptedCount = acceptedCount; 372 } 373 374 /** 375 * Sets the version of the user accepted agreement.<p> 376 * 377 * @param acceptedVersion the version of the user accepted agreement 378 */ 379 public void setAcceptedVersion(double acceptedVersion) { 380 381 m_acceptedVersion = acceptedVersion; 382 } 383 384 /** 385 * Sets the originally requested workplace resource path parameter.<p> 386 * 387 * @param paramWpres the originally requested workplace resource path parameter 388 */ 389 public void setParamWpres(String paramWpres) { 390 391 m_paramWpres = paramWpres; 392 } 393 394 /** 395 * Sets the required version of the user accepted agreement.<p> 396 * 397 * @param requiredVersion the required version of the user accepted agreement 398 */ 399 public void setRequiredVersion(double requiredVersion) { 400 401 m_requiredVersion = requiredVersion; 402 } 403 404 /** 405 * The standard "OK" and "Cancel" buttons are overridden to show other labels.<p> 406 * 407 * See also {@link CmsDialog#dialogButtonsHtml(StringBuffer, int, String)} 408 * 409 * @param result a string buffer where the rendered HTML gets appended to 410 * @param button a integer key to identify the button 411 * @param attribute an optional string with possible tag attributes, or null 412 */ 413 @Override 414 protected void dialogButtonsHtml(StringBuffer result, int button, String attribute) { 415 416 attribute = appendDelimiter(attribute); 417 418 switch (button) { 419 case BUTTON_OK: 420 result.append("<input name=\"ok\" value=\""); 421 result.append(getConfigurationContentStringValue(NODE_BUTTON_ACCEPT)); 422 result.append("\""); 423 if (attribute.toLowerCase().indexOf("onclick") == -1) { 424 result.append(" type=\"submit\""); 425 } else { 426 result.append(" type=\"button\""); 427 } 428 result.append(" class=\"dialogbutton\""); 429 result.append(attribute); 430 result.append(">\n"); 431 break; 432 case BUTTON_CANCEL: 433 result.append("<input name=\"cancel\" type=\"button\" value=\""); 434 result.append(getConfigurationContentStringValue(NODE_BUTTON_DECLINE)); 435 result.append("\""); 436 if (attribute.toLowerCase().indexOf("onclick") == -1) { 437 result.append(" onclick=\"submitAction('" + DIALOG_CANCEL + "', form);\""); 438 } 439 result.append(" class=\"dialogbutton\""); 440 result.append(attribute); 441 result.append(">\n"); 442 break; 443 default: 444 // other buttons are not overridden, call super implementation 445 super.dialogButtonsHtml(result, button, attribute); 446 } 447 } 448 449 /** 450 * Returns the user agreement configuration content.<p> 451 * 452 * @return the user agreement configuration content 453 */ 454 protected CmsXmlContent getConfigurationContent() { 455 456 if (m_configurationContent == null) { 457 String configFileName = getConfigurationVfsPath(); 458 if (getCms().existsResource(configFileName)) { 459 // configuration file found, check VFS cache for unmarshalled content 460 CmsVfsMemoryObjectCache vfsCache = CmsVfsMemoryObjectCache.getVfsMemoryObjectCache(); 461 m_configurationContent = (CmsXmlContent)vfsCache.getCachedObject(getCms(), configFileName); 462 if (m_configurationContent == null) { 463 // content not found in cache, read the file and unmarshal it 464 try { 465 CmsFile configFile = getCms().readFile(configFileName); 466 CmsXmlContent content = CmsXmlContentFactory.unmarshal(getCms(), configFile); 467 // put the result content in the cache 468 vfsCache.putCachedObject(getCms(), configFileName, content); 469 m_configurationContent = content; 470 } catch (CmsException e) { 471 // should never happen, because we checked the resource existence before 472 } 473 } 474 } 475 } 476 return m_configurationContent; 477 } 478 479 /** 480 * Initializes the 'accepted' data from the current user.<p> 481 * Returns the absolute path in the OpenCms VFS to the user agreement configuration file.<p> 482 */ 483 protected void initAcceptData() { 484 485 // read the current users agreement values 486 CmsUser user = getCms().getRequestContext().getCurrentUser(); 487 String result = (String)user.getAdditionalInfo(CmsUserSettings.LOGIN_USERAGREEMENT_ACCEPTED); 488 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(result)) { 489 // read JSON data structure that is stored in the user additional info 490 try { 491 JSONObject jsonData = new JSONObject(result); 492 m_acceptedVersion = jsonData.getDouble(KEY_ACCEPTED_VERSION); 493 m_acceptedCount = jsonData.getInt(KEY_ACCEPTED_COUNT); 494 } catch (JSONException e) { 495 LOG.error(e.getLocalizedMessage(), e); 496 } 497 } 498 } 499 500 /** 501 * @see org.opencms.workplace.CmsWorkplace#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, javax.servlet.http.HttpServletRequest) 502 */ 503 @Override 504 protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) { 505 506 // fill the parameter values in the get/set methods 507 fillParamValues(request); 508 509 initAcceptData(); 510 511 // set the dialog type 512 setParamDialogtype(DIALOG_TYPE); 513 // set the action for the JSP switch 514 if (DIALOG_TYPE.equals(getParamAction())) { 515 setAction(ACTION_ACCEPT); 516 } else if (DIALOG_CANCEL.equals(getParamAction())) { 517 setAction(ACTION_CANCEL); 518 } else { 519 setAction(ACTION_DEFAULT); 520 // build the title for the user agreement dialog 521 setParamTitle(getConfigurationContentStringValue(NODE_DIALOG_TITLE)); 522 } 523 } 524 525}