001/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Florent Guillaume 016 * Nelson Silva 017 */ 018package org.nuxeo.ecm.liveconnect.google.drive; 019 020import java.io.IOException; 021import java.util.Locale; 022 023import javax.faces.application.Application; 024import javax.faces.component.NamingContainer; 025import javax.faces.component.UIComponent; 026import javax.faces.component.UIInput; 027import javax.faces.component.html.HtmlInputText; 028import javax.faces.context.FacesContext; 029import javax.faces.context.ResponseWriter; 030import javax.servlet.http.HttpServletRequest; 031 032import org.apache.commons.lang.StringUtils; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.common.utils.i18n.I18NUtils; 036import org.nuxeo.ecm.core.api.Blob; 037import org.nuxeo.ecm.core.api.DocumentModelList; 038import org.nuxeo.ecm.core.api.NuxeoException; 039import org.nuxeo.ecm.core.blob.BlobManager; 040import org.nuxeo.ecm.liveconnect.google.drive.GoogleDriveBlobProvider.FileInfo; 041import org.nuxeo.ecm.platform.ui.web.component.file.InputFileChoice; 042import org.nuxeo.ecm.platform.ui.web.component.file.InputFileInfo; 043import org.nuxeo.ecm.platform.ui.web.component.file.JSFBlobUploader; 044import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 045import org.nuxeo.runtime.api.Framework; 046 047import com.google.api.client.auth.oauth2.Credential; 048 049/** 050 * JSF Blob Upload based on Google Drive blobs. 051 * 052 * @since 7.3 053 */ 054public class GoogleDriveBlobUploader implements JSFBlobUploader { 055 056 private static final Log log = LogFactory.getLog(GoogleDriveBlobUploader.class); 057 058 public static final String UPLOAD_GOOGLE_DRIVE_FACET_NAME = "uploadGoogleDrive"; 059 060 // restrict sign-in to accounts at this domain 061 public static final String GOOGLE_DOMAIN_PROP = "nuxeo.google.domain"; 062 063 protected final String id; 064 065 public GoogleDriveBlobUploader(String id) { 066 this.id = id; 067 try { 068 getGoogleDriveBlobProvider(); 069 } catch (NuxeoException e) { 070 // this exception is caught by JSFBlobUploaderDescriptor.getJSFBlobUploader 071 // to mean that the uploader is not available because badly configured 072 throw new IllegalStateException(e); 073 } 074 } 075 076 @Override 077 public String getChoice() { 078 return InputFileChoice.UPLOAD + "GoogleDrive"; 079 } 080 081 @Override 082 public void hookSubComponent(UIInput parent) { 083 Application app = FacesContext.getCurrentInstance().getApplication(); 084 ComponentUtils.initiateSubComponent(parent, UPLOAD_GOOGLE_DRIVE_FACET_NAME, 085 app.createComponent(HtmlInputText.COMPONENT_TYPE)); 086 087 } 088 089 // Needs supporting JavaScript code for nuxeo.utils.pickFromGoogleDrive defined in googleclient.js. 090 @Override 091 public void encodeBeginUpload(UIInput parent, FacesContext context, String onClick) throws IOException { 092 UIComponent facet = parent.getFacet(UPLOAD_GOOGLE_DRIVE_FACET_NAME); 093 if (!(facet instanceof HtmlInputText)) { 094 return; 095 } 096 HtmlInputText inputText = (HtmlInputText) facet; 097 098 // not ours to close 099 @SuppressWarnings("resource") 100 ResponseWriter writer = context.getResponseWriter(); 101 102 String inputId = facet.getClientId(context); 103 String prefix = parent.getClientId(context) + NamingContainer.SEPARATOR_CHAR; 104 String pickId = prefix + "GoogleDrivePickMsg"; 105 String authId = prefix + "GoogleDriveAuthMsg"; 106 String infoId = prefix + "GoogleDriveInfo"; 107 String authorizationUrl = hasServiceAccount() ? "" : getOAuthAuthorizationUrl(); 108 Locale locale = context.getViewRoot().getLocale(); 109 String message; 110 boolean isProviderAvailable = getGoogleDriveBlobProvider().getOAuth2Provider().isProviderAvailable(); 111 112 writer.startElement("button", parent); 113 writer.writeAttribute("type", "button", null); 114 writer.writeAttribute("class", "button GoogleDrivePickerButton", null); 115 116 // only add onclick event to button if oauth service provider is available 117 // this prevents users from using the picker if some configuration is missing 118 if (isProviderAvailable) { 119 // TODO pass existing access token 120 String onButtonClick = onClick 121 + ";" 122 + String.format("new nuxeo.utils.GoogleDrivePicker('%s','%s','%s','%s','%s','%s', '%s')", 123 getClientId(), pickId, authId, inputId, infoId, getGoogleDomain(), authorizationUrl); 124 writer.writeAttribute("onclick", onButtonClick, null); 125 } 126 127 writer.startElement("span", parent); 128 writer.writeAttribute("id", pickId, null); 129 message = I18NUtils.getMessageString("messages", "label.inputFile.googleDriveUploadPicker", null, locale); 130 writer.write(message); 131 writer.endElement("span"); 132 133 writer.startElement("span", parent); 134 writer.writeAttribute("id", authId, null); 135 writer.writeAttribute("style", "display:none", null); // hidden 136 message = I18NUtils.getMessageString("messages", "label.inputFile.authenticate", null, locale); 137 writer.write(message); 138 writer.endElement("span"); 139 140 writer.endElement("button"); 141 142 if (isProviderAvailable) { 143 writer.write(ComponentUtils.WHITE_SPACE_CHARACTER); 144 writer.startElement("span", parent); 145 writer.writeAttribute("id", infoId, null); 146 message = I18NUtils.getMessageString("messages", "error.inputFile.noFileSelected", null, locale); 147 writer.write(message); 148 writer.endElement("span"); 149 } else { 150 // if oauth service provider not properly setup, add warning message 151 writer.startElement("span", parent); 152 writer.writeAttribute("class", "processMessage completeWarning", null); 153 writer.writeAttribute("style", "margin: 0 0 .5em 0; font-size: 11px; background-position-y: 0.6em", null); 154 message = I18NUtils.getMessageString("messages", "error.googledrive.providerUnavailable", null, locale); 155 writer.write(message); 156 writer.endElement("span"); 157 } 158 159 inputText.setLocalValueSet(false); 160 inputText.setStyle("display:none"); // hidden 161 ComponentUtils.encodeComponent(context, inputText); 162 } 163 164 @Override 165 public void validateUpload(UIInput parent, FacesContext context, InputFileInfo submitted) { 166 UIComponent facet = parent.getFacet(UPLOAD_GOOGLE_DRIVE_FACET_NAME); 167 if (!(facet instanceof HtmlInputText)) { 168 return; 169 } 170 HtmlInputText inputText = (HtmlInputText) facet; 171 Object value = inputText.getSubmittedValue(); 172 if (value != null && !(value instanceof String)) { 173 ComponentUtils.addErrorMessage(context, parent, "error.inputFile.invalidSpecialBlob"); 174 parent.setValid(false); 175 return; 176 } 177 String string = (String) value; 178 if (StringUtils.isBlank(string) || string.indexOf(':') < 0) { 179 String message = context.getPartialViewContext().isAjaxRequest() ? InputFileInfo.INVALID_WITH_AJAX_MESSAGE 180 : InputFileInfo.INVALID_FILE_MESSAGE; 181 ComponentUtils.addErrorMessage(context, parent, message); 182 parent.setValid(false); 183 return; 184 } 185 186 // micro parse the string (user:fileId) 187 String[] parts = string.split(":"); 188 String user = parts[0]; 189 String fileId = parts[1]; 190 191 // check if we can get an access token 192 String accessToken = getAccessToken(user); 193 if (accessToken == null) { 194 String link = String.format("<a href='#' onclick=\"openPopup('%s'); return false;\">Register a new token</a> and try again.", getOAuthAuthorizationUrl()); 195 ComponentUtils.addErrorMessage(context, parent, "error.inputFile.accessToken", new Object[] { user, link }); 196 parent.setValid(false); 197 return; 198 } 199 200 Blob blob = createBlob(new FileInfo(user, fileId, null)); // no revisionId 201 submitted.setBlob(blob); 202 submitted.setFilename(blob.getFilename()); 203 submitted.setMimeType(blob.getMimeType()); 204 } 205 206 /** 207 * Google Drive upload button is added to the file widget if and only if Google Drive OAuth service provider is enabled 208 * 209 * @return true if Google Drive OAuth service provider is enabled or false otherwise. 210 */ 211 @Override 212 public boolean isEnabled() { 213 return getGoogleDriveBlobProvider().getOAuth2Provider().isEnabled(); 214 } 215 216 /** 217 * Creates a Google Drive managed blob. 218 * 219 * @param fileInfo the Google Drive file info 220 * @return the blob 221 */ 222 protected Blob createBlob(FileInfo fileInfo) { 223 try { 224 return getGoogleDriveBlobProvider().getBlob(fileInfo); 225 } catch (IOException e) { 226 throw new RuntimeException(e); // TODO better feedback 227 } 228 } 229 230 protected GoogleDriveBlobProvider getGoogleDriveBlobProvider() { 231 return (GoogleDriveBlobProvider) Framework.getService(BlobManager.class) 232 .getBlobProvider(id); 233 } 234 235 protected String getGoogleDomain() { 236 String domain = Framework.getProperty(GOOGLE_DOMAIN_PROP); 237 return (domain != null) ? domain : ""; 238 } 239 240 protected String getClientId() { 241 String clientId = getGoogleDriveBlobProvider().getClientId(); 242 return (clientId != null) ? clientId : ""; 243 } 244 245 protected String getAccessToken(String user) { 246 try { 247 Credential credential = getGoogleDriveBlobProvider().getCredential(user); 248 if (credential != null) { 249 String accessToken = credential.getAccessToken(); 250 if (accessToken != null) { 251 return accessToken; 252 } 253 } 254 } catch (IOException e) { 255 log.error("Failed to get access token for " + user, e); 256 } 257 return null; 258 } 259 260 private boolean hasServiceAccount() { 261 HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); 262 String username = request.getUserPrincipal().getName(); 263 GoogleOAuth2ServiceProvider provider = (GoogleOAuth2ServiceProvider) getGoogleDriveBlobProvider().getOAuth2Provider(); 264 return provider != null && provider.getServiceUser(username) != null; 265 } 266 267 private String getOAuthAuthorizationUrl() { 268 HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); 269 GoogleOAuth2ServiceProvider provider = (GoogleOAuth2ServiceProvider) getGoogleDriveBlobProvider().getOAuth2Provider(); 270 return (provider != null && provider.getClientId() != null) ? provider.getAuthorizationUrl(request) : ""; 271 } 272}