gecos-team/gecoscc-ui

View on GitHub
gecoscc/__init__.py

Summary

Maintainability
B
5 hrs
Test Coverage
#
# Copyright 2013, Junta de Andalucia
# http://www.juntadeandalucia.es/
#
# Authors:
#   Antonio Perez-Aranda <ant30tx@gmail.com>
#   Pablo Martin <goinnn@gmail.com>
#
# All rights reserved - EUPL License V 1.1
# https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
#

import gevent.monkey
gevent.monkey.patch_all()

import json
import os
import pymongo
import sys
import subprocess
import jinja2
from multiprocessing import Process

from configparser import ConfigParser

from pyramid.authentication import SessionAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.config import Configurator
from pyramid.exceptions import ConfigurationError
from pyramid.threadlocal import get_current_registry

from gecoscc.db import MongoDB, get_db
from gecoscc.userdb import get_userdb, get_groups, get_user
from gecoscc.eventsmanager import get_jobstorage, ExpiredSessionEvent
from gecoscc.permissions import (is_logged, LoggedFactory, SuperUserFactory, SuperUserOrMyProfileFactory,
    InternalAccessFactory, RootFactory, ReadOnlyOrManageFactory, ManageFactory)
from gecoscc.utils import auditlog
from gecoscc.session import session_factory_from_settings

from urllib.parse import urlsplit
import colander

import socketio

import logging
logger = logging.getLogger(__name__)


def read_setting_from_env(settings, key, default=None):
    env_variable = key.upper()
    if env_variable in os.environ:
        return os.environ[env_variable]

    return settings.get(key, default)

def pregen(_request, elements, kw):
    kw.setdefault('rollback', '')
    return elements, kw

def include_file(name):
    if os.path.isfile(name):
        with open(name) as f:
            return jinja2.Markup(f.read())
    else:
        logger.warn('File not found: %s'%(name))
        return 'File not found: %s'%(name)

def route_config(config):
    config.add_static_view('static', 'static')
    config.add_static_view('deform_static', 'deform:static')
    config.add_route('home', '/', factory=LoggedFactory)
    config.add_route('updates', '/updates/', factory=SuperUserFactory)
    config.add_route('updates_add', '/updates/add/', factory=SuperUserFactory)
    config.add_route('updates_download', '/updates/download/{id}', factory=SuperUserFactory)
    config.add_route('updates_log', '/updates/log/{sequence}/{rollback:.*}', factory=SuperUserFactory, pregenerator=pregen)
    config.add_route('updates_tail', '/updates/tail/{sequence}/{rollback:.*}', factory=SuperUserFactory, pregenerator=pregen)
    config.add_route('updates_repeat', '/updates/repeat/{sequence}', factory=SuperUserFactory, pregenerator=pregen)
    
    config.add_route('admins', '/admins/', factory=SuperUserFactory)
    config.add_route('admins_add', '/admins/add/', factory=SuperUserFactory)
    config.add_route('admins_superuser', '/admins/superuser/{username}/', factory=SuperUserFactory)
    config.add_route('admins_ou_manage', '/admins/manage_ou/{username}/', factory=SuperUserFactory)

    config.add_route('admins_edit', '/admins/edit/{username}/', factory=SuperUserOrMyProfileFactory)
    config.add_route('admins_set_variables', '/admins/variables/{username}/', factory=SuperUserOrMyProfileFactory)
    config.add_route('admin_delete', '/admins/delete/', factory=SuperUserOrMyProfileFactory)
    config.add_route('admin_maintenance', '/admins/maintenance/', factory=SuperUserFactory)

    config.add_route('settings', '/settings/', factory=SuperUserFactory)
    config.add_route('settings_save', '/settings/save/', factory=SuperUserFactory)
    config.add_route('reports', '/reports/', factory=ReadOnlyOrManageFactory)
    config.add_route('report_file', '/report', factory=ReadOnlyOrManageFactory)
    config.add_route('computer_logs', '/computer/logs/{node_id}/{filename}', factory=LoggedFactory)
    config.add_route('download_computer_logs', '/download/computer/logs/{node_id}/{filename}', factory=LoggedFactory)
    config.add_route('delete_computer_logs', '/delete/computer/logs/{node_id}/{filename}', factory=ManageFactory)
    config.add_route('i18n_catalog', '/i18n-catalog/')
    config.add_route('login', '/login/')
    config.add_route('logout', 'logout/')
    config.add_route('forbidden-view', '/error403/')
    config.add_renderer('csv', 'gecoscc.views.reports.CSVRenderer')
    config.add_renderer('pdf', 'gecoscc.views.reports.PDFRenderer')
    config.add_renderer('txt', 'gecoscc.views.computer_logs.TXTRenderer')
    
    config.add_route('statistics', '/admins/statistics/', factory=ReadOnlyOrManageFactory)
    config.add_route('server_status', '/server/status', factory=SuperUserFactory)
    config.add_route('internal_server_status', '/internal/server/status', factory=InternalAccessFactory)
    config.add_route('server_connections', '/server/connections', factory=SuperUserFactory)
    config.add_route('internal_server_connections', '/internal/server/connections', factory=InternalAccessFactory)
    config.add_route('server_log', '/server/log', factory=SuperUserFactory)

    


