001/* 002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * Wojciech Sulejman 018 */ 019package org.nuxeo.ecm.platform.signature.web.sign; 020 021import java.io.IOException; 022import java.io.OutputStream; 023import java.io.Serializable; 024 025import javax.faces.application.FacesMessage; 026import javax.faces.context.FacesContext; 027import javax.faces.validator.ValidatorException; 028import javax.servlet.http.HttpServletResponse; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.jboss.seam.ScopeType; 033import org.jboss.seam.annotations.In; 034import org.jboss.seam.annotations.Name; 035import org.jboss.seam.annotations.Scope; 036import org.jboss.seam.faces.FacesMessages; 037import org.jboss.seam.international.StatusMessage; 038import org.nuxeo.ecm.core.api.CoreSession; 039import org.nuxeo.ecm.core.api.DocumentModel; 040import org.nuxeo.ecm.core.api.NuxeoPrincipal; 041import org.nuxeo.ecm.platform.signature.api.exception.CertException; 042import org.nuxeo.ecm.platform.signature.api.pki.CertService; 043import org.nuxeo.ecm.platform.signature.api.user.CUserService; 044import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 045import org.nuxeo.ecm.platform.ui.web.api.WebActions; 046import org.nuxeo.ecm.platform.usermanager.UserManager; 047import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor; 048 049/** 050 * Certificate management actions exposed as a Seam component. Used for launching certificate generation, storage and 051 * retrieving operations from low level services. Allows verifying if a user certificate is already present. 052 * 053 * @author <a href="mailto:[email protected]">Wojciech Sulejman</a> 054 */ 055@Name("certActions") 056@Scope(ScopeType.CONVERSATION) 057public class CertActions implements Serializable { 058 059 private static final long serialVersionUID = 2L; 060 061 private static final Log LOG = LogFactory.getLog(CertActions.class); 062 063 private static final int MINIMUM_PASSWORD_LENGTH = 8; 064 065 private static final String USER_FIELD_FIRSTNAME = "user:firstName"; 066 067 private static final String USER_FIELD_LASTNAME = "user:lastName"; 068 069 private static final String USER_FIELD_EMAIL = "user:email"; 070 071 private static final String HOME_TAB = "MAIN_TABS:home"; 072 073 private static final String CERTIFICATE_TAB = "USER_CENTER:Certificate"; 074 075 @In(create = true) 076 protected transient CertService certService; 077 078 @In(create = true) 079 protected transient CUserService cUserService; 080 081 @In(create = true) 082 protected transient NavigationContext navigationContext; 083 084 @In(create = true, required = false) 085 protected FacesMessages facesMessages; 086 087 @In(create = true) 088 protected ResourcesAccessor resourcesAccessor; 089 090 @In(create = true, required = false) 091 protected transient CoreSession documentManager; 092 093 @In(create = true) 094 protected transient NuxeoPrincipal currentUser; 095 096 @In(create = true) 097 protected transient UserManager userManager; 098 099 @In(create = true, required = false) 100 protected WebActions webActions; 101 102 protected DocumentModel lastVisitedDocument; 103 104 protected DocumentModel certificate; 105 106 private static final String LOCAL_CA_CERTIFICATE_FILE_NAME = "LOCAL_CA_.crt"; 107 108 /** 109 * Retrieves a user certificate and returns a certificate's document model object 110 * 111 * @return 112 */ 113 public DocumentModel getCertificate() { 114 String userID = (String) getCurrentUserModel().getPropertyValue("user:username"); 115 return cUserService.getCertificate(userID); 116 } 117 118 /** 119 * Checks if a specified user has a certificate 120 * 121 * @param user 122 * @return 123 */ 124 public boolean hasCertificate(DocumentModel user) { 125 String userID = (String) user.getPropertyValue("user:username"); 126 return cUserService.hasCertificate(userID); 127 } 128 129 /** 130 * Checks if a specified user has a certificate 131 * 132 * @param userID 133 * @return 134 */ 135 public boolean hasCertificate(String userID) { 136 return cUserService.hasCertificate(userID); 137 } 138 139 /** 140 * Checks if a specified user has a certificate 141 * 142 * @return 143 */ 144 public boolean hasCertificate() { 145 return hasCertificate(getCurrentUserModel()); 146 } 147 148 /** 149 * Indicates whether a user has the right to generate a certificate. 150 * 151 * @param user 152 * @return 153 */ 154 public boolean canGenerateCertificate() { 155 boolean canGenerateCertificate = false; 156 // TODO currently allows generating certificates but will be used for 157 // tightening security 158 canGenerateCertificate = true; 159 return canGenerateCertificate; 160 } 161 162 /** 163 * Launches certificate generation. Requires valid passwords for certificate encryption. 164 * 165 * @param user 166 * @param firstPassword 167 * @param secondPassword 168 */ 169 public void createCertificate(String firstPassword, String secondPassword) { 170 boolean areRequirementsMet = false; 171 172 try { 173 validatePasswords(firstPassword, secondPassword); 174 validateRequiredUserFields(); 175 // passed through validations 176 areRequirementsMet = true; 177 } catch (ValidatorException v) { 178 facesMessages.add(StatusMessage.Severity.ERROR, v.getFacesMessage().getDetail()); 179 } 180 181 if (areRequirementsMet) { 182 try { 183 cUserService.createCertificate(getCurrentUserModel(), firstPassword); 184 facesMessages.add(StatusMessage.Severity.INFO, 185 resourcesAccessor.getMessages().get("label.cert.created")); 186 } catch (CertException e) { 187 LOG.error(e); 188 facesMessages.add(StatusMessage.Severity.ERROR, 189 resourcesAccessor.getMessages().get("label.cert.generate.problem") + e.getMessage()); 190 } 191 } 192 } 193 194 /** 195 * @since 5.8 - action to remove certificate. 196 */ 197 public void deleteCertificate() { 198 try { 199 cUserService.deleteCertificate((String) getCurrentUserModel().getPropertyValue("user:username")); 200 facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("label.cert.deleted")); 201 } catch (CertException e) { 202 LOG.error("Digital signature certificate deletion issue", e); 203 facesMessages.add(StatusMessage.Severity.ERROR, 204 resourcesAccessor.getMessages().get("label.cert.delete.problem") + e.getMessage()); 205 } 206 } 207 208 /** 209 * Validates that the password follows business rules. 210 * <p> 211 * The password must be typed in twice correctly, follow minimum length, and be different than the application login 212 * password. 213 * <p> 214 * The validations are performed in the following sequence cheapest validations first, then the ones requiring more 215 * system resources. 216 * 217 * @param firstPassword 218 * @param secondPassword 219 */ 220 public void validatePasswords(String firstPassword, String secondPassword) { 221 222 if (firstPassword == null || secondPassword == null) { 223 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, resourcesAccessor.getMessages().get( 224 "label.review.added.reviewer"), null); 225 facesMessages.add(StatusMessage.Severity.ERROR, "ABC" + message.getDetail()); 226 throw new ValidatorException(message); 227 } 228 229 if (!firstPassword.equals(secondPassword)) { 230 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, resourcesAccessor.getMessages().get( 231 "label.cert.password.mismatch"), null); 232 throw new ValidatorException(message); 233 } 234 235 // at least 8 characters 236 if (firstPassword.length() < MINIMUM_PASSWORD_LENGTH) { 237 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, resourcesAccessor.getMessages().get( 238 "label.cert.password.too.short"), null); 239 throw new ValidatorException(message); 240 } 241 242 /* 243 * If the certificate password matches the user login password an exception is thrown, as those passwords should 244 * not be the same to increase security and decouple one from another to allow for reuse 245 */ 246 if (userManager.checkUsernamePassword(currentUser.getName(), firstPassword)) { 247 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, resourcesAccessor.getMessages().get( 248 "label.cert.password.is.login.password"), null); 249 throw new ValidatorException(message); 250 } 251 } 252 253 /** 254 * Validates user identity fields required for certificate generation NXP-6485 255 * <p> 256 */ 257 public void validateRequiredUserFields() { 258 259 DocumentModel user = userManager.getUserModel(currentUser.getName()); 260 // first name 261 String firstName = (String) user.getPropertyValue(USER_FIELD_FIRSTNAME); 262 if (null == firstName || firstName.length() == 0) { 263 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, resourcesAccessor.getMessages().get( 264 "label.cert.user.firstname.missing"), null); 265 throw new ValidatorException(message); 266 } 267 // last name 268 String lastName = (String) user.getPropertyValue(USER_FIELD_LASTNAME); 269 if (null == lastName || lastName.length() == 0) { 270 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, resourcesAccessor.getMessages().get( 271 "label.cert.user.lastname.missing"), null); 272 throw new ValidatorException(message); 273 } 274 // email - // a very forgiving check (e.g. accepts _@localhost) 275 String email = (String) user.getPropertyValue(USER_FIELD_EMAIL); 276 String emailRegex = ".+@.+"; 277 if (null == email || email.length() == 0 || !email.matches(emailRegex)) { 278 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, resourcesAccessor.getMessages().get( 279 "label.cert.user.email.problem"), null); 280 throw new ValidatorException(message); 281 } 282 } 283 284 public void downloadRootCertificate() throws CertException { 285 try { 286 byte[] rootCertificateData = cUserService.getRootCertificateData(); 287 HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse(); 288 response.setContentType("application/octet-stream"); 289 response.addHeader("Content-Disposition", "attachment;filename=" + LOCAL_CA_CERTIFICATE_FILE_NAME); 290 response.setContentLength(rootCertificateData.length); 291 OutputStream writer = response.getOutputStream(); 292 writer.write(rootCertificateData); 293 writer.flush(); 294 writer.close(); 295 FacesContext.getCurrentInstance().responseComplete(); 296 } catch (IOException e) { 297 throw new CertException(e); 298 } 299 } 300 301 public String goToCertificateManagement() { 302 lastVisitedDocument = navigationContext.getCurrentDocument(); 303 webActions.setCurrentTabIds(HOME_TAB); 304 webActions.setCurrentTabIds(CERTIFICATE_TAB); 305 return "view_home"; 306 } 307 308 public String backToDocument() { 309 if (lastVisitedDocument != null) { 310 webActions.setCurrentTabIds("sign_view"); 311 return navigationContext.navigateToDocument(lastVisitedDocument); 312 } else { 313 return navigationContext.goHome(); 314 } 315 } 316 317 protected DocumentModel getCurrentUserModel() { 318 return userManager.getUserModel(currentUser.getName()); 319 } 320}