--- a/server/__init__.py Mon Jan 04 18:40:30 2016 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,364 +0,0 @@
-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Server subcube of cubicweb : defines objects used only on the server
-(repository) side
-
-The server module contains functions to initialize a new repository.
-"""
-from __future__ import print_function
-
-__docformat__ = "restructuredtext en"
-
-import sys
-from os.path import join, exists
-from glob import glob
-from contextlib import contextmanager
-
-from six import text_type, string_types
-from six.moves import filter
-
-from logilab.common.modutils import LazyObject
-from logilab.common.textutils import splitstrip
-from logilab.common.registry import yes
-from logilab import database
-
-from yams import BASE_GROUPS
-
-from cubicweb import CW_SOFTWARE_ROOT
-from cubicweb.appobject import AppObject
-
-class ShuttingDown(BaseException):
- """raised when trying to access some resources while the repository is
- shutting down. Inherit from BaseException so that `except Exception` won't
- catch it.
- """
-
-# server-side services #########################################################
-
-class Service(AppObject):
- """Base class for services.
-
- A service is a selectable object that performs an action server-side.
- Use :class:`cubicweb.dbapi.Connection.call_service` to call them from
- the web-side.
-
- When inheriting this class, do not forget to define at least the __regid__
- attribute (and probably __select__ too).
- """
- __registry__ = 'services'
- __select__ = yes()
-
- def call(self, **kwargs):
- raise NotImplementedError
-
-
-# server-side debugging ########################################################
-
-# server debugging flags. They may be combined using binary operators.
-
-#:no debug information
-DBG_NONE = 0 #: no debug information
-#: rql execution information
-DBG_RQL = 1
-#: executed sql
-DBG_SQL = 2
-#: repository events
-DBG_REPO = 4
-#: multi-sources
-DBG_MS = 8
-#: hooks
-DBG_HOOKS = 16
-#: operations
-DBG_OPS = 32
-#: security
-DBG_SEC = 64
-#: more verbosity
-DBG_MORE = 128
-#: all level enabled
-DBG_ALL = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_HOOKS + DBG_OPS + DBG_SEC + DBG_MORE
-
-_SECURITY_ITEMS = []
-_SECURITY_CAPS = ['read', 'add', 'update', 'delete', 'transition']
-
-#: current debug mode
-DEBUG = 0
-
-@contextmanager
-def tunesecurity(items=(), capabilities=()):
- """Context manager to use in conjunction with DBG_SEC.
-
- This allows some tuning of:
- * the monitored capabilities ('read', 'add', ....)
- * the object being checked by the security checkers
-
- When no item is given, all of them will be watched.
- By default all capabilities are monitored, unless specified.
-
- Example use::
-
- from cubicweb.server import debugged, DBG_SEC, tunesecurity
- with debugged(DBG_SEC):
- with tunesecurity(items=('Elephant', 'trumps'),
- capabilities=('update', 'delete')):
- babar.cw_set(trumps=celeste)
- flore.cw_delete()
-
- ==>
-
- check_perm: 'update' 'relation Elephant.trumps.Elephant'
- [(ERQLExpression(Any X WHERE U has_update_permission X, X eid %(x)s, U eid %(u)s),
- {'eid': 2167}, True)]
- check_perm: 'delete' 'Elephant'
- [(ERQLExpression(Any X WHERE U has_delete_permission X, X eid %(x)s, U eid %(u)s),
- {'eid': 2168}, True)]
-
- """
- olditems = _SECURITY_ITEMS[:]
- _SECURITY_ITEMS.extend(list(items))
- oldactions = _SECURITY_CAPS[:]
- _SECURITY_CAPS[:] = capabilities
- yield
- _SECURITY_ITEMS[:] = olditems
- _SECURITY_CAPS[:] = oldactions
-
-def set_debug(debugmode):
- """change the repository debugging mode"""
- global DEBUG
- if not debugmode:
- DEBUG = 0
- return
- if isinstance(debugmode, string_types):
- for mode in splitstrip(debugmode, sep='|'):
- DEBUG |= globals()[mode]
- else:
- DEBUG |= debugmode
-
-class debugged(object):
- """Context manager and decorator to help debug the repository.
-
- It can be used either as a context manager:
-
- >>> with debugged('DBG_RQL | DBG_REPO'):
- ... # some code in which you want to debug repository activity,
- ... # seing information about RQL being executed an repository events.
-
- or as a function decorator:
-
- >>> @debugged('DBG_RQL | DBG_REPO')
- ... def some_function():
- ... # some code in which you want to debug repository activity,
- ... # seing information about RQL being executed an repository events
-
- The debug mode will be reset to its original value when leaving the "with"
- block or the decorated function.
- """
- def __init__(self, debugmode):
- self.debugmode = debugmode
- self._clevel = None
-
- def __enter__(self):
- """enter with block"""
- self._clevel = DEBUG
- set_debug(self.debugmode)
-
- def __exit__(self, exctype, exc, traceback):
- """leave with block"""
- set_debug(self._clevel)
- return traceback is None
-
- def __call__(self, func):
- """decorate function"""
- def wrapped(*args, **kwargs):
- _clevel = DEBUG
- set_debug(self.debugmode)
- try:
- return func(*args, **kwargs)
- finally:
- set_debug(self._clevel)
- return wrapped
-
-# database initialization ######################################################
-
-def create_user(session, login, pwd, *groups):
- # monkey patch this method if you want to customize admin/anon creation
- # (that maybe necessary if you change CWUser's schema)
- user = session.create_entity('CWUser', login=login, upassword=pwd)
- for group in groups:
- session.execute('SET U in_group G WHERE U eid %(u)s, G name %(group)s',
- {'u': user.eid, 'group': text_type(group)})
- return user
-
-def init_repository(config, interactive=True, drop=False, vreg=None,
- init_config=None):
- """initialise a repository database by creating tables add filling them
- with the minimal set of entities (ie at least the schema, base groups and
- a initial user)
- """
- from cubicweb.repoapi import get_repository, connect
- from cubicweb.server.repository import Repository
- from cubicweb.server.utils import manager_userpasswd
- from cubicweb.server.sqlutils import sqlexec, sqlschema, sql_drop_all_user_tables
- from cubicweb.server.sqlutils import _SQL_DROP_ALL_USER_TABLES_FILTER_FUNCTION as drop_filter
- # configuration to avoid db schema loading and user'state checking
- # on connection
- config.creating = True
- config.consider_user_state = False
- config.cubicweb_appobject_path = set(('hooks', 'entities'))
- config.cube_appobject_path = set(('hooks', 'entities'))
- # only enable the system source at initialization time
- repo = Repository(config, vreg=vreg)
- if init_config is not None:
- # further config initialization once it has been bootstrapped
- init_config(config)
- schema = repo.schema
- sourcescfg = config.read_sources_file()
- source = sourcescfg['system']
- driver = source['db-driver']
- with repo.internal_cnx() as cnx:
- sqlcnx = cnx.cnxset.cnx
- sqlcursor = cnx.cnxset.cu
- execute = sqlcursor.execute
- if drop:
- helper = database.get_db_helper(driver)
- dropsql = sql_drop_all_user_tables(helper, sqlcursor)
- # We may fail dropping some tables because of table dependencies, in a first pass.
- # So, we try a second drop sequence to drop remaining tables if needed.
- # Note that 2 passes is an arbitrary choice as it seems enough for our usecases
- # (looping may induce infinite recursion when user have no rights for example).
- # Here we try to keep code simple and backend independent. That's why we don't try to
- # distinguish remaining tables (missing privileges, dependencies, ...).
- failed = sqlexec(dropsql, execute, cnx=sqlcnx,
- pbtitle='-> dropping tables (first pass)')
- if failed:
- failed = sqlexec(failed, execute, cnx=sqlcnx,
- pbtitle='-> dropping tables (second pass)')
- remainings = list(filter(drop_filter, helper.list_tables(sqlcursor)))
- assert not remainings, 'Remaining tables: %s' % ', '.join(remainings)
- handler = config.migration_handler(schema, interactive=False, repo=repo, cnx=cnx)
- # install additional driver specific sql files
- handler.cmd_install_custom_sql_scripts()
- for cube in reversed(config.cubes()):
- handler.cmd_install_custom_sql_scripts(cube)
- _title = '-> creating tables '
- print(_title, end=' ')
- # schema entities and relations tables
- # can't skip entities table even if system source doesn't support them,
- # they are used sometimes by generated sql. Keeping them empty is much
- # simpler than fixing this...
- schemasql = sqlschema(schema, driver)
- #skip_entities=[str(e) for e in schema.entities()
- # if not repo.system_source.support_entity(str(e))])
- failed = sqlexec(schemasql, execute, pbtitle=_title, delimiter=';;')
- if failed:
- print('The following SQL statements failed. You should check your schema.')
- print(failed)
- raise Exception('execution of the sql schema failed, you should check your schema')
- sqlcursor.close()
- sqlcnx.commit()
- with repo.internal_cnx() as cnx:
- # insert entity representing the system source
- ssource = cnx.create_entity('CWSource', type=u'native', name=u'system')
- repo.system_source.eid = ssource.eid
- cnx.execute('SET X cw_source X WHERE X eid %(x)s', {'x': ssource.eid})
- # insert base groups and default admin
- print('-> inserting default user and default groups.')
- try:
- login = text_type(sourcescfg['admin']['login'])
- pwd = sourcescfg['admin']['password']
- except KeyError:
- if interactive:
- msg = 'enter login and password of the initial manager account'
- login, pwd = manager_userpasswd(msg=msg, confirm=True)
- else:
- login, pwd = text_type(source['db-user']), source['db-password']
- # sort for eid predicatability as expected in some server tests
- for group in sorted(BASE_GROUPS):
- cnx.create_entity('CWGroup', name=text_type(group))
- admin = create_user(cnx, login, pwd, u'managers')
- cnx.execute('SET X owned_by U WHERE X is IN (CWGroup,CWSource), U eid %(u)s',
- {'u': admin.eid})
- cnx.commit()
- repo.shutdown()
- # re-login using the admin user
- config._cubes = None # avoid assertion error
- repo = get_repository(config=config)
- with connect(repo, login, password=pwd) as cnx:
- with cnx.security_enabled(False, False):
- repo.system_source.eid = ssource.eid # redo this manually
- handler = config.migration_handler(schema, interactive=False,
- cnx=cnx, repo=repo)
- # serialize the schema
- initialize_schema(config, schema, handler)
- # yoo !
- cnx.commit()
- repo.system_source.init_creating()
- cnx.commit()
- repo.shutdown()
- # restore initial configuration
- config.creating = False
- config.consider_user_state = True
- # (drop instance attribute to get back to class attribute)
- del config.cubicweb_appobject_path
- del config.cube_appobject_path
- print('-> database for instance %s initialized.' % config.appid)
-
-
-def initialize_schema(config, schema, mhandler, event='create'):
- from cubicweb.server.schemaserial import serialize_schema
- cnx = mhandler.cnx
- cubes = config.cubes()
- # deactivate every hooks but those responsible to set metadata
- # so, NO INTEGRITY CHECKS are done, to have quicker db creation.
- # Active integrity is kept else we may pb such as two default
- # workflows for one entity type.
- with cnx.deny_all_hooks_but('metadata', 'activeintegrity'):
- # execute cubicweb's pre<event> script
- mhandler.cmd_exec_event_script('pre%s' % event)
- # execute cubes pre<event> script if any
- for cube in reversed(cubes):
- mhandler.cmd_exec_event_script('pre%s' % event, cube)
- # execute instance's pre<event> script (useful in tests)
- mhandler.cmd_exec_event_script('pre%s' % event, apphome=True)
- # enter instance'schema into the database
- serialize_schema(cnx, schema)
- cnx.commit()
- # execute cubicweb's post<event> script
- mhandler.cmd_exec_event_script('post%s' % event)
- # execute cubes'post<event> script if any
- for cube in reversed(cubes):
- mhandler.cmd_exec_event_script('post%s' % event, cube)
- # execute instance's post<event> script (useful in tests)
- mhandler.cmd_exec_event_script('post%s' % event, apphome=True)
-
-
-# sqlite'stored procedures have to be registered at connection opening time
-from logilab.database import SQL_CONNECT_HOOKS
-
-# add to this set relations which should have their add security checking done
-# *BEFORE* adding the actual relation (done after by default)
-BEFORE_ADD_RELATIONS = set(('owned_by',))
-
-# add to this set relations which should have their add security checking done
-# *at COMMIT TIME* (done after by default)
-ON_COMMIT_ADD_RELATIONS = set(())
-
-# available sources registry
-SOURCE_TYPES = {'native': LazyObject('cubicweb.server.sources.native', 'NativeSQLSource'),
- 'datafeed': LazyObject('cubicweb.server.sources.datafeed', 'DataFeedSource'),
- 'ldapfeed': LazyObject('cubicweb.server.sources.ldapfeed', 'LDAPFeedSource'),
- }