Example #1
0
 def _populate_database(
         cls,
         settings: plaster_pastedeploy.ConfigDict,
         add_test_data: bool
 ) -> None:
     engine = get_engine(settings)
     session_factory = get_session_factory(engine)
     app_config = CFG(settings)
     print("- Populate database with default data -")
     with transaction.manager:
         dbsession = get_tm_session(session_factory, transaction.manager)
         try:
             fixtures = [BaseFixture]
             fixtures_loader = FixturesLoader(dbsession, app_config)
             fixtures_loader.loads(fixtures)
             transaction.commit()
             if add_test_data:
                 app_config.configure_filedepot()
                 fixtures = [ContentFixture]
                 fixtures_loader.loads(fixtures)
             transaction.commit()
             print("Database initialized.")
         except IntegrityError as exc:
             transaction.abort()
             print('Database initialization failed')
             raise DatabaseInitializationFailed(
                 'Warning, there was a problem when adding default data'
                 ', it may have already been added.'
             ) from exc
Example #2
0
class TracimEnv(BaseMiddleware):
    def __init__(self, application, config):
        super().__init__(application, config)
        self._application = application
        self.settings = config["tracim_settings"]
        self.app_config = CFG(self.settings)
        self.app_config.configure_filedepot()
        self.plugin_manager = init_plugin_manager(self.app_config)
        self.engine = get_engine(self.app_config)
        self.session_factory = get_session_factory(self.engine)

    def __call__(self, environ, start_response):
        # TODO - G.M - 18-05-2018 - This code should not create trouble
        # with thread and database, this should be verify.
        # see https://github.com/tracim/tracim_backend/issues/62
        registry = get_current_registry()
        registry.ldap_connector = None
        if AuthType.LDAP in self.app_config.AUTH_TYPES:
            registry = self.setup_ldap(registry, self.app_config)
        environ["tracim_registry"] = registry
        tracim_context = WebdavTracimContext(environ, self.app_config,
                                             self.plugin_manager)
        session = create_dbsession_for_context(self.session_factory,
                                               transaction.manager,
                                               tracim_context)
        tracim_context.dbsession = session
        environ["tracim_context"] = tracim_context
        try:
            for chunk in self._application(environ, start_response):
                yield chunk
            transaction.commit()
        except Exception:
            transaction.abort()
            raise
        finally:
            # NOTE SGD 2020-06-30: avoid circular reference between environment dict and context.
            # This ensures the context will be deleted as soon as this function is exited
            del environ["tracim_context"]
            tracim_context.cleanup()

    def setup_ldap(self, registry: Registry, app_config: CFG):
        manager = ConnectionManager(
            uri=app_config.LDAP_URL,
            bind=app_config.LDAP_BIND_DN,
            passwd=app_config.LDAP_BIND_PASS,
            tls=app_config.LDAP_TLS,
            use_pool=app_config.LDAP_USE_POOL,
            pool_size=app_config.LDAP_POOL_SIZE,
            pool_lifetime=app_config.LDAP_POOL_LIFETIME,
            get_info=app_config.LDAP_GET_INFO,
        )
        registry.ldap_login_query = _LDAPQuery(
            base_dn=app_config.LDAP_USER_BASE_DN,
            filter_tmpl=app_config.LDAP_USER_FILTER,
            scope=ldap3.LEVEL,
            attributes=ldap3.ALL_ATTRIBUTES,
            cache_period=0,
        )
        registry.ldap_connector = Connector(registry, manager)
        return registry
Example #3
0
    def take_action(self, parsed_args: argparse.Namespace) -> None:
        super(DeleteDBCommand, self).take_action(parsed_args)
        config_uri = parsed_args.config_file
        # setup_logging(config_uri)
        settings = get_appsettings(config_uri)
        settings.update(settings.global_conf)
        if 'sqlalchemy.url' not in settings or not settings['sqlalchemy.url']:
            raise InvalidSettingFile('Wrong or empty sqlalchemy database url,'
                                     'check config file')
        engine = get_engine(settings)
        app_config = CFG(settings)
        app_config.configure_filedepot()

        if parsed_args.force:
            print('Database deletion begin.')
            DeclarativeBase.metadata.drop_all(engine)
            print('Database deletion done.')
            try:
                print('Cleaning depot begin.')
                depot = DepotManager.get()
                depot_files = depot.list()
                for file_ in depot_files:
                    depot.delete(file_)
                print('Cleaning depot done.')
            except FileNotFoundError:
                print(
                    'Warning! Can delete depots file, is depot path correctly'
                    ' configured?'
                )
        else:
            raise ForceArgumentNeeded(
                'Warning, You should use --force if you really want to'
                ' delete database.'
            )
Example #4
0
    def take_action(self, parsed_args: argparse.Namespace) -> None:
        super(DeleteDBCommand, self).take_action(parsed_args)
        config_uri = parsed_args.config_file
        # setup_logging(config_uri)
        settings = get_appsettings(config_uri)
        settings.update(settings.global_conf)
        app_config = CFG(settings)
        app_config.configure_filedepot()
        engine = get_engine(app_config)

        if parsed_args.force:
            print("Database deletion begin.")
            DeclarativeBase.metadata.drop_all(engine)
            print("Database deletion done.")
            try:
                print("Cleaning depot begin.")
                depot = DepotManager.get()
                depot_files = depot.list()
                for file_ in depot_files:
                    try:
                        depot.delete(file_)
                    # TODO - G.M - 2019-05-09 - better handling of specific exception here
                    except Exception as exc:
                        traceback.print_exc()
                        print("Something goes wrong during deletion of {}".format(file_))
                        raise exc
                print("Cleaning depot done.")
            except FileNotFoundError:
                print("Warning! Can delete depots file, is depot path correctly" " configured?")
        else:
            raise ForceArgumentNeeded(
                "Warning, You should use --force if you really want to" " delete database."
            )