def sockjs_config(config, global_config):
    settings = config.registry.settings

    sockjs_options = {}
    if 'sockjs_options' in settings:
        sockjs_options = json.loads(settings['sockjs_options'])

    parser = ConfigParser({'here': global_config['here']})
    parser.read(global_config['__file__'])
    settings['server:main:worker_class'] = parser.get('server:main', 'worker_class')

    mgr = socketio.KombuManager(settings['sockjs_url'],
        connection_options=sockjs_options)
    sio = socketio.Server(client_manager=mgr, async_mode='gevent')
    config.registry.settings['sio'] = sio



def route_config_auxiliary(config, route_prefix):
    config.add_route('sockjs_home', route_prefix)
    config.add_route('sockjs_message', '%smessage/' % route_prefix)


def database_config(config):
    settings = config.registry.settings
    mongo_uri = read_setting_from_env(settings, 'mongo_uri', None)
    if not mongo_uri:
        raise ConfigurationError("The mongo_uri option is required")

    settings['mongo_uri'] = mongo_uri

    mongo_replicaset = read_setting_from_env(settings, 'mongo_replicaset',
                                             None)
    settings['mongo_replicaset'] = mongo_replicaset

    if mongo_replicaset is not None:
        mongodb = MongoDB(settings['mongo_uri'],
                          replicaSet=mongo_replicaset)
    else:
        mongodb = MongoDB(settings['mongo_uri'])
    config.registry.settings['mongodb'] = mongodb

    config.add_request_method(get_db, 'db', reify=True)

