diff options
Diffstat (limited to 'stoc/source/security/access_controller.cxx')
-rw-r--r-- | stoc/source/security/access_controller.cxx | 863 |
1 files changed, 863 insertions, 0 deletions
diff --git a/stoc/source/security/access_controller.cxx b/stoc/source/security/access_controller.cxx new file mode 100644 index 000000000..c96f1f61f --- /dev/null +++ b/stoc/source/security/access_controller.cxx @@ -0,0 +1,863 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <vector> + +#include <osl/diagnose.h> +#include <osl/mutex.hxx> +#include <osl/thread.hxx> + +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +#include <uno/current_context.h> +#include <uno/lbnames.h> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <com/sun/star/uno/XCurrentContext.hpp> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/security/XAccessController.hpp> +#include <com/sun/star/security/XPolicy.hpp> + +#include "lru_cache.h" +#include "permissions.h" + +#include <memory> + +constexpr OUStringLiteral SERVICE_NAME = u"com.sun.star.security.AccessController"; +constexpr OUStringLiteral USER_CREDS = u"access-control.user-credentials.id"; + + +using namespace ::std; +using namespace ::osl; +using namespace ::cppu; +using namespace ::com::sun::star; +using namespace css::uno; +using namespace stoc_sec; + +namespace { + +// static stuff initialized when loading lib +OUString s_envType = CPPU_CURRENT_LANGUAGE_BINDING_NAME; +constexpr OUStringLiteral s_acRestriction = u"access-control.restriction"; + + +/** ac context intersects permissions of two ac contexts +*/ +class acc_Intersection + : public WeakImplHelper< security::XAccessControlContext > +{ + Reference< security::XAccessControlContext > m_x1, m_x2; + + acc_Intersection( + Reference< security::XAccessControlContext > const & x1, + Reference< security::XAccessControlContext > const & x2 ); + +public: + static Reference< security::XAccessControlContext > create( + Reference< security::XAccessControlContext > const & x1, + Reference< security::XAccessControlContext > const & x2 ); + + // XAccessControlContext impl + virtual void SAL_CALL checkPermission( + Any const & perm ) override; +}; + +acc_Intersection::acc_Intersection( + Reference< security::XAccessControlContext > const & x1, + Reference< security::XAccessControlContext > const & x2 ) + : m_x1( x1 ) + , m_x2( x2 ) +{} + +Reference< security::XAccessControlContext > acc_Intersection::create( + Reference< security::XAccessControlContext > const & x1, + Reference< security::XAccessControlContext > const & x2 ) +{ + if (! x1.is()) + return x2; + if (! x2.is()) + return x1; + return new acc_Intersection( x1, x2 ); +} + +void acc_Intersection::checkPermission( + Any const & perm ) +{ + m_x1->checkPermission( perm ); + m_x2->checkPermission( perm ); +} + +/** ac context unifies permissions of two ac contexts +*/ +class acc_Union + : public WeakImplHelper< security::XAccessControlContext > +{ + Reference< security::XAccessControlContext > m_x1, m_x2; + + acc_Union( + Reference< security::XAccessControlContext > const & x1, + Reference< security::XAccessControlContext > const & x2 ); + +public: + static Reference< security::XAccessControlContext > create( + Reference< security::XAccessControlContext > const & x1, + Reference< security::XAccessControlContext > const & x2 ); + + // XAccessControlContext impl + virtual void SAL_CALL checkPermission( + Any const & perm ) override; +}; + +acc_Union::acc_Union( + Reference< security::XAccessControlContext > const & x1, + Reference< security::XAccessControlContext > const & x2 ) + : m_x1( x1 ) + , m_x2( x2 ) +{} + +Reference< security::XAccessControlContext > acc_Union::create( + Reference< security::XAccessControlContext > const & x1, + Reference< security::XAccessControlContext > const & x2 ) +{ + if (! x1.is()) + return Reference< security::XAccessControlContext >(); // unrestricted + if (! x2.is()) + return Reference< security::XAccessControlContext >(); // unrestricted + return new acc_Union( x1, x2 ); +} + +void acc_Union::checkPermission( + Any const & perm ) +{ + try + { + m_x1->checkPermission( perm ); + } + catch (security::AccessControlException &) + { + m_x2->checkPermission( perm ); + } +} + +/** ac context doing permission checks on static permissions +*/ +class acc_Policy + : public WeakImplHelper< security::XAccessControlContext > +{ + PermissionCollection m_permissions; + +public: + explicit acc_Policy( + PermissionCollection const & permissions ) + : m_permissions( permissions ) + {} + + // XAccessControlContext impl + virtual void SAL_CALL checkPermission( + Any const & perm ) override; +}; + +void acc_Policy::checkPermission( + Any const & perm ) +{ + m_permissions.checkPermission( perm ); +} + +/** current context overriding dynamic ac restriction +*/ +class acc_CurrentContext + : public WeakImplHelper< XCurrentContext > +{ + Reference< XCurrentContext > m_xDelegate; + Any m_restriction; + +public: + acc_CurrentContext( + Reference< XCurrentContext > const & xDelegate, + Reference< security::XAccessControlContext > const & xRestriction ); + + // XCurrentContext impl + virtual Any SAL_CALL getValueByName( OUString const & name ) override; +}; + +acc_CurrentContext::acc_CurrentContext( + Reference< XCurrentContext > const & xDelegate, + Reference< security::XAccessControlContext > const & xRestriction ) + : m_xDelegate( xDelegate ) +{ + if (xRestriction.is()) + { + m_restriction <<= xRestriction; + } + // return empty any otherwise on getValueByName(), not null interface +} + +Any acc_CurrentContext::getValueByName( OUString const & name ) +{ + if (name == s_acRestriction) + { + return m_restriction; + } + else if (m_xDelegate.is()) + { + return m_xDelegate->getValueByName( name ); + } + else + { + return Any(); + } +} + + +Reference< security::XAccessControlContext > getDynamicRestriction( + Reference< XCurrentContext > const & xContext ) +{ + if (xContext.is()) + { + Any acc(xContext->getValueByName(s_acRestriction)); + if (typelib_TypeClass_INTERFACE == acc.pType->eTypeClass) + { + // avoid ref-counting + OUString const & typeName = + OUString::unacquired( &acc.pType->pTypeName ); + if ( typeName == "com.sun.star.security.XAccessControlContext" ) + { + return Reference< security::XAccessControlContext >( + *static_cast< security::XAccessControlContext ** >( acc.pData ) ); + } + else // try to query + { + return Reference< security::XAccessControlContext >::query( + *static_cast< XInterface ** >( acc.pData ) ); + } + } + } + return Reference< security::XAccessControlContext >(); +} + +class cc_reset +{ + void * m_cc; +public: + explicit cc_reset( void * cc ) + : m_cc( cc ) {} + ~cc_reset() + { ::uno_setCurrentContext( m_cc, s_envType.pData, nullptr ); } +}; + +typedef WeakComponentImplHelper< + security::XAccessController, lang::XServiceInfo, lang::XInitialization > t_helper; + + +class AccessController + : public cppu::BaseMutex + , public t_helper +{ + Reference< XComponentContext > m_xComponentContext; + + Reference< security::XPolicy > m_xPolicy; + Reference< security::XPolicy > const & getPolicy(); + + // mode + enum class Mode { Off, On, DynamicOnly, SingleUser, SingleDefaultUser }; + Mode m_mode; + + PermissionCollection m_defaultPermissions; + // for single-user mode + PermissionCollection m_singleUserPermissions; + OUString m_singleUserId; + bool m_defaultPerm_init; + bool m_singleUser_init; + // for multi-user mode + lru_cache< OUString, PermissionCollection, OUStringHash, equal_to< OUString > > + m_user2permissions; + + ThreadData m_rec; + typedef vector< pair< OUString, Any > > t_rec_vec; + void clearPostPoned(); + void checkAndClearPostPoned(); + + PermissionCollection getEffectivePermissions( + Reference< XCurrentContext > const & xContext, + Any const & demanded_perm ); + +protected: + virtual void SAL_CALL disposing() override; + +public: + explicit AccessController( Reference< XComponentContext > const & xComponentContext ); + + // XInitialization impl + virtual void SAL_CALL initialize( + Sequence< Any > const & arguments ) override; + + // XAccessController impl + virtual void SAL_CALL checkPermission( + Any const & perm ) override; + virtual Any SAL_CALL doRestricted( + Reference< security::XAction > const & xAction, + Reference< security::XAccessControlContext > const & xRestriction ) override; + virtual Any SAL_CALL doPrivileged( + Reference< security::XAction > const & xAction, + Reference< security::XAccessControlContext > const & xRestriction ) override; + virtual Reference< security::XAccessControlContext > SAL_CALL getContext() override; + + // XServiceInfo impl + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override; +}; + +AccessController::AccessController( Reference< XComponentContext > const & xComponentContext ) + : t_helper( m_aMutex ) + , m_xComponentContext( xComponentContext ) + , m_mode( Mode::On ) // default + , m_defaultPerm_init( false ) + , m_singleUser_init( false ) + , m_rec( nullptr ) +{ + // The .../mode value had originally been set in + // cppu::add_access_control_entries (cppuhelper/source/servicefactory.cxx) + // to something other than "off" depending on various UNO_AC* bootstrap + // variables that are no longer supported, so this is mostly dead code now: + OUString mode; + if (m_xComponentContext->getValueByName( "/services/" + SERVICE_NAME + "/mode" ) >>= mode) + { + if ( mode == "off" ) + { + m_mode = Mode::Off; + } + else if ( mode == "on" ) + { + m_mode = Mode::On; + } + else if ( mode == "dynamic-only" ) + { + m_mode = Mode::DynamicOnly; + } + else if ( mode == "single-user" ) + { + m_xComponentContext->getValueByName( + "/services/" + SERVICE_NAME + "/single-user-id" ) >>= m_singleUserId; + if (m_singleUserId.isEmpty()) + { + throw RuntimeException( + "expected a user id in component context entry " + "\"/services/" + SERVICE_NAME + "/single-user-id\"!", + static_cast<OWeakObject *>(this) ); + } + m_mode = Mode::SingleUser; + } + else if ( mode == "single-default-user" ) + { + m_mode = Mode::SingleDefaultUser; + } + } + + // switch on caching for Mode::DynamicOnly and Mode::On (shareable multi-user process) + if (Mode::On != m_mode && Mode::DynamicOnly != m_mode) + return; + + sal_Int32 cacheSize = 0; // multi-user cache size + if (! (m_xComponentContext->getValueByName( + "/services/" + SERVICE_NAME + "/user-cache-size" ) >>= cacheSize)) + { + cacheSize = 128; // reasonable default? + } +#ifdef __CACHE_DIAGNOSE + cacheSize = 2; +#endif + m_user2permissions.setSize( cacheSize ); +} + +void AccessController::disposing() +{ + m_mode = Mode::Off; // avoid checks from now on xxx todo review/ better Mode::DynamicOnly? + m_xPolicy.clear(); + m_xComponentContext.clear(); +} + +// XInitialization impl + +void AccessController::initialize( + Sequence< Any > const & arguments ) +{ + // xxx todo: review for forking + // portal forking hack: re-initialize for another user-id + if (Mode::SingleUser != m_mode) // only if in single-user mode + { + throw RuntimeException( + "invalid call: ac must be in \"single-user\" mode!", static_cast<OWeakObject *>(this) ); + } + OUString userId; + arguments[ 0 ] >>= userId; + if ( userId.isEmpty() ) + { + throw RuntimeException( + "expected a user-id as first argument!", static_cast<OWeakObject *>(this) ); + } + // assured that no sync is necessary: no check happens at this forking time + m_singleUserId = userId; + m_singleUser_init = false; +} + + +Reference< security::XPolicy > const & AccessController::getPolicy() +{ + // get policy singleton + if (! m_xPolicy.is()) + { + Reference< security::XPolicy > xPolicy; + m_xComponentContext->getValueByName( + "/singletons/com.sun.star.security.thePolicy" ) >>= xPolicy; + if (!xPolicy.is()) + { + throw SecurityException( + "cannot get policy singleton!", static_cast<OWeakObject *>(this) ); + } + + MutexGuard guard( m_aMutex ); + if (! m_xPolicy.is()) + { + m_xPolicy = xPolicy; + } + } + return m_xPolicy; +} + +#ifdef __DIAGNOSE +static void dumpPermissions( + PermissionCollection const & collection, OUString const & userId = OUString() ) +{ + OUStringBuffer buf( 48 ); + if (!userId.isEmpty()) + { + buf.append( "> dumping permissions of user \"" ); + buf.append( userId ); + buf.append( "\":" ); + } + else + { + buf.append( "> dumping default permissions:" ); + } + SAL_INFO("stoc", buf.makeStringAndClear() ); + Sequence< OUString > permissions( collection.toStrings() ); + OUString const * p = permissions.getConstArray(); + for ( sal_Int32 nPos = 0; nPos < permissions.getLength(); ++nPos ) + { + SAL_INFO("stoc", p[ nPos ] ); + } + SAL_INFO("stoc", "> permission dump done" ); +} +#endif + + +void AccessController::clearPostPoned() +{ + delete static_cast< t_rec_vec * >( m_rec.getData() ); + m_rec.setData( nullptr ); +} + +void AccessController::checkAndClearPostPoned() +{ + // check postponed permissions + std::unique_ptr< t_rec_vec > rec( static_cast< t_rec_vec * >( m_rec.getData() ) ); + m_rec.setData( nullptr ); // takeover ownership + OSL_ASSERT(rec); + if (!rec) + return; + + t_rec_vec const& vec = *rec; + switch (m_mode) + { + case Mode::SingleUser: + { + OSL_ASSERT( m_singleUser_init ); + for (const auto & p : vec) + { + OSL_ASSERT( m_singleUserId == p.first ); + m_singleUserPermissions.checkPermission( p.second ); + } + break; + } + case Mode::SingleDefaultUser: + { + OSL_ASSERT( m_defaultPerm_init ); + for (const auto & p : vec) + { + OSL_ASSERT( p.first.isEmpty() ); // default-user + m_defaultPermissions.checkPermission( p.second ); + } + break; + } + case Mode::On: + { + for (const auto & p : vec) + { + PermissionCollection const * pPermissions; + // lookup policy for user + { + MutexGuard guard( m_aMutex ); + pPermissions = m_user2permissions.lookup( p.first ); + } + OSL_ASSERT( pPermissions ); + if (pPermissions) + { + pPermissions->checkPermission( p.second ); + } + } + break; + } + default: + OSL_FAIL( "### this should never be called in this ac mode!" ); + break; + } +} + +/** this is the only function calling the policy singleton and thus has to take care + of recurring calls! + + @param demanded_perm (if not empty) is the demanded permission of a checkPermission() call + which will be postponed for recurring calls +*/ +PermissionCollection AccessController::getEffectivePermissions( + Reference< XCurrentContext > const & xContext, + Any const & demanded_perm ) +{ + OUString userId; + + switch (m_mode) + { + case Mode::SingleUser: + { + if (m_singleUser_init) + return m_singleUserPermissions; + userId = m_singleUserId; + break; + } + case Mode::SingleDefaultUser: + { + if (m_defaultPerm_init) + return m_defaultPermissions; + break; + } + case Mode::On: + { + if (xContext.is()) + { + xContext->getValueByName( USER_CREDS ) >>= userId; + } + if ( userId.isEmpty() ) + { + throw SecurityException( + "cannot determine current user in multi-user ac!", static_cast<OWeakObject *>(this) ); + } + + // lookup policy for user + MutexGuard guard( m_aMutex ); + PermissionCollection const * pPermissions = m_user2permissions.lookup( userId ); + if (pPermissions) + return *pPermissions; + break; + } + default: + OSL_FAIL( "### this should never be called in this ac mode!" ); + return PermissionCollection(); + } + + // call on policy + // iff this is a recurring call for the default user, then grant all permissions + t_rec_vec * rec = static_cast< t_rec_vec * >( m_rec.getData() ); + if (rec) // tls entry exists => this is recursive call + { + if (demanded_perm.hasValue()) + { + // enqueue + rec->push_back( pair< OUString, Any >( userId, demanded_perm ) ); + } +#ifdef __DIAGNOSE + SAL_INFO("stoc", "> info: recurring call of user: " << userId ); +#endif + return PermissionCollection( new AllPermission() ); + } + else // no tls + { + rec = new t_rec_vec; + m_rec.setData( rec ); + } + + try // calls on API + { + // init default permissions + if (! m_defaultPerm_init) + { + PermissionCollection defaultPermissions( + getPolicy()->getDefaultPermissions() ); + // assign + MutexGuard guard( m_aMutex ); + if (! m_defaultPerm_init) + { + m_defaultPermissions = defaultPermissions; + m_defaultPerm_init = true; + } +#ifdef __DIAGNOSE + dumpPermissions( m_defaultPermissions ); +#endif + } + + PermissionCollection ret; + + // init user permissions + switch (m_mode) + { + case Mode::SingleUser: + { + ret = PermissionCollection( + getPolicy()->getPermissions( userId ), m_defaultPermissions ); + { + // assign + MutexGuard guard( m_aMutex ); + if (m_singleUser_init) + { + ret = m_singleUserPermissions; + } + else + { + m_singleUserPermissions = ret; + m_singleUser_init = true; + } + } +#ifdef __DIAGNOSE + dumpPermissions( ret, userId ); +#endif + break; + } + case Mode::SingleDefaultUser: + { + ret = m_defaultPermissions; + break; + } + case Mode::On: + { + ret = PermissionCollection( + getPolicy()->getPermissions( userId ), m_defaultPermissions ); + { + // cache + MutexGuard guard( m_aMutex ); + m_user2permissions.set( userId, ret ); + } +#ifdef __DIAGNOSE + dumpPermissions( ret, userId ); +#endif + break; + } + default: + break; + } + + // check postponed + checkAndClearPostPoned(); + return ret; + } + catch (const security::AccessControlException & exc) // wrapped into DeploymentException + { + clearPostPoned(); // safety: exception could have happened before checking postponed? + throw DeploymentException( "deployment error (AccessControlException occurred): " + exc.Message, exc.Context ); + } + catch (RuntimeException &) + { + // don't check postponed, just cleanup + clearPostPoned(); + delete static_cast< t_rec_vec * >( m_rec.getData() ); + m_rec.setData( nullptr ); + throw; + } + catch (Exception &) + { + // check postponed permissions first + // => AccessControlExceptions are errors, user exceptions not! + checkAndClearPostPoned(); + throw; + } + catch (...) + { + // don't check postponed, just cleanup + clearPostPoned(); + throw; + } +} + +// XAccessController impl + +void AccessController::checkPermission( + Any const & perm ) +{ + if (rBHelper.bDisposed) + { + throw lang::DisposedException( + "checkPermission() call on disposed AccessController!", static_cast<OWeakObject *>(this) ); + } + + if (Mode::Off == m_mode) + return; + + // first dynamic check of ac contexts + Reference< XCurrentContext > xContext; + ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr ); + Reference< security::XAccessControlContext > xACC( getDynamicRestriction( xContext ) ); + if (xACC.is()) + { + xACC->checkPermission( perm ); + } + + if (Mode::DynamicOnly == m_mode) + return; + + // then static check + getEffectivePermissions( xContext, perm ).checkPermission( perm ); +} + +Any AccessController::doRestricted( + Reference< security::XAction > const & xAction, + Reference< security::XAccessControlContext > const & xRestriction ) +{ + if (rBHelper.bDisposed) + { + throw lang::DisposedException( + "doRestricted() call on disposed AccessController!", static_cast<OWeakObject *>(this) ); + } + + if (Mode::Off == m_mode) // optimize this way, because no dynamic check will be performed + return xAction->run(); + + if (xRestriction.is()) + { + Reference< XCurrentContext > xContext; + ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr ); + + // override restriction + Reference< XCurrentContext > xNewContext( + new acc_CurrentContext( xContext, acc_Intersection::create( + xRestriction, getDynamicRestriction( xContext ) ) ) ); + ::uno_setCurrentContext( xNewContext.get(), s_envType.pData, nullptr ); + cc_reset reset( xContext.get() ); + return xAction->run(); + } + else + { + return xAction->run(); + } +} + +Any AccessController::doPrivileged( + Reference< security::XAction > const & xAction, + Reference< security::XAccessControlContext > const & xRestriction ) +{ + if (rBHelper.bDisposed) + { + throw lang::DisposedException( + "doPrivileged() call on disposed AccessController!", static_cast<OWeakObject *>(this) ); + } + + if (Mode::Off == m_mode) // no dynamic check will be performed + { + return xAction->run(); + } + + Reference< XCurrentContext > xContext; + ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr ); + + Reference< security::XAccessControlContext > xOldRestr( + getDynamicRestriction( xContext ) ); + + if (xOldRestr.is()) // previous restriction + { + // override restriction + Reference< XCurrentContext > xNewContext( + new acc_CurrentContext( xContext, acc_Union::create( xRestriction, xOldRestr ) ) ); + ::uno_setCurrentContext( xNewContext.get(), s_envType.pData, nullptr ); + cc_reset reset( xContext.get() ); + return xAction->run(); + } + else // no previous restriction => never current restriction + { + return xAction->run(); + } +} + +Reference< security::XAccessControlContext > AccessController::getContext() +{ + if (rBHelper.bDisposed) + { + throw lang::DisposedException( + "getContext() call on disposed AccessController!", static_cast<OWeakObject *>(this) ); + } + + if (Mode::Off == m_mode) // optimize this way, because no dynamic check will be performed + { + return new acc_Policy( PermissionCollection( new AllPermission() ) ); + } + + Reference< XCurrentContext > xContext; + ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr ); + + return acc_Intersection::create( + getDynamicRestriction( xContext ), + new acc_Policy( getEffectivePermissions( xContext, Any() ) ) ); +} + +// XServiceInfo impl + +OUString AccessController::getImplementationName() +{ + return "com.sun.star.security.comp.stoc.AccessController"; +} + +sal_Bool AccessController::supportsService( OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + +Sequence< OUString > AccessController::getSupportedServiceNames() +{ + Sequence<OUString> aSNS { SERVICE_NAME }; + return aSNS; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_security_comp_stoc_AccessController_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new AccessController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |