1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 */ 9 10 #include <com/sun/star/beans/PropertyAttribute.hpp> 11 #include <com/sun/star/beans/PropertyValue.hpp> 12 #include <com/sun/star/beans/XPropertySetInfo.hpp> 13 #include <com/sun/star/lang/IllegalArgumentException.hpp> 14 #include <com/sun/star/ucb/XCommandInfo.hpp> 15 #include <com/sun/star/ucb/XDynamicResultSet.hpp> 16 #ifndef SYSTEM_CURL 17 #include <com/sun/star/xml/crypto/XDigestContext.hpp> 18 #include <com/sun/star/xml/crypto/DigestID.hpp> 19 #include <com/sun/star/xml/crypto/NSSInitializer.hpp> 20 #endif 21 22 #include <config_oauth2.h> 23 #include <rtl/uri.hxx> 24 #include <sal/log.hxx> 25 #include <tools/urlobj.hxx> 26 #include <ucbhelper/cancelcommandexecution.hxx> 27 #include <ucbhelper/contentidentifier.hxx> 28 #include <ucbhelper/propertyvalueset.hxx> 29 #include <ucbhelper/proxydecider.hxx> 30 #include <ucbhelper/macros.hxx> 31 32 #include "auth_provider.hxx" 33 #include "certvalidation_handler.hxx" 34 #include "cmis_content.hxx" 35 #include "cmis_provider.hxx" 36 #include "cmis_repo_content.hxx" 37 #include "cmis_resultset.hxx" 38 #include <memory> 39 40 #define OUSTR_TO_STDSTR(s) std::string( OUStringToOString( s, RTL_TEXTENCODING_UTF8 ).getStr() ) 41 #define STD_TO_OUSTR( str ) OUString( str.c_str(), str.length( ), RTL_TEXTENCODING_UTF8 ) 42 43 using namespace com::sun::star; 44 45 namespace cmis 46 { RepoContent(const uno::Reference<uno::XComponentContext> & rxContext,ContentProvider * pProvider,const uno::Reference<ucb::XContentIdentifier> & Identifier,std::vector<libcmis::RepositoryPtr> const & aRepos)47 RepoContent::RepoContent( const uno::Reference< uno::XComponentContext >& rxContext, 48 ContentProvider *pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier, 49 std::vector< libcmis::RepositoryPtr > const & aRepos ) 50 : ContentImplHelper( rxContext, pProvider, Identifier ), 51 m_pProvider( pProvider ), 52 m_aURL( Identifier->getContentIdentifier( ) ), 53 m_sRepositoryId( ), 54 m_aRepositories( aRepos ) 55 { 56 // Split the URL into bits 57 OUString sURL = m_xIdentifier->getContentIdentifier( ); 58 SAL_INFO( "ucb.ucp.cmis", "RepoContent::RepoContent() " << sURL ); 59 60 m_sRepositoryId = m_aURL.getObjectPath(); 61 if (!m_sRepositoryId.isEmpty() && m_sRepositoryId[0] == '/') 62 m_sRepositoryId = m_sRepositoryId.copy(1); 63 } 64 ~RepoContent()65 RepoContent::~RepoContent() 66 { 67 } 68 getBadArgExcept()69 uno::Any RepoContent::getBadArgExcept() 70 { 71 return uno::makeAny( lang::IllegalArgumentException( 72 "Wrong argument type!", 73 static_cast< cppu::OWeakObject * >( this ), -1) ); 74 } 75 getPropertyValues(const uno::Sequence<beans::Property> & rProperties,const uno::Reference<ucb::XCommandEnvironment> & xEnv)76 uno::Reference< sdbc::XRow > RepoContent::getPropertyValues( 77 const uno::Sequence< beans::Property >& rProperties, 78 const uno::Reference< ucb::XCommandEnvironment >& xEnv ) 79 { 80 rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( m_xContext ); 81 82 for( const beans::Property& rProp : rProperties ) 83 { 84 try 85 { 86 if ( rProp.Name == "IsDocument" ) 87 { 88 xRow->appendBoolean( rProp, false ); 89 } 90 else if ( rProp.Name == "IsFolder" ) 91 { 92 xRow->appendBoolean( rProp, true ); 93 } 94 else if ( rProp.Name == "Title" ) 95 { 96 xRow->appendString( rProp, STD_TO_OUSTR( getRepository( xEnv )->getName( ) ) ); 97 } 98 else if ( rProp.Name == "IsReadOnly" ) 99 { 100 xRow->appendBoolean( rProp, true ); 101 } 102 else 103 { 104 xRow->appendVoid( rProp ); 105 SAL_INFO( "ucb.ucp.cmis", "Looking for unsupported property " << rProp.Name ); 106 } 107 } 108 catch (const libcmis::Exception&) 109 { 110 xRow->appendVoid( rProp ); 111 } 112 } 113 114 return xRow; 115 } 116 getRepositories(const uno::Reference<ucb::XCommandEnvironment> & xEnv)117 void RepoContent::getRepositories( const uno::Reference< ucb::XCommandEnvironment > & xEnv ) 118 { 119 #ifndef SYSTEM_CURL 120 // Initialize NSS library to make sure libcmis (and curl) can access CACERTs using NSS 121 // when using internal libcurl. 122 uno::Reference< css::xml::crypto::XNSSInitializer > 123 xNSSInitializer = css::xml::crypto::NSSInitializer::create( m_xContext ); 124 125 uno::Reference< css::xml::crypto::XDigestContext > xDigestContext( 126 xNSSInitializer->getDigestContext( css::xml::crypto::DigestID::SHA256, 127 uno::Sequence< beans::NamedValue >() ), 128 uno::UNO_SET_THROW ); 129 #endif 130 131 // Set the proxy if needed. We are doing that all times as the proxy data shouldn't be cached. 132 ucbhelper::InternetProxyDecider aProxyDecider( m_xContext ); 133 INetURLObject aBindingUrl( m_aURL.getBindingUrl( ) ); 134 const ucbhelper::InternetProxyServer& rProxy = aProxyDecider.getProxy( 135 INetURLObject::GetScheme( aBindingUrl.GetProtocol( ) ), aBindingUrl.GetHost(), aBindingUrl.GetPort() ); 136 OUString sProxy = rProxy.aName; 137 if ( rProxy.nPort > 0 ) 138 sProxy += ":" + OUString::number( rProxy.nPort ); 139 libcmis::SessionFactory::setProxySettings( OUSTR_TO_STDSTR( sProxy ), std::string(), std::string(), std::string() ); 140 141 if ( !m_aRepositories.empty() ) 142 return; 143 144 // Set the SSL Validation handler 145 libcmis::CertValidationHandlerPtr certHandler( 146 new CertValidationHandler( xEnv, m_xContext, aBindingUrl.GetHost( ) ) ); 147 libcmis::SessionFactory::setCertificateValidationHandler( certHandler ); 148 149 // Get the auth credentials 150 AuthProvider authProvider( xEnv, m_xIdentifier->getContentIdentifier( ), m_aURL.getBindingUrl( ) ); 151 AuthProvider::setXEnv( xEnv ); 152 153 std::string rUsername = OUSTR_TO_STDSTR( m_aURL.getUsername( ) ); 154 std::string rPassword = OUSTR_TO_STDSTR( m_aURL.getPassword( ) ); 155 156 bool bIsDone = false; 157 158 while( !bIsDone ) 159 { 160 if ( authProvider.authenticationQuery( rUsername, rPassword ) ) 161 { 162 try 163 { 164 // Create a session to get repositories 165 libcmis::OAuth2DataPtr oauth2Data; 166 if ( m_aURL.getBindingUrl( ) == GDRIVE_BASE_URL ) 167 { 168 libcmis::SessionFactory::setOAuth2AuthCodeProvider( AuthProvider::copyWebAuthCodeFallback ); 169 oauth2Data.reset( new libcmis::OAuth2Data( 170 GDRIVE_AUTH_URL, GDRIVE_TOKEN_URL, 171 GDRIVE_SCOPE, GDRIVE_REDIRECT_URI, 172 GDRIVE_CLIENT_ID, GDRIVE_CLIENT_SECRET ) ); 173 } 174 if ( m_aURL.getBindingUrl().startsWith( ALFRESCO_CLOUD_BASE_URL ) ) 175 oauth2Data.reset( new libcmis::OAuth2Data( 176 ALFRESCO_CLOUD_AUTH_URL, ALFRESCO_CLOUD_TOKEN_URL, 177 ALFRESCO_CLOUD_SCOPE, ALFRESCO_CLOUD_REDIRECT_URI, 178 ALFRESCO_CLOUD_CLIENT_ID, ALFRESCO_CLOUD_CLIENT_SECRET ) ); 179 if ( m_aURL.getBindingUrl( ) == ONEDRIVE_BASE_URL ) 180 { 181 libcmis::SessionFactory::setOAuth2AuthCodeProvider( AuthProvider::copyWebAuthCodeFallback ); 182 oauth2Data.reset( new libcmis::OAuth2Data( 183 ONEDRIVE_AUTH_URL, ONEDRIVE_TOKEN_URL, 184 ONEDRIVE_SCOPE, ONEDRIVE_REDIRECT_URI, 185 ONEDRIVE_CLIENT_ID, ONEDRIVE_CLIENT_SECRET ) ); 186 } 187 188 std::unique_ptr<libcmis::Session> session(libcmis::SessionFactory::createSession( 189 OUSTR_TO_STDSTR( m_aURL.getBindingUrl( ) ), 190 rUsername, rPassword, "", false, oauth2Data )); 191 if (!session) 192 ucbhelper::cancelCommandExecution( 193 ucb::IOErrorCode_INVALID_DEVICE, 194 uno::Sequence< uno::Any >( 0 ), 195 xEnv ); 196 m_aRepositories = session->getRepositories( ); 197 198 bIsDone = true; 199 } 200 catch ( const libcmis::Exception& e ) 201 { 202 SAL_INFO( "ucb.ucp.cmis", "Error getting repositories: " << e.what() ); 203 204 if ( e.getType() != "permissionDenied" ) 205 { 206 ucbhelper::cancelCommandExecution( 207 ucb::IOErrorCode_INVALID_DEVICE, 208 uno::Sequence< uno::Any >( 0 ), 209 xEnv ); 210 } 211 } 212 } 213 else 214 { 215 // Throw user cancelled exception 216 ucbhelper::cancelCommandExecution( 217 ucb::IOErrorCode_ABORT, 218 uno::Sequence< uno::Any >( 0 ), 219 xEnv, 220 "Authentication cancelled" ); 221 } 222 } 223 } 224 getRepository(const uno::Reference<ucb::XCommandEnvironment> & xEnv)225 libcmis::RepositoryPtr RepoContent::getRepository( const uno::Reference< ucb::XCommandEnvironment > & xEnv ) 226 { 227 // Ensure we have the repositories extracted 228 getRepositories( xEnv ); 229 230 libcmis::RepositoryPtr repo; 231 232 if ( !m_sRepositoryId.isEmpty() ) 233 { 234 auto it = std::find_if(m_aRepositories.begin(), m_aRepositories.end(), 235 [&](const libcmis::RepositoryPtr& rRepo) { return STD_TO_OUSTR(rRepo->getId()) == m_sRepositoryId; }); 236 if (it != m_aRepositories.end()) 237 repo = *it; 238 } 239 else 240 repo = m_aRepositories.front( ); 241 return repo; 242 } 243 getProperties(const uno::Reference<ucb::XCommandEnvironment> &)244 uno::Sequence< beans::Property > RepoContent::getProperties( 245 const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ ) 246 { 247 static const beans::Property aGenericProperties[] = 248 { 249 beans::Property( "IsDocument", 250 -1, cppu::UnoType<bool>::get(), 251 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), 252 beans::Property( "IsFolder", 253 -1, cppu::UnoType<bool>::get(), 254 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), 255 beans::Property( "Title", 256 -1, cppu::UnoType<OUString>::get(), 257 beans::PropertyAttribute::BOUND ), 258 beans::Property( "IsReadOnly", 259 -1, cppu::UnoType<bool>::get(), 260 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), 261 }; 262 263 const int nProps = SAL_N_ELEMENTS(aGenericProperties); 264 return uno::Sequence< beans::Property > ( aGenericProperties, nProps ); 265 } 266 getCommands(const uno::Reference<ucb::XCommandEnvironment> &)267 uno::Sequence< ucb::CommandInfo > RepoContent::getCommands( 268 const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ ) 269 { 270 static const ucb::CommandInfo aCommandInfoTable[] = 271 { 272 // Required commands 273 ucb::CommandInfo 274 ( "getCommandInfo", 275 -1, cppu::UnoType<void>::get() ), 276 ucb::CommandInfo 277 ( "getPropertySetInfo", 278 -1, cppu::UnoType<void>::get() ), 279 ucb::CommandInfo 280 ( "getPropertyValues", 281 -1, cppu::UnoType<uno::Sequence< beans::Property >>::get() ), 282 ucb::CommandInfo 283 ( "setPropertyValues", 284 -1, cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() ), 285 286 // Optional standard commands 287 ucb::CommandInfo 288 ( "open", 289 -1, cppu::UnoType<ucb::OpenCommandArgument2>::get() ), 290 }; 291 292 const int nProps = SAL_N_ELEMENTS(aCommandInfoTable); 293 return uno::Sequence< ucb::CommandInfo >(aCommandInfoTable, nProps ); 294 } 295 getParentURL()296 OUString RepoContent::getParentURL( ) 297 { 298 SAL_INFO( "ucb.ucp.cmis", "RepoContent::getParentURL()" ); 299 300 // TODO Implement me 301 302 return OUString(); 303 } 304 305 XTYPEPROVIDER_COMMON_IMPL( RepoContent ); 306 getImplementationName()307 OUString SAL_CALL RepoContent::getImplementationName() 308 { 309 return "com.sun.star.comp.CmisRepoContent"; 310 } 311 getSupportedServiceNames()312 uno::Sequence< OUString > SAL_CALL RepoContent::getSupportedServiceNames() 313 { 314 return { "com.sun.star.ucb.Content" }; 315 } 316 getContentType()317 OUString SAL_CALL RepoContent::getContentType() 318 { 319 return CMIS_REPO_TYPE; 320 } 321 execute(const ucb::Command & aCommand,sal_Int32,const uno::Reference<ucb::XCommandEnvironment> & xEnv)322 uno::Any SAL_CALL RepoContent::execute( 323 const ucb::Command& aCommand, 324 sal_Int32 /*CommandId*/, 325 const uno::Reference< ucb::XCommandEnvironment >& xEnv ) 326 { 327 SAL_INFO( "ucb.ucp.cmis", "RepoContent::execute( ) - " << aCommand.Name ); 328 329 uno::Any aRet; 330 331 if ( aCommand.Name == "getPropertyValues" ) 332 { 333 uno::Sequence< beans::Property > Properties; 334 if ( !( aCommand.Argument >>= Properties ) ) 335 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); 336 aRet <<= getPropertyValues( Properties, xEnv ); 337 } 338 else if ( aCommand.Name == "getPropertySetInfo" ) 339 aRet <<= getPropertySetInfo( xEnv, false ); 340 else if ( aCommand.Name == "getCommandInfo" ) 341 aRet <<= getCommandInfo( xEnv, false ); 342 else if ( aCommand.Name == "open" ) 343 { 344 ucb::OpenCommandArgument2 aOpenCommand; 345 if ( !( aCommand.Argument >>= aOpenCommand ) ) 346 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); 347 const ucb::OpenCommandArgument2& rOpenCommand = aOpenCommand; 348 349 getRepositories( xEnv ); 350 uno::Reference< ucb::XDynamicResultSet > xSet 351 = new DynamicResultSet(m_xContext, this, rOpenCommand, xEnv ); 352 aRet <<= xSet; 353 } 354 else 355 { 356 SAL_INFO( "ucb.ucp.cmis", "Command not allowed" ); 357 } 358 359 return aRet; 360 } 361 abort(sal_Int32)362 void SAL_CALL RepoContent::abort( sal_Int32 /*CommandId*/ ) 363 { 364 SAL_INFO( "ucb.ucp.cmis", "TODO - RepoContent::abort()" ); 365 // TODO Implement me 366 } 367 getTypes()368 uno::Sequence< uno::Type > SAL_CALL RepoContent::getTypes() 369 { 370 static cppu::OTypeCollection s_aFolderCollection 371 (CPPU_TYPE_REF( lang::XTypeProvider ), 372 CPPU_TYPE_REF( lang::XServiceInfo ), 373 CPPU_TYPE_REF( lang::XComponent ), 374 CPPU_TYPE_REF( ucb::XContent ), 375 CPPU_TYPE_REF( ucb::XCommandProcessor ), 376 CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), 377 CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), 378 CPPU_TYPE_REF( beans::XPropertyContainer ), 379 CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), 380 CPPU_TYPE_REF( container::XChild ) ); 381 return s_aFolderCollection.getTypes(); 382 } 383 getChildren()384 std::vector< uno::Reference< ucb::XContent > > RepoContent::getChildren( ) 385 { 386 std::vector< uno::Reference< ucb::XContent > > result; 387 388 // TODO Cache the results somehow 389 SAL_INFO( "ucb.ucp.cmis", "RepoContent::getChildren" ); 390 391 if ( m_sRepositoryId.isEmpty( ) ) 392 { 393 for ( const auto& rRepo : m_aRepositories ) 394 { 395 URL aUrl( m_aURL ); 396 aUrl.setObjectPath( STD_TO_OUSTR( rRepo->getId( ) ) ); 397 398 uno::Reference< ucb::XContentIdentifier > xId = new ucbhelper::ContentIdentifier( aUrl.asString( ) ); 399 uno::Reference< ucb::XContent > xContent = new RepoContent( m_xContext, m_pProvider, xId, m_aRepositories ); 400 401 result.push_back( xContent ); 402 } 403 } 404 else 405 { 406 // Return the repository root as child 407 OUString sUrl; 408 OUString sEncodedBinding = rtl::Uri::encode( 409 m_aURL.getBindingUrl( ) + "#" + m_sRepositoryId, 410 rtl_UriCharClassRelSegment, 411 rtl_UriEncodeKeepEscapes, 412 RTL_TEXTENCODING_UTF8 ); 413 sUrl = "vnd.libreoffice.cmis://" + sEncodedBinding; 414 415 uno::Reference< ucb::XContentIdentifier > xId = new ucbhelper::ContentIdentifier( sUrl ); 416 uno::Reference< ucb::XContent > xContent = new Content( m_xContext, m_pProvider, xId ); 417 418 result.push_back( xContent ); 419 } 420 return result; 421 } 422 } 423 424 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 425