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