Example #5
0
def initialize_config_from_environment() -> CFG:
    config_uri = os.environ["TRACIM_CONF_PATH"]
    setup_logging(config_uri)
    settings = get_appsettings(config_uri)
    settings.update(settings.global_conf)
    app_config = CFG(settings)
    app_config.configure_filedepot()
    return app_config
Example #6
0
class TracimEnv(BaseMiddleware):

    def __init__(self, application, config):
        super().__init__(application, config)
        self._application = application
        self.settings = config['tracim_settings']
        self.engine = get_engine(self.settings)
        self.session_factory = get_scoped_session_factory(self.engine)
        self.app_config = CFG(self.settings)
        self.app_config.configure_filedepot()

    def __call__(self, environ, start_response):
        # TODO - G.M - 18-05-2018 - This code should not create trouble
        # with thread and database, this should be verify.
        # see https://github.com/tracim/tracim_backend/issues/62
        tm = transaction.manager
        session = get_tm_session(self.session_factory, tm)
        registry = get_current_registry()
        registry.ldap_connector = None
        if AuthType.LDAP in self.app_config.AUTH_TYPES:
            registry = self.setup_ldap(registry, self.app_config)
        environ['tracim_registry'] =  registry
        environ['tracim_context'] = WebdavTracimContext(environ, self.app_config, session)
        try:
            app = self._application(environ, start_response)
        except Exception as exc:
            transaction.rollback()
            raise exc
        finally:
            transaction.commit()
            session.close()
        return app

    def setup_ldap(self, registry: Registry, app_config: CFG):
        manager = ConnectionManager(
            uri=app_config.LDAP_URL,
            bind=app_config.LDAP_BIND_DN,
            passwd=app_config.LDAP_BIND_PASS,
            tls=app_config.LDAP_TLS,
            use_pool=app_config.LDAP_USE_POOL,
            pool_size=app_config.LDAP_POOL_SIZE,
            pool_lifetime=app_config.LDAP_POOL_LIFETIME,
            get_info=app_config.LDAP_GET_INFO
        )
        registry.ldap_login_query = _LDAPQuery(
            base_dn=app_config.LDAP_USER_BASE_DN,
            filter_tmpl=app_config.LDAP_USER_FILTER,
            scope=ldap3.LEVEL,
            attributes=ldap3.ALL_ATTRIBUTES,
            cache_period=0
        )
        registry.ldap_connector = Connector(registry, manager)
        return registry
Example #7
0
class TracimEnv(BaseMiddleware):
    def __init__(self, application, config):
        super().__init__(application, config)
        self._application = application
        self.settings = config["tracim_settings"]
        self.app_config = CFG(self.settings)
        self.app_config.configure_filedepot()
        self.engine = get_engine(self.app_config)
        self.session_factory = get_scoped_session_factory(self.engine)

    def __call__(self, environ, start_response):
        # TODO - G.M - 18-05-2018 - This code should not create trouble
        # with thread and database, this should be verify.
        # see https://github.com/tracim/tracim_backend/issues/62
        tm = transaction.manager
        session = get_tm_session(self.session_factory, tm)
        registry = get_current_registry()
        registry.ldap_connector = None
        if AuthType.LDAP in self.app_config.AUTH_TYPES:
            registry = self.setup_ldap(registry, self.app_config)
        environ["tracim_registry"] = registry
        environ["tracim_context"] = WebdavTracimContext(
            environ, self.app_config, session)
        try:
            app = self._application(environ, start_response)
        except Exception as exc:
            transaction.rollback()
            raise exc
        finally:
            transaction.commit()
            session.close()
        return app

    def setup_ldap(self, registry: Registry, app_config: CFG):
        manager = ConnectionManager(
            uri=app_config.LDAP_URL,
            bind=app_config.LDAP_BIND_DN,
            passwd=app_config.LDAP_BIND_PASS,
            tls=app_config.LDAP_TLS,
            use_pool=app_config.LDAP_USE_POOL,
            pool_size=app_config.LDAP_POOL_SIZE,
            pool_lifetime=app_config.LDAP_POOL_LIFETIME,
            get_info=app_config.LDAP_GET_INFO,
        )
        registry.ldap_login_query = _LDAPQuery(
            base_dn=app_config.LDAP_USER_BASE_DN,
            filter_tmpl=app_config.LDAP_USER_FILTER,
            scope=ldap3.LEVEL,
            attributes=ldap3.ALL_ATTRIBUTES,
            cache_period=0,
        )
        registry.ldap_connector = Connector(registry, manager)
        return registry
Example #8
0
# coding=utf-8
# Runner for daemon
import os
from pyramid.paster import get_appsettings
from pyramid.paster import setup_logging
from tracim_backend.config import CFG
from tracim_backend.lib.mail_fetcher.daemon import MailFetcherDaemon

config_uri = os.environ['TRACIM_CONF_PATH']

setup_logging(config_uri)
settings = get_appsettings(config_uri)
settings.update(settings.global_conf)
app_config = CFG(settings)
app_config.configure_filedepot()

