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