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