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 * Kevin Leturc 018 */ 019package org.nuxeo.ecm.liveconnect.onedrive; 020 021import java.io.IOException; 022import java.io.Serializable; 023import java.util.HashMap; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Optional; 027 028import javax.faces.application.Application; 029import javax.faces.component.UIComponent; 030import javax.faces.component.UIInput; 031import javax.faces.component.UINamingContainer; 032import javax.faces.component.html.HtmlInputText; 033import javax.faces.context.FacesContext; 034import javax.faces.context.ResponseWriter; 035import javax.servlet.http.HttpServletRequest; 036 037import org.apache.commons.lang.StringUtils; 038import org.nuxeo.common.utils.i18n.I18NUtils; 039import org.nuxeo.ecm.core.api.Blob; 040import org.nuxeo.ecm.core.api.NuxeoException; 041import org.nuxeo.ecm.core.blob.BlobManager; 042import org.nuxeo.ecm.liveconnect.core.LiveConnectFileInfo; 043import org.nuxeo.ecm.liveconnect.onedrive.OneDriveBlobProvider; 044import org.nuxeo.ecm.liveconnect.onedrive.OneDriveOAuth2ServiceProvider; 045import org.nuxeo.ecm.platform.oauth2.tokens.NuxeoOAuth2Token; 046import org.nuxeo.ecm.platform.ui.web.component.file.InputFileChoice; 047import org.nuxeo.ecm.platform.ui.web.component.file.InputFileInfo; 048import org.nuxeo.ecm.platform.ui.web.component.file.JSFBlobUploader; 049import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 050import org.nuxeo.runtime.api.Framework; 051 052import com.google.api.client.auth.oauth2.Credential; 053 054/** 055 * JSF Blob Upload based on one drive blobs. 056 * 057 * @since 8.2 058 */ 059public class OneDriveBlobUploader implements JSFBlobUploader { 060 061 public static final String UPLOAD_ONEDRIVE_FACET_NAME = InputFileChoice.UPLOAD + "OneDrive"; 062 063 protected final String id; 064 065 public OneDriveBlobUploader(String id) { 066 this.id = id; 067 try { 068 getOneDriveBlobProvider(); 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 UPLOAD_ONEDRIVE_FACET_NAME; 079 } 080 081 @Override 082 public void hookSubComponent(UIInput parent) { 083 Application app = FacesContext.getCurrentInstance().getApplication(); 084 ComponentUtils.initiateSubComponent(parent, UPLOAD_ONEDRIVE_FACET_NAME, 085 app.createComponent(HtmlInputText.COMPONENT_TYPE)); 086 } 087 088 @Override 089 public void encodeBeginUpload(UIInput parent, FacesContext context, String onClick) throws IOException { 090 UIComponent facet = parent.getFacet(UPLOAD_ONEDRIVE_FACET_NAME); 091 if (!(facet instanceof HtmlInputText)) { 092 return; 093 } 094 HtmlInputText inputText = (HtmlInputText) facet; 095 096 // not ours to close 097 @SuppressWarnings("resource") 098 ResponseWriter writer = context.getResponseWriter(); 099 OneDriveBlobProvider blobProvider = getOneDriveBlobProvider(); 100 OneDriveOAuth2ServiceProvider oauthProvider = blobProvider.getOAuth2Provider(); 101 102 String inputId = facet.getClientId(context); 103 String prefix = parent.getClientId(context) + UINamingContainer.getSeparatorChar(context); 104 String pickId = prefix + "OneDrivePickMsg"; 105 String infoId = prefix + "OneDriveInfo"; 106 Locale locale = context.getViewRoot().getLocale(); 107 String message; 108 boolean isProviderAvailable = oauthProvider != null && oauthProvider.isProviderAvailable(); 109 110 writer.startElement("button", parent); 111 writer.writeAttribute("type", "button", null); 112 writer.writeAttribute("class", "button", null); 113 114 // only add onclick event to button if oauth service provider is available 115 // this prevents users from using the picker if some configuration is missing 116 if (isProviderAvailable) { 117 String accessToken = getCurrentUserAccessToken(blobProvider); 118 String authorizationUrl = getOAuthAuthorizationUrl(oauthProvider); 119 String baseUrl = oauthProvider.getAPIInitializer().apply("").getBaseURL(); 120 121 String onButtonClick = onClick 122 + ";" 123 + String.format("new nuxeo.utils.OneDrivePicker('%s', '%s','%s', '%s', '%s', '%s')", 124 getClientId(oauthProvider), inputId, infoId, accessToken, authorizationUrl, baseUrl); 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.oneDriveUploadPicker", null, locale); 131 writer.write(message); 132 writer.endElement("span"); 133 134 writer.endElement("button"); 135 136 if (isProviderAvailable) { 137 writer.write(ComponentUtils.WHITE_SPACE_CHARACTER); 138 writer.startElement("span", parent); 139 writer.writeAttribute("id", infoId, null); 140 message = I18NUtils.getMessageString("messages", "error.inputFile.noFileSelected", null, locale); 141 writer.write(message); 142 writer.endElement("span"); 143 } else { 144 // if oauth service provider not properly setup, add warning message 145 writer.startElement("span", parent); 146 writer.writeAttribute("class", "processMessage completeWarning", null); 147 writer.writeAttribute( 148 "style", 149 "margin: 0 0 .5em 0; font-size: 11px; padding: 0.4em 0.5em 0.5em 2.2em; background-position-y: 0.6em", 150 null); 151 message = I18NUtils.getMessageString("messages", "error.oneDrive.providerUnavailable", null, locale); 152 writer.write(message); 153 writer.endElement("span"); 154 } 155 156 inputText.setLocalValueSet(false); 157 inputText.setStyle("display: none"); 158 ComponentUtils.encodeComponent(context, inputText); 159 } 160 161 @Override 162 public void validateUpload(UIInput parent, FacesContext context, InputFileInfo submitted) { 163 UIComponent facet = parent.getFacet(UPLOAD_ONEDRIVE_FACET_NAME); 164 if (!(facet instanceof HtmlInputText)) { 165 return; 166 } 167 HtmlInputText inputText = (HtmlInputText) facet; 168 Object value = inputText.getSubmittedValue(); 169 if (value != null && !(value instanceof String)) { 170 ComponentUtils.addErrorMessage(context, parent, "error.inputFile.invalidSpecialBlob"); 171 parent.setValid(false); 172 return; 173 } 174 String fileId = (String) value; 175 if (StringUtils.isBlank(fileId)) { 176 String message = context.getPartialViewContext().isAjaxRequest() ? InputFileInfo.INVALID_WITH_AJAX_MESSAGE 177 : InputFileInfo.INVALID_FILE_MESSAGE; 178 ComponentUtils.addErrorMessage(context, parent, message); 179 parent.setValid(false); 180 return; 181 } 182 183 OneDriveBlobProvider blobProvider = getOneDriveBlobProvider(); 184 OneDriveOAuth2ServiceProvider oauthProvider = blobProvider.getOAuth2Provider(); 185 if (oauthProvider == null) { 186 ComponentUtils.addErrorMessage(context, parent, "error.inputFile.oneDriveInvalidConfiguration"); 187 parent.setValid(false); 188 return; 189 } 190 191 Optional<NuxeoOAuth2Token> nuxeoToken = getCurrentNuxeoToken(blobProvider); 192 if (!nuxeoToken.isPresent()) { 193 String link = String.format( 194 "<a href='#' onclick=\"openPopup('%s'); return false;\">Register a new token</a> and try again.", 195 getOAuthAuthorizationUrl(oauthProvider)); 196 ComponentUtils.addErrorMessage(context, parent, "error.inputFile.oneDriveInvalidPermissions", 197 new Object[] { link }); 198 parent.setValid(false); 199 return; 200 } 201 202 try { 203 LiveConnectFileInfo fileInfo = new LiveConnectFileInfo(nuxeoToken.get().getServiceLogin(), fileId); 204 Blob blob = blobProvider.toBlob(fileInfo); 205 submitted.setBlob(blob); 206 submitted.setFilename(blob.getFilename()); 207 submitted.setMimeType(blob.getMimeType()); 208 } catch (IOException e) { 209 throw new RuntimeException(e); // TODO better feedback 210 } 211 } 212 213 /** 214 * OneDrive upload button is added to the file widget if and only if OneDrive OAuth service provider is enabled 215 * 216 * @return {@code true} if OneDrive OAuth service provider is enabled or {@code false} otherwise 217 */ 218 @Override 219 public boolean isEnabled() { 220 OneDriveOAuth2ServiceProvider provider = getOneDriveBlobProvider().getOAuth2Provider(); 221 return provider != null && provider.isEnabled(); 222 } 223 224 protected String getClientId(OneDriveOAuth2ServiceProvider provider) { 225 return Optional.ofNullable(provider).map(OneDriveOAuth2ServiceProvider::getClientId).orElse(""); 226 } 227 228 protected OneDriveBlobProvider getOneDriveBlobProvider() { 229 return (OneDriveBlobProvider) Framework.getService(BlobManager.class).getBlobProvider(id); 230 } 231 232 private String getCurrentUserAccessToken(OneDriveBlobProvider provider) throws IOException { 233 Optional<NuxeoOAuth2Token> nuxeoToken = getCurrentNuxeoToken(provider); 234 if (nuxeoToken.isPresent()) { 235 // Here we don't need to handle NuxeoException as we just retrieved the token 236 Credential credential = provider.getCredential(nuxeoToken.get().getServiceLogin()); 237 Long expiresInSeconds = credential.getExpiresInSeconds(); 238 if (expiresInSeconds != null && expiresInSeconds > 0) { 239 return credential.getAccessToken(); 240 } 241 } 242 return ""; 243 } 244 245 private Optional<NuxeoOAuth2Token> getCurrentNuxeoToken(OneDriveBlobProvider provider) { 246 Map<String, Serializable> filter = new HashMap<>(); 247 filter.put(NuxeoOAuth2Token.KEY_NUXEO_LOGIN, FacesContext.getCurrentInstance() 248 .getExternalContext() 249 .getUserPrincipal() 250 .getName()); 251 return provider.getOAuth2Provider() 252 .getCredentialDataStore() 253 .query(filter) 254 .stream() 255 .map(NuxeoOAuth2Token::new) 256 .findFirst(); 257 } 258 259 private String getOAuthAuthorizationUrl(OneDriveOAuth2ServiceProvider provider) { 260 HttpServletRequest request = getHttpServletRequest(); 261 return (provider != null && provider.getClientId() != null) ? provider.getAuthorizationUrl(request) : ""; 262 } 263 264 private HttpServletRequest getHttpServletRequest() { 265 return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); 266 } 267}