daemon = MailFetcherDaemon(app_config, burst=False)
daemon.run()
Example #9
0
def web(global_config, **local_settings):
    """ This function returns a Pyramid WSGI application.
    """
    settings = global_config
    settings.update(local_settings)
    # set CFG object
    app_config = CFG(settings)
    app_config.configure_filedepot()
    settings['CFG'] = app_config
    configurator = Configurator(settings=settings, autocommit=True)
    # Add AuthPolicy
    configurator.include("pyramid_beaker")
    configurator.include("pyramid_multiauth")
    policies = []
    if app_config.REMOTE_USER_HEADER:
        policies.append(
            RemoteAuthentificationPolicy(
                remote_user_email_login_header=app_config.REMOTE_USER_HEADER,
            ))
    policies.append(
        CookieSessionAuthentificationPolicy(
            reissue_time=app_config.SESSION_REISSUE_TIME),  # nopep8
    )
    if app_config.API_KEY:
        policies.append(
            ApiTokenAuthentificationPolicy(
                api_key_header=TRACIM_API_KEY_HEADER,
                api_user_email_login_header=TRACIM_API_USER_EMAIL_LOGIN_HEADER
            ), )
    policies.append(
        TracimBasicAuthAuthenticationPolicy(realm=BASIC_AUTH_WEBUI_REALM), )
    # Hack for ldap
    if AuthType.LDAP in app_config.AUTH_TYPES:
        import ldap3
        configurator.include('pyramid_ldap3')
        configurator.ldap_setup(app_config.LDAP_URL,
                                bind=app_config.LDAP_BIND_DN,
                                passwd=app_config.LDAP_BIND_PASS,
                                use_tls=app_config.LDAP_TLS,
                                use_pool=app_config.LDAP_USE_POOL,
                                pool_size=app_config.LDAP_POOL_SIZE,
                                pool_lifetime=app_config.LDAP_POOL_LIFETIME,
                                get_info=app_config.LDAP_GET_INFO)
        configurator.ldap_set_login_query(
            base_dn=app_config.LDAP_USER_BASE_DN,
            filter_tmpl=app_config.LDAP_USER_FILTER,
            scope=ldap3.LEVEL,
            attributes=ldap3.ALL_ATTRIBUTES)

    configurator.include(add_cors_support)
    # make sure to add this before other routes to intercept OPTIONS
    configurator.add_cors_preflight_handler()
    # Default authorization : Accept anything.
    configurator.set_authorization_policy(AcceptAllAuthorizationPolicy())
    authn_policy = MultiAuthenticationPolicy(policies)
    configurator.set_authentication_policy(authn_policy)
    # INFO - GM - 11-04-2018 - set default perm
    # setting default perm is needed to force authentification
    # mecanism in all views.
    configurator.set_default_permission(TRACIM_DEFAULT_PERM)
    # Override default request
    configurator.set_request_factory(TracimRequest)
    # Pyramids "plugin" include.
    configurator.include('pyramid_jinja2')
    # Add SqlAlchemy DB
    configurator.include('.models.setup_models')
    # set Hapic
    context = PyramidContext(
        configurator=configurator,
        default_error_builder=ErrorSchema(),
        debug=app_config.DEBUG,
    )
    hapic.set_context(context)
    # INFO - G.M - 2018-07-04 - global-context exceptions
    # Not found
    context.handle_exception(PageNotFound, HTTPStatus.NOT_FOUND)
    # Bad request
    context.handle_exception(WorkspaceNotFoundInTracimRequest,
                             HTTPStatus.BAD_REQUEST)  # nopep8
    context.handle_exception(UserNotFoundInTracimRequest,
                             HTTPStatus.BAD_REQUEST)  # nopep8
    context.handle_exception(ContentNotFoundInTracimRequest,
                             HTTPStatus.BAD_REQUEST)  # nopep8
    context.handle_exception(WorkspaceNotFound, HTTPStatus.BAD_REQUEST)
    context.handle_exception(UserDoesNotExist, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentNotFound, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentTypeNotExist, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentInNotEditableState, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
    context.handle_exception(InvalidId, HTTPStatus.BAD_REQUEST)
    context.handle_exception(SameValueError, HTTPStatus.BAD_REQUEST)
    # Auth exception
    context.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
    context.handle_exception(UserAuthenticatedIsNotActive,
                             HTTPStatus.FORBIDDEN)
    context.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
    context.handle_exception(InsufficientUserRoleInWorkspace,
                             HTTPStatus.FORBIDDEN)  # nopep8
    context.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
    # Internal server error
    context.handle_exception(OperationalError,
                             HTTPStatus.INTERNAL_SERVER_ERROR)
    context.handle_exception(Exception, HTTPStatus.INTERNAL_SERVER_ERROR)

    # Add controllers
    session_controller = SessionController()
    system_controller = SystemController()
    user_controller = UserController()
    account_controller = AccountController()
    reset_password_controller = ResetPasswordController()
    workspace_controller = WorkspaceController()
    comment_controller = CommentController()
    html_document_controller = HTMLDocumentController()
    thread_controller = ThreadController()
    file_controller = FileController()
    folder_controller = FolderController()
    configurator.include(session_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(system_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(user_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(account_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(reset_password_controller.bind,
                         route_prefix=BASE_API_V2)  # nopep8
    configurator.include(workspace_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(comment_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(html_document_controller.bind,
                         route_prefix=BASE_API_V2)  # nopep8
    configurator.include(thread_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(file_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(folder_controller.bind, route_prefix=BASE_API_V2)
    if app_config.FRONTEND_SERVE:
        configurator.include('pyramid_mako')
        frontend_controller = FrontendController(
            app_config.FRONTEND_DIST_FOLDER_PATH)  # nopep8
        configurator.include(frontend_controller.bind)

    hapic.add_documentation_view(
        '/api/v2/doc',
        'Tracim v2 API',
        'API of Tracim v2',
    )
    return configurator.make_wsgi_app()
Example #10
0
class FunctionalTest(unittest.TestCase):

    fixtures = [BaseFixture]
    config_uri = "tests_configs.ini"
    config_section = "functional_test"

    def _set_logger(self) -> None:
        """
        Set all logger to a high level to avoid getting too much noise for tests
        """
        logger._logger.setLevel("ERROR")
        logging.getLogger().setLevel("ERROR")
        logging.getLogger("sqlalchemy").setLevel("ERROR")
        logging.getLogger("txn").setLevel("ERROR")
        logging.getLogger("cliff").setLevel("ERROR")
        logging.getLogger("_jb_pytest_runner").setLevel("ERROR")

    def setUp(self) -> None:
        self._set_logger()
        DepotManager._clear()
        settings = plaster.get_settings(self.config_uri, self.config_section)
        self.settings = self.override_settings(settings)
        # INFO - G.M - 2019-03-19 - Reset all hapic context: PyramidContext
        # and controllers
        hapic.reset_context()
        # TODO - G.M - 2019-03-19 - Replace this code by something better, see
        # https://github.com/algoo/hapic/issues/144
        hapic._controllers = []
        self.app_config = CFG(self.settings)  # type: CFG
        self.app_config = self.override_app_config(self.app_config)
        self.app_config.configure_filedepot()
        self.connect_database(create_tables=True)
        self.init_database()
        DepotManager._clear()
        self.run_app()

    def connect_database(self, create_tables: bool = False) -> None:
        self.engine = get_engine(self.app_config)
        if create_tables:
            DeclarativeBase.metadata.create_all(self.engine)
        self.session_factory = get_session_factory(self.engine)
        self.session = get_tm_session(self.session_factory,
                                      transaction.manager)

    def override_settings(
        self,
        settings: typing.Dict[str,
                              typing.Any]) -> typing.Dict[str, typing.Any]:
        """
        Allow to override some setting by code.
        by default : do nothing.
        """
        return settings

    def override_app_config(self, app_config: CFG) -> CFG:
        """
        Allow to override app_config parameter for tests
        by default : do nothing.
        """
        return app_config

    def run_app(self) -> None:
        app = web({}, **self.settings)
        self.testapp = TestApp(app)

    def init_database(self):
        with transaction.manager:
            try:
                fixtures_loader = FixturesLoader(self.session, self.app_config)
                fixtures_loader.loads(self.fixtures)
                transaction.commit()
                logger.info(self, "Database initialized.")
            except IntegrityError:
                logger.error(
                    self,
                    "Warning, there was a problem when adding default data"
                    ", it may have already been added:",
                )
                import traceback

                logger.error(self, traceback.format_exc())
                transaction.abort()
                logger.error(self, "Database initialization failed")

    def disconnect_database(self, remove_tables: bool = False) -> None:
        self.session.rollback()
        transaction.abort()
        self.session.close_all()
        self.engine.dispose()
        if remove_tables:
            DeclarativeBase.metadata.drop_all(self.engine)
        DepotManager._clear()

    def tearDown(self) -> None:
        logger.debug(self, "TearDown Test...")
        self.disconnect_database(remove_tables=True)
        testing.tearDown()
Example #11
0
def web(global_config: OrderedDict, **local_settings) -> Router:
    """ This function returns a Pyramid WSGI application.
    """
    settings = deepcopy(global_config)
    settings.update(local_settings)
    # set CFG object
    app_config = CFG(settings)
    app_config.configure_filedepot()
    settings["CFG"] = app_config

    # Init plugin manager
    plugin_manager = init_plugin_manager(app_config)
    settings["plugin_manager"] = plugin_manager

    configurator = Configurator(settings=settings, autocommit=True)
    # Add beaker session cookie
    tracim_setting_for_beaker = sliced_dict(settings, beginning_key_string="session.")
    tracim_setting_for_beaker["session.data_dir"] = app_config.SESSION__DATA_DIR
    tracim_setting_for_beaker["session.lock_dir"] = app_config.SESSION__LOCK_DIR
    tracim_setting_for_beaker["session.httponly"] = app_config.SESSION__HTTPONLY
    tracim_setting_for_beaker["session.secure"] = app_config.SESSION__SECURE
    session_factory = pyramid_beaker.session_factory_from_settings(tracim_setting_for_beaker)
    configurator.set_session_factory(session_factory)
    pyramid_beaker.set_cache_regions_from_settings(tracim_setting_for_beaker)
    # Add AuthPolicy
    configurator.include("pyramid_multiauth")
    policies = []
    if app_config.REMOTE_USER_HEADER:
        policies.append(
            RemoteAuthentificationPolicy(remote_user_login_header=app_config.REMOTE_USER_HEADER)
        )
    policies.append(CookieSessionAuthentificationPolicy())
    policies.append(QueryTokenAuthentificationPolicy())
    if app_config.API__KEY:
        policies.append(
            ApiTokenAuthentificationPolicy(
                api_key_header=TRACIM_API_KEY_HEADER,
                api_user_login_header=TRACIM_API_USER_LOGIN_HEADER,
            )
        )
    policies.append(TracimBasicAuthAuthenticationPolicy(realm=BASIC_AUTH_WEBUI_REALM))
    # Hack for ldap
    if AuthType.LDAP in app_config.AUTH_TYPES:
        import ldap3

        configurator.include("pyramid_ldap3")
        configurator.ldap_setup(
            app_config.LDAP_URL,
            bind=app_config.LDAP_BIND_DN,
            passwd=app_config.LDAP_BIND_PASS,
            use_tls=app_config.LDAP_TLS,
            use_pool=app_config.LDAP_USE_POOL,
            pool_size=app_config.LDAP_POOL_SIZE,
            pool_lifetime=app_config.LDAP_POOL_LIFETIME,
            get_info=app_config.LDAP_GET_INFO,
        )
        configurator.ldap_set_login_query(
            base_dn=app_config.LDAP_USER_BASE_DN,
            filter_tmpl=app_config.LDAP_USER_FILTER,
            scope=ldap3.LEVEL,
            attributes=ldap3.ALL_ATTRIBUTES,
        )

    configurator.include(add_cors_support)
    # make sure to add this before other routes to intercept OPTIONS
    configurator.add_cors_preflight_handler()
    # Default authorization : Accept anything.
    configurator.set_authorization_policy(AcceptAllAuthorizationPolicy())
    authn_policy = MultiAuthenticationPolicy(policies)
    configurator.set_authentication_policy(authn_policy)
    # INFO - GM - 11-04-2018 - set default perm
    # setting default perm is needed to force authentification
    # mechanism in all views.
    configurator.set_default_permission(TRACIM_DEFAULT_PERM)
    # Override default request
    configurator.set_request_factory(TracimRequest)
    # Pyramids "plugin" include.
    # Add SqlAlchemy DB
    init_models(configurator, app_config)
    # set Hapic
    context = TracimPyramidContext(
        configurator=configurator, default_error_builder=ErrorSchema(), debug=app_config.DEBUG
    )
    hapic.set_context(context)
    # INFO - G.M - 2018-07-04 - global-context exceptions
    # Not found
    context.handle_exception(PageNotFound, HTTPStatus.NOT_FOUND)
    # Bad request
    context.handle_exception(WorkspaceNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)
    context.handle_exception(UserNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)
    context.handle_exception(WorkspaceNotFound, HTTPStatus.BAD_REQUEST)
    context.handle_exception(UserDoesNotExist, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentNotFound, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentTypeNotExist, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentInNotEditableState, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
    context.handle_exception(InvalidId, HTTPStatus.BAD_REQUEST)
    context.handle_exception(SameValueError, HTTPStatus.BAD_REQUEST)
    # Auth exception
    context.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
    context.handle_exception(UserGivenIsNotTheSameAsAuthenticated, HTTPStatus.FORBIDDEN)
    context.handle_exception(UserAuthenticatedIsNotActive, HTTPStatus.FORBIDDEN)
    context.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
    context.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
    context.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
    # Internal server error
    context.handle_exception(OperationalError, HTTPStatus.INTERNAL_SERVER_ERROR)
    context.handle_exception(Exception, HTTPStatus.INTERNAL_SERVER_ERROR)

    # Add controllers
    session_controller = SessionController()
    system_controller = SystemController()
    user_controller = UserController()
    account_controller = AccountController()
    reset_password_controller = ResetPasswordController()
    workspace_controller = WorkspaceController()
    comment_controller = CommentController()
    configurator.include(session_controller.bind, route_prefix=BASE_API)
    configurator.include(system_controller.bind, route_prefix=BASE_API)
    configurator.include(user_controller.bind, route_prefix=BASE_API)
    configurator.include(account_controller.bind, route_prefix=BASE_API)
    configurator.include(reset_password_controller.bind, route_prefix=BASE_API)
    configurator.include(workspace_controller.bind, route_prefix=BASE_API)
    configurator.include(comment_controller.bind, route_prefix=BASE_API)

    app_lib = ApplicationApi(app_list=app_list)
    for app in app_lib.get_all():
        app.load_controllers(
            app_config=app_config, configurator=configurator, route_prefix=BASE_API, context=context
        )

    configurator.scan("tracim_backend.lib.utils.authentification")

    # TODO - G.M - 2019-05-17 - check if possible to avoid this import here,
    # import is here because import SearchController without adding it to
    # pyramid make trouble in hapic which try to get view related
    # to controller but failed.
    from tracim_backend.lib.search.search_factory import SearchFactory

    search_controller = SearchFactory.get_search_controller(app_config)

    configurator.include(search_controller.bind, route_prefix=BASE_API)
    if app_config.FRONTEND__SERVE:
        configurator.include("pyramid_mako")
        frontend_controller = FrontendController(
            dist_folder_path=app_config.FRONTEND__DIST_FOLDER_PATH,
            custom_toolbox_folder_path=app_config.FRONTEND__CUSTOM_TOOLBOX_FOLDER_PATH,
            cache_token=app_config.FRONTEND__CACHE_TOKEN,
        )
        configurator.include(frontend_controller.bind)

    # INFO - G.M - 2019-11-27 - Include plugin custom web code
    plugin_manager.hook.web_include(configurator=configurator, app_config=app_config)

    hapic.add_documentation_view("/api/doc", "Tracim API", "API of Tracim")
    return configurator.make_wsgi_app()
Example #12
0
    def take_action(self, parsed_args: argparse.Namespace) -> None:
        super(UpdateNamingConventionsV1ToV2Command,
              self).take_action(parsed_args)
        config_uri = parsed_args.config_file
        settings = get_appsettings(config_uri)
        settings.update(settings.global_conf)
        app_config = CFG(settings)
        app_config.configure_filedepot()
        engine = get_engine(app_config)
        inspector = reflection.Inspector.from_engine(engine)
        v1_unique_convention = re.compile(r"uk__(\w+)__(\w+)")
        v1_foreign_key_convention = re.compile(r"fk__(\w+)__(\w+)__(\w+)")
        v1_primary_key_convention = re.compile(r"pk__(\w+)")

        if not engine.dialect.name.startswith("postgresql"):
            raise ValueError(
                "This command is only supported on PostgreSQL databases")

        with engine.begin():
            for table_name in inspector.get_table_names():
                if table_name == "migrate_version":
                    continue

                for unique_constraint in inspector.get_unique_constraints(
                        table_name):
                    match = v1_unique_convention.search(
                        unique_constraint["name"])
                    if match:
                        new_name = "uq__{}__{}".format(match.group(1),
                                                       match.group(2))
                        engine.execute(
                            "ALTER TABLE {} RENAME CONSTRAINT {} TO {}".format(
                                table_name, unique_constraint["name"],
                                new_name))

                for foreign_key in inspector.get_foreign_keys(table_name):
                    match = v1_foreign_key_convention.search(
                        foreign_key["name"])
                    # special cases for content_revisions and revision_read_status
                    if foreign_key[
                            "name"] == "fk__content_revisions__owner_id":
                        new_name = "fk_content_revisions_owner_id_users"
                        engine.execute(
                            "ALTER TABLE {} RENAME CONSTRAINT {} TO {}".format(
                                table_name, foreign_key["name"], new_name))
                    elif foreign_key[
                            "name"] == "revision_read_status_revision_id_fkey":
                        new_name = "fk_revision_read_status_revision_id_content_revisions"
                        engine.execute(
                            "ALTER TABLE {} RENAME CONSTRAINT {} TO {}".format(
                                table_name, foreign_key["name"], new_name))
                    elif foreign_key[
                            "name"] == "revision_read_status_user_id_fkey":
                        new_name = "fk_revision_read_status_user_id_users"
                        engine.execute(
                            "ALTER TABLE {} RENAME CONSTRAINT {} TO {}".format(
                                table_name, foreign_key["name"], new_name))
                    elif match:
                        new_name = "fk_{}_{}_{}".format(
                            match.group(1), match.group(2), match.group(3))
                        engine.execute(
                            "ALTER TABLE {} RENAME CONSTRAINT {} TO {}".format(
                                table_name, foreign_key["name"], new_name))

                primary_key = inspector.get_pk_constraint(table_name)
                if primary_key:
                    match = v1_primary_key_convention.search(
                        primary_key["name"])
                    if primary_key["name"] == "pk__users__user_id":
                        engine.execute(
                            "ALTER INDEX {} RENAME TO pk_users".format(
                                primary_key["name"]))
                    elif primary_key[
                            "name"] == "pk__content_revisions__revision_id":
                        engine.execute(
                            "ALTER INDEX {} RENAME TO pk_content_revisions".
                            format(primary_key["name"]))
                    elif primary_key[
                            "name"] == "pk__user_workspace__user_id__workspace_id":
                        engine.execute(
                            "ALTER INDEX {} RENAME TO pk_user_workspace".
                            format(primary_key["name"]))
                    elif primary_key["name"] == "pk__workspace__workspace_id":
                        engine.execute(
                            "ALTER INDEX {} RENAME TO pk_workspaces".format(
                                primary_key["name"]))
                    elif primary_key["name"] == "revision_read_status_pkey":
                        engine.execute(
                            "ALTER INDEX {} RENAME TO pk_revision_read_status".
                            format(primary_key["name"]))
                    elif match:
                        new_name = "pk_{}".format(match.group(1))
                        engine.execute("ALTER INDEX {} RENAME TO {}".format(
                            primary_key["name"], new_name))
Example #13
0
def web(global_config, **local_settings):
    """ This function returns a Pyramid WSGI application.
    """
    settings = global_config
    settings.update(local_settings)
    # set CFG object
    app_config = CFG(settings)
    app_config.configure_filedepot()
    settings['CFG'] = app_config
    configurator = Configurator(settings=settings, autocommit=True)
    # Add AuthPolicy
    configurator.include("pyramid_beaker")
    configurator.include("pyramid_multiauth")
    policies = []
    if app_config.REMOTE_USER_HEADER:
        policies.append(
            RemoteAuthentificationPolicy(
                remote_user_email_login_header=app_config.REMOTE_USER_HEADER,
            )
        )
    policies.append(
        CookieSessionAuthentificationPolicy(
            reissue_time=app_config.SESSION_REISSUE_TIME),  # nopep8
    )
    if app_config.API_KEY:
        policies.append(
            ApiTokenAuthentificationPolicy(
                api_key_header=TRACIM_API_KEY_HEADER,
                api_user_email_login_header=TRACIM_API_USER_EMAIL_LOGIN_HEADER
            ),
        )
    policies.append(
        TracimBasicAuthAuthenticationPolicy(
            realm=BASIC_AUTH_WEBUI_REALM
        ),
    )
    # Hack for ldap
    if AuthType.LDAP in app_config.AUTH_TYPES:
        import ldap3
        configurator.include('pyramid_ldap3')
        configurator.ldap_setup(
            app_config.LDAP_URL,
            bind=app_config.LDAP_BIND_DN,
            passwd=app_config.LDAP_BIND_PASS,
            use_tls=app_config.LDAP_TLS,
            use_pool=app_config.LDAP_USE_POOL,
            pool_size=app_config.LDAP_POOL_SIZE,
            pool_lifetime=app_config.LDAP_POOL_LIFETIME,
            get_info=app_config.LDAP_GET_INFO
        )
        configurator.ldap_set_login_query(
            base_dn=app_config.LDAP_USER_BASE_DN,
            filter_tmpl=app_config.LDAP_USER_FILTER,
            scope=ldap3.LEVEL,
            attributes=ldap3.ALL_ATTRIBUTES
        )

    configurator.include(add_cors_support)
    # make sure to add this before other routes to intercept OPTIONS
    configurator.add_cors_preflight_handler()
    # Default authorization : Accept anything.
    configurator.set_authorization_policy(AcceptAllAuthorizationPolicy())
    authn_policy = MultiAuthenticationPolicy(policies)
    configurator.set_authentication_policy(authn_policy)
    # INFO - GM - 11-04-2018 - set default perm
    # setting default perm is needed to force authentification
    # mecanism in all views.
    configurator.set_default_permission(TRACIM_DEFAULT_PERM)
    # Override default request
    configurator.set_request_factory(TracimRequest)
    # Pyramids "plugin" include.
    configurator.include('pyramid_jinja2')
    # Add SqlAlchemy DB
    configurator.include('.models.setup_models')
    # set Hapic
    context = PyramidContext(
        configurator=configurator,
        default_error_builder=ErrorSchema(),
        debug=app_config.DEBUG,
    )
    hapic.set_context(context)
    # INFO - G.M - 2018-07-04 - global-context exceptions
    # Not found
    context.handle_exception(PageNotFound, HTTPStatus.NOT_FOUND)
    # Bad request
    context.handle_exception(WorkspaceNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
    context.handle_exception(UserNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
    context.handle_exception(ContentNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
    context.handle_exception(WorkspaceNotFound, HTTPStatus.BAD_REQUEST)
    context.handle_exception(UserDoesNotExist, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentNotFound, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentTypeNotExist, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentInNotEditableState, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
    context.handle_exception(InvalidId, HTTPStatus.BAD_REQUEST)
    context.handle_exception(SameValueError, HTTPStatus.BAD_REQUEST)
    # Auth exception
    context.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
    context.handle_exception(UserAuthenticatedIsNotActive, HTTPStatus.FORBIDDEN)
    context.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
    context.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)  # nopep8
    context.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
    # Internal server error
    context.handle_exception(OperationalError, HTTPStatus.INTERNAL_SERVER_ERROR)
    context.handle_exception(Exception, HTTPStatus.INTERNAL_SERVER_ERROR)

    # Add controllers
    session_controller = SessionController()
    system_controller = SystemController()
    user_controller = UserController()
    account_controller = AccountController()
    reset_password_controller = ResetPasswordController()
    workspace_controller = WorkspaceController()
    comment_controller = CommentController()
    html_document_controller = HTMLDocumentController()
    thread_controller = ThreadController()
    file_controller = FileController()
    folder_controller = FolderController()
    configurator.include(session_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(system_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(user_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(account_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(reset_password_controller.bind, route_prefix=BASE_API_V2)  # nopep8
    configurator.include(workspace_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(comment_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(html_document_controller.bind, route_prefix=BASE_API_V2)  # nopep8
    configurator.include(thread_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(file_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(folder_controller.bind, route_prefix=BASE_API_V2)
    if app_config.FRONTEND_SERVE:
        configurator.include('pyramid_mako')
        frontend_controller = FrontendController(app_config.FRONTEND_DIST_FOLDER_PATH)  # nopep8
        configurator.include(frontend_controller.bind)

    hapic.add_documentation_view(
        '/api/v2/doc',
        'Tracim v2 API',
        'API of Tracim v2',
    )
    return configurator.make_wsgi_app()
Example #14
0
class FunctionalTest(unittest.TestCase):

    fixtures = [BaseFixture]
    config_uri = 'tests_configs.ini'
    config_section = 'functional_test'

    def _set_logger(self) -> None:
        """
        Set all logger to a high level to avoid getting too much noise for tests
        """
        logger._logger.setLevel('ERROR')
        logging.getLogger().setLevel('ERROR')
        logging.getLogger('sqlalchemy').setLevel('ERROR')
        logging.getLogger('txn').setLevel('ERROR')
        logging.getLogger('cliff').setLevel('ERROR')
        logging.getLogger('_jb_pytest_runner').setLevel('ERROR')

    def setUp(self) -> None:
        self._set_logger()
        DepotManager._clear()
        settings = plaster.get_settings(self.config_uri, self.config_section)
        self.settings = self.override_settings(settings)
        hapic.reset_context()
        self.connect_database(create_tables=True)
        self.app_config = CFG(self.settings)
        self.app_config.configure_filedepot()
        self.init_database(self.settings)
        DepotManager._clear()
        self.run_app()

    def connect_database(self, create_tables: bool = False) -> None:
        self.engine = get_engine(self.settings)
        if create_tables:
            DeclarativeBase.metadata.create_all(self.engine)
        self.session_factory = get_session_factory(self.engine)
        self.session = get_tm_session(self.session_factory,
                                      transaction.manager)

    def override_settings(
        self, settings: typing.Dict[str, typing.Any]
    ) -> typing.Dict[str, typing.Any]:  # nopep8
        """
        Allow to override some setting by code.
        by default : do nothing.
        """
        return settings

    def run_app(self) -> None:
        app = web({}, **self.settings)
        self.testapp = TestApp(app)

    def init_database(self, settings: typing.Dict[str, typing.Any]):
        with transaction.manager:
            try:
                fixtures_loader = FixturesLoader(self.session, self.app_config)
                fixtures_loader.loads(self.fixtures)
                transaction.commit()
                logger.info(self, "Database initialized.")
            except IntegrityError:
                logger.error(
                    self,
                    'Warning, there was a problem when adding default data'  # nopep8
                    ', it may have already been added:')
                import traceback
                logger.error(self, traceback.format_exc())
                transaction.abort()
                logger.error(self, 'Database initialization failed')

    def disconnect_database(self, remove_tables: bool = False) -> None:
        self.session.rollback()
        transaction.abort()
        self.session.close_all()
        self.engine.dispose()
        if remove_tables:
            DeclarativeBase.metadata.drop_all(self.engine)
        DepotManager._clear()

    def tearDown(self) -> None:
        logger.debug(self, 'TearDown Test...')
        self.disconnect_database(remove_tables=True)
        testing.tearDown()
Example #15
0
def web(global_config, **local_settings):
    """ This function returns a Pyramid WSGI application.
    """
    settings = deepcopy(global_config)
    settings.update(local_settings)
    # set CFG object
    app_config = CFG(settings)
    app_config.configure_filedepot()
    settings["CFG"] = app_config
    configurator = Configurator(settings=settings, autocommit=True)
    # Add beaker session cookie
    tracim_setting_for_beaker = sliced_dict(settings, beginning_key_string="session.")
    tracim_setting_for_beaker["session.data_dir"] = app_config.SESSION__DATA_DIR
    tracim_setting_for_beaker["session.lock_dir"] = app_config.SESSION__LOCK_DIR
    session_factory = pyramid_beaker.session_factory_from_settings(tracim_setting_for_beaker)
    configurator.set_session_factory(session_factory)
    pyramid_beaker.set_cache_regions_from_settings(tracim_setting_for_beaker)
    # Add AuthPolicy
    configurator.include("pyramid_multiauth")
    policies = []
    if app_config.REMOTE_USER_HEADER:
        policies.append(
            RemoteAuthentificationPolicy(
                remote_user_email_login_header=app_config.REMOTE_USER_HEADER
            )
        )
    policies.append(
        CookieSessionAuthentificationPolicy(reissue_time=app_config.SESSION__REISSUE_TIME)
    )
    if app_config.API__KEY:
        policies.append(
            ApiTokenAuthentificationPolicy(
                api_key_header=TRACIM_API_KEY_HEADER,
                api_user_email_login_header=TRACIM_API_USER_EMAIL_LOGIN_HEADER,
            )
        )
    policies.append(TracimBasicAuthAuthenticationPolicy(realm=BASIC_AUTH_WEBUI_REALM))
    # Hack for ldap
    if AuthType.LDAP in app_config.AUTH_TYPES:
        import ldap3

        configurator.include("pyramid_ldap3")
        configurator.ldap_setup(
            app_config.LDAP_URL,
            bind=app_config.LDAP_BIND_DN,
            passwd=app_config.LDAP_BIND_PASS,
            use_tls=app_config.LDAP_TLS,
            use_pool=app_config.LDAP_USE_POOL,
            pool_size=app_config.LDAP_POOL_SIZE,
            pool_lifetime=app_config.LDAP_POOL_LIFETIME,
            get_info=app_config.LDAP_GET_INFO,
        )
        configurator.ldap_set_login_query(
            base_dn=app_config.LDAP_USER_BASE_DN,
            filter_tmpl=app_config.LDAP_USER_FILTER,
            scope=ldap3.LEVEL,
            attributes=ldap3.ALL_ATTRIBUTES,
        )

    configurator.include(add_cors_support)
    # make sure to add this before other routes to intercept OPTIONS
    configurator.add_cors_preflight_handler()
    # Default authorization : Accept anything.
    configurator.set_authorization_policy(AcceptAllAuthorizationPolicy())
    authn_policy = MultiAuthenticationPolicy(policies)
    configurator.set_authentication_policy(authn_policy)
    # INFO - GM - 11-04-2018 - set default perm
    # setting default perm is needed to force authentification
    # mecanism in all views.
    configurator.set_default_permission(TRACIM_DEFAULT_PERM)
    # Override default request
    configurator.set_request_factory(TracimRequest)
    # Pyramids "plugin" include.
    configurator.include("pyramid_jinja2")
    # Add SqlAlchemy DB
    init_models(configurator, app_config)
    # set Hapic
    context = PyramidContext(
        configurator=configurator, default_error_builder=ErrorSchema(), debug=app_config.DEBUG
    )
    hapic.set_context(context)
    # INFO - G.M - 2018-07-04 - global-context exceptions
    # Not found
    context.handle_exception(PageNotFound, HTTPStatus.NOT_FOUND)
    # Bad request
    context.handle_exception(WorkspaceNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)
    context.handle_exception(UserNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)
    context.handle_exception(WorkspaceNotFound, HTTPStatus.BAD_REQUEST)
    context.handle_exception(UserDoesNotExist, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentNotFound, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentTypeNotExist, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentInNotEditableState, HTTPStatus.BAD_REQUEST)
    context.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
    context.handle_exception(InvalidId, HTTPStatus.BAD_REQUEST)
    context.handle_exception(SameValueError, HTTPStatus.BAD_REQUEST)
    # Auth exception
    context.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
    context.handle_exception(UserGivenIsNotTheSameAsAuthenticated, HTTPStatus.FORBIDDEN)
    context.handle_exception(UserAuthenticatedIsNotActive, HTTPStatus.FORBIDDEN)
    context.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
    context.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
    context.handle_exception(InsufficientUserProfile, HTTPStatus.FORBIDDEN)
    # Internal server error
    context.handle_exception(OperationalError, HTTPStatus.INTERNAL_SERVER_ERROR)
    context.handle_exception(Exception, HTTPStatus.INTERNAL_SERVER_ERROR)

    # Add controllers
    session_controller = SessionController()
    system_controller = SystemController()
    user_controller = UserController()
    account_controller = AccountController()
    reset_password_controller = ResetPasswordController()
    workspace_controller = WorkspaceController()
    comment_controller = CommentController()
    html_document_controller = HTMLDocumentController()
    thread_controller = ThreadController()
    file_controller = FileController()
    folder_controller = FolderController()
    configurator.include(session_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(system_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(user_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(account_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(reset_password_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(workspace_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(comment_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(html_document_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(thread_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(file_controller.bind, route_prefix=BASE_API_V2)
    configurator.include(folder_controller.bind, route_prefix=BASE_API_V2)
    if app_config.CALDAV__ENABLED:
        # FIXME - G.M - 2019-03-18 - check if possible to avoid this import here,
        # import is here because import AgendaController without adding it to
        # pyramid make trouble in hapic which try to get view related
        # to controller but failed.
        from tracim_backend.views.agenda_api.agenda_controller import AgendaController

        configurator.include(add_www_authenticate_header_for_caldav)
        # caldav exception
        context.handle_exception(CaldavNotAuthorized, HTTPStatus.FORBIDDEN)
        context.handle_exception(CaldavNotAuthenticated, HTTPStatus.UNAUTHORIZED)
        # controller
        radicale_proxy_controller = RadicaleProxyController(
            proxy_base_address=app_config.CALDAV__RADICALE_PROXY__BASE_URL,
            radicale_base_path=app_config.CALDAV__RADICALE__BASE_PATH,
            radicale_user_path=app_config.CALDAV__RADICALE__USER_PATH,
            radicale_workspace_path=app_config.CALDAV_RADICALE_WORKSPACE_PATH,
        )
        agenda_controller = AgendaController()
        configurator.include(agenda_controller.bind, route_prefix=BASE_API_V2)
        configurator.include(radicale_proxy_controller.bind)

    if app_config.FRONTEND__SERVE:
        configurator.include("pyramid_mako")
        frontend_controller = FrontendController(app_config.FRONTEND__DIST_FOLDER_PATH)
        configurator.include(frontend_controller.bind)

    hapic.add_documentation_view("/api/v2/doc", "Tracim v2 API", "API of Tracim v2")
    return configurator.make_wsgi_app()