001/* 002 * (C) Copyright 2014 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 * Michal Obrebski - Nuxeo 018 */ 019 020package org.nuxeo.easyshare; 021 022 023import java.util.HashMap; 024import java.util.Hashtable; 025import java.util.Map; 026 027import javax.ws.rs.GET; 028import javax.ws.rs.Path; 029import javax.ws.rs.PathParam; 030import javax.ws.rs.Produces; 031import javax.ws.rs.core.Response; 032 033import org.apache.commons.lang.StringUtils; 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.nuxeo.ecm.automation.AutomationService; 037import org.nuxeo.ecm.automation.OperationChain; 038import org.nuxeo.ecm.automation.OperationContext; 039import org.nuxeo.ecm.automation.jaxrs.io.documents.PaginableDocumentModelListImpl; 040import org.nuxeo.ecm.core.api.Blob; 041import org.nuxeo.ecm.core.api.NuxeoException; 042import org.nuxeo.ecm.core.api.CoreSession; 043import org.nuxeo.ecm.core.api.DocumentModel; 044import org.nuxeo.ecm.core.api.IdRef; 045import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 046import org.nuxeo.ecm.platform.ec.notification.NotificationConstants; 047import org.nuxeo.ecm.platform.ec.notification.email.EmailHelper; 048import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper; 049import org.nuxeo.ecm.platform.notification.api.Notification; 050import org.nuxeo.ecm.webengine.model.WebObject; 051import org.nuxeo.ecm.webengine.model.impl.ModuleRoot; 052import org.nuxeo.runtime.api.Framework; 053 054import javax.ws.rs.DefaultValue; 055import javax.ws.rs.QueryParam; 056 057import java.util.Date; 058 059/** 060 * The root entry for the WebEngine module. 061 * 062 * @author mikeobrebski 063 */ 064@Path("/easyshare") 065@Produces("text/html;charset=UTF-8") 066@WebObject(type = "EasyShare") 067public class EasyShare extends ModuleRoot { 068 069 private static final String DEFAULT_PAGE_INDEX = "0"; 070 private static final Long PAGE_SIZE = 20L; 071 private static final String SHARE_DOC_TYPE = "EasyShareFolder"; 072 private static AutomationService automationService; 073 protected final Log log = LogFactory.getLog(EasyShare.class); 074 075 @GET 076 public Object doGet() { 077 return getView("index"); 078 } 079 080 public EasyShareUnrestrictedRunner buildUnrestrictedRunner(final String docId, final Long pageIndex) { 081 082 return new EasyShareUnrestrictedRunner() { 083 @Override 084 public Object run(CoreSession session, IdRef docRef) throws NuxeoException { 085 if (session.exists(docRef)) { 086 DocumentModel docShare = session.getDocument(docRef); 087 088 if (!SHARE_DOC_TYPE.equals(docShare.getType())) { 089 return Response.serverError().status(Response.Status.NOT_FOUND).build(); 090 } 091 092 if (!checkIfShareIsValid(docShare)) { 093 return getView("expired").arg("docShare", docShare); 094 } 095 096 DocumentModel document = session.getDocument(new IdRef(docId)); 097 098 String query = buildQuery(document); 099 100 if (query == null) { 101 return getView("denied"); 102 } 103 104 try { 105 106 OperationContext opCtx = new OperationContext(session); 107 OperationChain chain = new OperationChain("getEasyShareContent"); 108 chain.add("Document.Query") 109 .set("query", query) 110 .set("currentPageIndex", pageIndex) 111 .set("pageSize", PAGE_SIZE); 112 113 PaginableDocumentModelListImpl paginable = (PaginableDocumentModelListImpl) getAutomationService().run(opCtx, chain); 114 115 OperationContext ctx = new OperationContext(session); 116 ctx.setInput(docShare); 117 118 // Audit Log 119 Map<String, Object> params = new HashMap<>(); 120 params.put("event", "Access"); 121 params.put("category", "Document"); 122 params.put("comment", "IP: " + getIpAddr()); 123 getAutomationService().run(ctx, "Audit.Log", params); 124 125 return getView("folderList") 126 .arg("isFolder", document.isFolder() && !SHARE_DOC_TYPE.equals(document.getType())) //Backward compatibility to non-collection 127 .arg("currentPageIndex", paginable.getCurrentPageIndex()) 128 .arg("numberOfPages", paginable.getNumberOfPages()) 129 .arg("docShare", docShare) 130 .arg("docList", paginable) 131 .arg("previousPageAvailable", paginable.isPreviousPageAvailable()) 132 .arg("nextPageAvailable", paginable.isNextPageAvailable()) 133 .arg("currentPageStatus", paginable.getProvider().getCurrentPageStatus()); 134 135 } catch (Exception ex) { 136 log.error(ex.getMessage()); 137 return getView("denied"); 138 } 139 140 } else { 141 return getView("denied"); 142 } 143 } 144 }; 145 } 146 147 148 protected static String buildQuery(DocumentModel documentModel) { 149 150 //Backward compatibility to non-collection 151 if (documentModel.isFolder() && !SHARE_DOC_TYPE.equals(documentModel.getType())) { 152 return " SELECT * FROM Document WHERE ecm:parentId = '" + documentModel.getId() + "' AND " + 153 "ecm:mixinType != 'HiddenInNavigation' AND " + 154 "ecm:mixinType != 'NotCollectionMember' AND " + 155 "ecm:isCheckedInVersion = 0 AND " + 156 "ecm:currentLifeCycleState != 'deleted'" 157 + "ORDER BY dc:title"; 158 159 } else if (SHARE_DOC_TYPE.equals(documentModel.getType())) { 160 return "SELECT * FROM Document where ecm:mixinType != 'HiddenInNavigation' AND " + 161 "ecm:isCheckedInVersion = 0 AND ecm:currentLifeCycleState != 'deleted' " + 162 "AND collectionMember:collectionIds/* = '" + documentModel.getId() + "'" + 163 "OR ecm:parentId = '" + documentModel.getId() + "'" 164 + "ORDER BY dc:title"; 165 } 166 return null; 167 } 168 169 private boolean checkIfShareIsValid(DocumentModel docShare) { 170 Date today = new Date(); 171 if (today.after(docShare.getProperty("dc:expired").getValue(Date.class))) { 172 173 //Email notification 174 Map<String, Object> mail = new HashMap<>(); 175 sendNotification("easyShareExpired", docShare, mail); 176 177 return false; 178 } 179 return true; 180 } 181 182 183 private static AutomationService getAutomationService() { 184 if (automationService == null) { 185 automationService = Framework.getService(AutomationService.class); 186 } 187 return automationService; 188 } 189 190 191 @Path("{shareId}/{folderId}") 192 @GET 193 public Object getFolderListing(@PathParam("shareId") String shareId, @PathParam("folderId") final String folderId, 194 @DefaultValue(DEFAULT_PAGE_INDEX) @QueryParam("p") final Long pageIndex) { 195 return buildUnrestrictedRunner(folderId, pageIndex).runUnrestricted(shareId); 196 } 197 198 @Path("{shareId}") 199 @GET 200 public Object getShareListing(@PathParam("shareId") String shareId, 201 @DefaultValue(DEFAULT_PAGE_INDEX) @QueryParam("p") Long pageIndex) { 202 return buildUnrestrictedRunner(shareId, pageIndex).runUnrestricted(shareId); 203 } 204 205 public String getFileName(DocumentModel doc) throws NuxeoException { 206 BlobHolder blobHolder = doc.getAdapter(BlobHolder.class); 207 if (blobHolder != null && blobHolder.getBlob() != null) { 208 return blobHolder.getBlob().getFilename(); 209 } 210 return doc.getName(); 211 } 212 213 @GET 214 @Path("{shareId}/{fileId}/{fileName}") 215 public Response getFileStream(@PathParam("shareId") final String shareId, @PathParam("fileId") String fileId) throws NuxeoException { 216 217 return (Response) new EasyShareUnrestrictedRunner() { 218 @Override 219 public Object run(CoreSession session, IdRef docRef) throws NuxeoException { 220 if (session.exists(docRef)) { 221 try { 222 DocumentModel doc = session.getDocument(docRef); 223 DocumentModel docShare = session.getDocument(new IdRef(shareId)); 224 225 if (!checkIfShareIsValid(docShare)) { 226 return Response.serverError().status(Response.Status.NOT_FOUND).build(); 227 } 228 229 Blob blob = doc.getAdapter(BlobHolder.class).getBlob(); 230 231 // Audit Log 232 OperationContext ctx = new OperationContext(session); 233 ctx.setInput(doc); 234 235 // Audit.Log automation parameter setting 236 Map<String, Object> params = new HashMap<>(); 237 params.put("event", "Download"); 238 params.put("category", "Document"); 239 params.put("comment", "IP: " + getIpAddr()); 240 AutomationService service = Framework.getLocalService(AutomationService.class); 241 service.run(ctx, "Audit.Log", params); 242 243 if (doc.isProxy()) { 244 DocumentModel liveDoc = session.getSourceDocument(docRef); 245 ctx.setInput(liveDoc); 246 service.run(ctx, "Audit.Log", params); 247 248 } 249 250 // Email notification 251 Map<String, Object> mail = new HashMap<>(); 252 mail.put("filename", blob.getFilename()); 253 sendNotification("easyShareDownload", docShare, mail); 254 255 return Response.ok(blob.getStream(), blob.getMimeType()).build(); 256 257 } catch (Exception ex) { 258 log.error("error ", ex); 259 return Response.serverError().status(Response.Status.NOT_FOUND).build(); 260 } 261 262 } else { 263 return Response.serverError().status(Response.Status.NOT_FOUND).build(); 264 } 265 } 266 }.runUnrestricted(fileId); 267 268 } 269 270 public void sendNotification(String notification, DocumentModel docShare, Map<String, Object> mail) { 271 272 Boolean hasNotification = docShare.getProperty("eshare:hasNotification").getValue(Boolean.class); 273 274 if (hasNotification) { 275 //Email notification 276 String email = docShare.getProperty("eshare:contactEmail").getValue(String.class); 277 if (StringUtils.isEmpty(email)) { 278 return; 279 } 280 try { 281 log.debug("Easyshare: starting email"); 282 EmailHelper emailHelper = new EmailHelper(); 283 Map<String, Object> mailProps = new Hashtable<>(); 284 mailProps.put("mail.from", Framework.getProperty("mail.from", "system@nuxeo.com")); 285 mailProps.put("mail.to", email); 286 mailProps.put("ip", getIpAddr()); 287 mailProps.put("docShare", docShare); 288 289 try { 290 Notification notif = NotificationServiceHelper.getNotificationService().getNotificationByName(notification); 291 292 if (notif.getSubjectTemplate() != null) { 293 mailProps.put(NotificationConstants.SUBJECT_TEMPLATE_KEY, notif.getSubjectTemplate()); 294 } 295 296 mailProps.put(NotificationConstants.SUBJECT_KEY, NotificationServiceHelper.getNotificationService().getEMailSubjectPrefix() + " " + notif.getSubject()); 297 mailProps.put(NotificationConstants.TEMPLATE_KEY, notif.getTemplate()); 298 299 mailProps.putAll(mail); 300 301 emailHelper.sendmail(mailProps); 302 303 } catch (NuxeoException e) { 304 log.warn(e.getMessage()); 305 } 306 307 log.debug("Easyshare: completed email"); 308 } catch (Exception ex) { 309 log.error("Cannot send easyShare notification email", ex); 310 } 311 } 312 } 313 314 315 protected String getIpAddr() { 316 String ip = request.getHeader("X-FORWARDED-FOR"); 317 if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 318 ip = request.getHeader("Proxy-Client-IP"); 319 } 320 if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 321 ip = request.getRemoteAddr(); 322 } 323 return ip; 324 } 325}