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.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.1 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 + ";" 121 + String.format("new nuxeo.utils.OneDrivePicker('%s', '%s','%s', '%s', '%s', '%s')", 122 getClientId(oauthProvider), inputId, infoId, accessToken, authorizationUrl, baseUrl); 123 writer.writeAttribute("onclick", onButtonClick, null); 124 } 125 126 writer.startElement("span", parent); 127 writer.writeAttribute("id", pickId, null); 128 message = I18NUtils.getMessageString("messages", "label.inputFile.oneDriveUploadPicker", null, locale); 129 writer.write(message); 130 writer.endElement("span"); 131 132 writer.endElement("button"); 133 134 if (isProviderAvailable) { 135 writer.write(ComponentUtils.WHITE_SPACE_CHARACTER); 136 writer.startElement("span", parent); 137 writer.writeAttribute("id", infoId, null); 138 message = I18NUtils.getMessageString("messages", "error.inputFile.noFileSelected", null, locale); 139 writer.write(message); 140 writer.endElement("span"); 141 } else { 142 // if oauth service provider not properly setup, add warning message 143 writer.startElement("span", parent); 144 writer.writeAttribute("class", "processMessage completeWarning", null); 145 writer.writeAttribute( 146 "style", 147 "margin: 0 0 .5em 0; font-size: 11px; padding: 0.4em 0.5em 0.5em 2.2em; background-position-y: 0.6em", 148 null); 149 message = I18NUtils.getMessageString("messages", "error.oneDrive.providerUnavailable", null, locale); 150 writer.write(message); 151 writer.endElement("span"); 152 } 153 154 inputText.setLocalValueSet(false); 155 inputText.setStyle("display: none"); 156 ComponentUtils.encodeComponent(context, inputText); 157 } 158 159 @Override 160 public void validateUpload(UIInput parent, FacesContext context, InputFileInfo submitted) { 161 UIComponent facet = parent.getFacet(UPLOAD_ONEDRIVE_FACET_NAME); 162 if (!(facet instanceof HtmlInputText)) { 163 return; 164 } 165 HtmlInputText inputText = (HtmlInputText) facet; 166 Object value = inputText.getSubmittedValue(); 167 if (value != null && !(value instanceof String)) { 168 ComponentUtils.addErrorMessage(context, parent, "error.inputFile.invalidSpecialBlob"); 169 parent.setValid(false); 170 return; 171 } 172 String fileId = (String) value; 173 if (StringUtils.isBlank(fileId)) { 174 String message = context.getPartialViewContext().isAjaxRequest() ? InputFileInfo.INVALID_WITH_AJAX_MESSAGE 175 : InputFileInfo.INVALID_FILE_MESSAGE; 176 ComponentUtils.addErrorMessage(context, parent, message); 177 parent.setValid(false); 178 return; 179 } 180 181 OneDriveBlobProvider blobProvider = getOneDriveBlobProvider(); 182 OneDriveOAuth2ServiceProvider oauthProvider = blobProvider.getOAuth2Provider(); 183 if (oauthProvider == null) { 184 ComponentUtils.addErrorMessage(context, parent, "error.inputFile.oneDriveInvalidConfiguration"); 185 parent.setValid(false); 186 return; 187 } 188 189 Optional<NuxeoOAuth2Token> nuxeoToken = getCurrentNuxeoToken(blobProvider); 190 if (!nuxeoToken.isPresent()) { 191 String link = String.format( 192 "<a href='#' onclick=\"openPopup('%s'); return false;\">Register a new token</a> and try again.", 193 getOAuthAuthorizationUrl(oauthProvider)); 194 ComponentUtils.addErrorMessage(context, parent, "error.inputFile.oneDriveInvalidPermissions", 195 new Object[] { link }); 196 parent.setValid(false); 197 return; 198 } 199 200 try { 201 LiveConnectFileInfo fileInfo = new LiveConnectFileInfo(nuxeoToken.get().getServiceLogin(), fileId); 202 Blob blob = blobProvider.toBlob(fileInfo); 203 submitted.setBlob(blob); 204 submitted.setFilename(blob.getFilename()); 205 submitted.setMimeType(blob.getMimeType()); 206 } catch (IOException e) { 207 throw new RuntimeException(e); // TODO better feedback 208 } 209 } 210 211 /** 212 * OneDrive upload button is added to the file widget if and only if OneDrive OAuth service provider is enabled 213 * 214 * @return {@code true} if OneDrive OAuth service provider is enabled or {@code false} otherwise 215 */ 216 @Override 217 public boolean isEnabled() { 218 OneDriveOAuth2ServiceProvider provider = getOneDriveBlobProvider().getOAuth2Provider(); 219 return provider != null && provider.isEnabled(); 220 } 221 222 protected String getClientId(OneDriveOAuth2ServiceProvider provider) { 223 return Optional.ofNullable(provider).map(OneDriveOAuth2ServiceProvider::getClientId).orElse(""); 224 } 225 226 protected OneDriveBlobProvider getOneDriveBlobProvider() { 227 return (OneDriveBlobProvider) Framework.getService(BlobManager.class).getBlobProvider(id); 228 } 229 230 private String getCurrentUserAccessToken(OneDriveBlobProvider provider) throws IOException { 231 Optional<NuxeoOAuth2Token> nuxeoToken = getCurrentNuxeoToken(provider); 232 if (nuxeoToken.isPresent()) { 233 // Here we don't need to handle NuxeoException as we just retrieved the token 234 Credential credential = provider.getCredential(nuxeoToken.get().getServiceLogin()); 235 Long expiresInSeconds = credential.getExpiresInSeconds(); 236 if (expiresInSeconds != null && expiresInSeconds > 0) { 237 return credential.getAccessToken(); 238 } 239 } 240 return ""; 241 } 242 243 private Optional<NuxeoOAuth2Token> getCurrentNuxeoToken(OneDriveBlobProvider provider) { 244 Map<String, Serializable> filter = new HashMap<>(); 245 filter.put(NuxeoOAuth2Token.KEY_NUXEO_LOGIN, FacesContext.getCurrentInstance() 246 .getExternalContext() 247 .getUserPrincipal() 248 .getName()); 249 return provider.getOAuth2Provider() 250 .getCredentialDataStore() 251 .query(filter) 252 .stream() 253 .map(NuxeoOAuth2Token::new) 254 .findFirst(); 255 } 256 257 private String getOAuthAuthorizationUrl(OneDriveOAuth2ServiceProvider provider) { 258 HttpServletRequest request = getHttpServletRequest(); 259 return (provider != null && provider.getClientId() != null) ? provider.getAuthorizationUrl(request) : ""; 260 } 261 262 private HttpServletRequest getHttpServletRequest() { 263 return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); 264 } 265}