def check_server_list(config):
    settings = config.registry.settings
    server_name = read_setting_from_env(settings, 'server_name', None)
    if not server_name:
        # Try to get the server name from "hostname" command
        p = subprocess.Popen('hostname', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        server_name, _ = p.communicate()

    if not server_name:
        raise ConfigurationError("server_name option required in gecoscc.ini")

    server_address = read_setting_from_env(settings, 'server_address', None)

    if not server_address:
        raise ConfigurationError("server_address option required in gecoscc.ini")
    
    db = config.registry.settings['mongodb'].get_database()
        
    # Create/update server is in the collection
    server = {'name': server_name.strip(), 'address':server_address.strip()}
    db.servers.update_one({'name': server_name.strip()}, {'$set': server},
                          upsert=True)
        
    
def userdb_config(config):
    # TODO
    # * Support LDAP Users
    # * Organization Users
    # * Mixed users
    from .userdb import MongoUserDB
    userdb = MongoUserDB(config.registry.settings['mongodb'], 'adminusers')
    config.registry.settings['userdb'] = userdb
    config.add_request_method(get_userdb, 'userdb', reify=True)
    config.add_request_method(get_user, 'user', reify=True)


def auth_config(config):
    authn_policy = SessionAuthenticationPolicy(callback=get_groups)
    authz_policy = ACLAuthorizationPolicy()
    config.set_authorization_policy(authz_policy)
    config.set_authentication_policy(authn_policy)

def env_override(value, key):
    return os.getenv(key, value)

def jinja2_config(config):
    settings = config.registry.settings
    settings.setdefault('jinja2.i18n.domain', 'gecoscc')
    settings.setdefault('jinja2.newstyle', True)

    settings.setdefault('jinja2.extensions', ['jinja2.ext.with_'])

    settings.setdefault('jinja2.directories', 'gecoscc:templates')
    settings.setdefault('jinja2.undefined', 'strict')
    settings.setdefault('jinja2.filters', """
        admin_jsonify = gecoscc.filters.admin_serialize
        datetime = gecoscc.filters.datetime
        regex_match = gecoscc.filters.regex_match
        timediff = gecoscc.filters.timediff
        route_url = pyramid_jinja2.filters:route_url_filter
        static_url = pyramid_jinja2.filters:static_url_filter
    """)


def test_celery_config(config):
    # Configure for the tests 
    from pyramid_celery import configure, celery_app
    configure(config, '%s/config-templates/test.ini'%(os.getcwd()))
    logger.info("Celery in eager mode: %s"%(celery_app.conf.task_always_eager))
    



def celery_config(config):
    if sys.argv[0].endswith('pserve'):
        # Configure Celery only in the pserve script (gecoscc process)
        from pyramid_celery import configure, celery_app
        configure(config, sys.argv[1])
        # logger.info("Celery configuration: %s"%(celery_app.conf))
        
    if sys.argv[0].endswith('setup.py'):
        test_celery_config(config)


def locale_config(config):
    settings = config.registry.settings
    settings['pyramid.locales'] = json.loads(settings['pyramid.locales'])
    needs_patching = False
    from gecoscc.models import Policy, Policies, Job, Jobs
    for locale in settings['pyramid.locales']:
        if locale == settings['pyramid.default_locale_name']:
            continue
        needs_patching = True
        
        getattr(Policy, '__all_schema_nodes__', []).append(colander.SchemaNode(colander.String(),
                                                                               name='name_%s' % locale,
                                                                               default='',
                                                                               missing=''))
        getattr(Job, '__all_schema_nodes__', []).append(colander.SchemaNode(colander.String(),
                                                                            name='policyname_%s' % locale,
                                                                            default='',
                                                                            missing=''))
    if needs_patching:
        Policies.policies = Policy(name='policies')
        Policies.__class_schema_nodes__ = [Policies.policies]
        Policies.__all_schema_nodes__ = [Policies.policies]

        Jobs.jobs = Job(name='jobs')
        Jobs.__class_schema_nodes__ = [Jobs.jobs]
        Jobs.__all_schema_nodes__ = [Jobs.jobs]


def main(global_config, **settings):
    """ This function returns a WSGI application.
    """
    settings = dict(settings)
    config = Configurator(root_factory=RootFactory, settings=settings,
                          autocommit=True)
    
    database_config(config)
    userdb_config(config)
    auth_config(config)
    celery_config(config)
    locale_config(config)

    session_factory = session_factory_from_settings(settings)
    config.set_session_factory(session_factory)

    # check_server_list must be in a separate process because it uses MongoDB
    # and MongoDB connections shouldn't be used before fork
    p = Process(target=check_server_list, args=(config, ))
    p.start()
    p.join()

    config.add_translation_dirs('gecoscc:locale/')

    jinja2_config(config)

    config.include('pyramid_jinja2')
    config.include('pyramid_celery')
    config.include('cornice')

    jinja2_env = config.get_jinja2_environment()
    jinja2_env.globals["include_file"] = include_file
    jinja2_env.filters['env_override'] = env_override
    
    def add_renderer_globals(event):
        current_settings = get_current_registry().settings
        event['help_base_url'] = current_settings['help_base_url']
        event['help_policy_url'] = current_settings['help_policy_url']

    def expire_session(event):
        auditlog(event.request, 'expire')

    config.add_subscriber(add_renderer_globals, 'pyramid.events.BeforeRender')

    config.add_subscriber('gecoscc.i18n.setAcceptedLanguagesLocale',
                          'pyramid.events.NewRequest')

    config.add_subscriber('gecoscc.i18n.add_localizer',
                          'pyramid.events.NewRequest')

    config.add_subscriber('gecoscc.context_processors.set_version',
                          'pyramid.events.NewRequest')

    config.add_subscriber(expire_session, 'gecoscc.eventsmanager.ExpiredSessionEvent')

    route_config(config)

    config.add_request_method(is_logged, 'is_logged', reify=True)
    config.add_request_method(get_jobstorage, 'jobs', reify=True)

    config.scan('gecoscc.views')
    config.scan('gecoscc.api')

    sockjs_config(config, global_config)
    
    # Create socketio middleware
    app = socketio.WSGIApp(config.registry.settings['sio'],
                           config.make_wsgi_app())
        

    return app