wcs/wcs/ctl/check_hobos.py

240 lines
8.8 KiB
Python
Raw Normal View History

# w.c.s. - web application for online forms
# Copyright (C) 2005-2014 Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
import ConfigParser
import json
import os
import subprocess
import sys
import tempfile
import urllib2
from qommon.ctl import Command
from qommon.storage import atomic_write
class NoChange(Exception):
pass
class CmdCheckHobos(Command):
name = 'check-hobos'
def execute(self, base_options, sub_options, args):
import publisher
self.base_options = base_options
publisher.WcsPublisher.configure(self.config, sub_options.extra)
pub = publisher.WcsPublisher.create_publisher()
global_app_dir = pub.app_dir
if not args or args[0] == '-':
# get environment definition from stdin
self.all_services = json.load(sys.stdin)
else:
self.all_services = json.load(file(args[0]))
services = [x for x in self.all_services.get('services', []) if \
x.get('service-id') == 'wcs']
# initialize all instances of w.c.s.
for service in services:
pub.app_dir = os.path.join(global_app_dir,
self.get_instance_path(service))
if not os.path.exists(pub.app_dir):
print 'initializing instance in', pub.app_dir
os.mkdir(pub.app_dir)
pub.initialize_app_dir()
skeleton_filepath = os.path.join(global_app_dir, 'skeletons',
service.get('template_name'))
if os.path.exists(skeleton_filepath):
pub.import_zip(file(skeleton_filepath))
new_site = True
else:
print 'updating instance in', pub.app_dir
pub.set_config()
new_site = False
try:
self.configure_site_options(service, pub)
except NoChange:
print ' skipping'
continue
self.update_configuration(service, pub)
self.configure_authentication_methods(service, pub)
if new_site:
self.configure_sql(service, pub)
def update_configuration(self, service, pub):
if not pub.cfg.get('misc'):
pub.cfg['misc'] = {}
pub.cfg['misc']['sitename'] = service.get('title').encode('utf-8')
pub.cfg['misc']['frontoffice-url'] = service.get('base_url').encode('utf-8')
pub.write_cfg()
def configure_authentication_methods(self, service, pub):
# look for an identity provider
idps = [x for x in self.all_services.get('services', []) if x.get('service-id') == 'authentic']
if not pub.cfg.get('identification'):
pub.cfg['identification'] = {}
methods = pub.cfg['identification'].get('methods', [])
if idps and not 'idp' in methods:
methods.append('idp')
elif not idps and not 'password' in methods:
methods.append('password')
pub.cfg['identification']['methods'] = methods
pub.write_cfg()
if not idps:
return
# initialize service provider side
if not pub.cfg.get('sp', {}).get('publickey'):
from qommon.ident.idp import MethodAdminDirectory
if not pub.cfg.get('sp'):
pub.cfg['sp'] = {}
spconfig = pub.cfg['sp']
spconfig['saml2_base_url'] = str(service.get('base_url')) + '/saml'
spconfig['saml2_providerid'] = spconfig['saml2_base_url'] + '/metadata'
MethodAdminDirectory().generate_rsa_keypair()
for idp in idps:
metadata_url = '%s/idp/saml2/metadata' % idp['base_url']
try:
rfd = urllib2.urlopen(metadata_url)
except (urllib2.HTTPError, urllib2.URLError), e:
print >> sys.stderr, 'failed to get metadata URL', metadata_url, e
continue
except Exception, e:
print >> sys.stderr, 'failed to get metadata URL', metadata_url, e
continue
s = rfd.read()
(bfd, metadata_pathname) = tempfile.mkstemp('.metadata')
atomic_write(metadata_pathname, s)
from qommon.ident.idp import AdminIDPDir
admin_dir = AdminIDPDir()
key_provider_id = admin_dir.submit_new_remote(
metadata_pathname, None, metadata_url, None)
pub.cfg['idp'][key_provider_id]['admin-attributes'] = \
{'role': 'admin::%s' % str(service.get('slug'))}
pub.write_cfg()
def get_instance_path(self, service):
parsed_url = urllib2.urlparse.urlsplit(service.get('base_url'))
instance_path = parsed_url.netloc
if parsed_url.path:
instance_path = '%s+' % parsed_url.path.replace('/', '+')
return instance_path
def configure_site_options(self, service, pub):
# configure site-options.cfg
config = ConfigParser.RawConfigParser()
site_options_filepath = os.path.join(pub.app_dir, 'site-options.cfg')
if os.path.exists(site_options_filepath):
config.read(site_options_filepath)
try:
if config.getint('hobo', 'timestamp') == self.all_services.get('timestamp'):
raise NoChange()
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
pass
if not 'hobo' in config.sections():
config.add_section('hobo')
config.set('hobo', 'timestamp', self.all_services.get('timestamp'))
if self.all_services.get('variables'):
if not 'variables' in config.sections():
config.add_section('variables')
for key, value in self.all_services.get('variables').items():
config.set('variables', key, value)
with open(site_options_filepath, 'wb') as site_options:
config.write(site_options)
def configure_sql(self, service, pub):
if not pub.cfg.get('postgresql'):
return
if not pub.has_site_option('postgresql'):
return
import psycopg2
import psycopg2.errorcodes
# determine database name using the instance path; if the template
# database name contained an underscore character, use the first part
# as a prefix to the database name
database_name = pub.cfg['postgresql'].get('database', 'wcs')
domain_table_name = self.get_instance_path(service).replace(
'-', '_').replace('.', '_').replace('+', '_')
if not domain_table_name in database_name:
database_name = '%s_%s' % (database_name.split('_')[0], domain_table_name)
postgresql_cfg = {}
for k, v in pub.cfg['postgresql'].items():
if v:
postgresql_cfg[k] = v
try:
pgconn = psycopg2.connect(**postgresql_cfg)
except psycopg2.Error:
print >> sys.stderr, 'failed to connect to postgresql (%s)' % \
psycopg2.errorcodes.lookup(e.pgcode)
return
pgconn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = pgconn.cursor()
new_database = True
try:
cur.execute('''CREATE DATABASE %s''' % database_name)
except psycopg2.Error as e:
if e.pgcode == psycopg2.errorcodes.DUPLICATE_DATABASE:
new_database = False
else:
print >> sys.stderr, 'failed to create database (%s)' % \
psycopg2.errorcodes.lookup(e.pgcode)
return
else:
cur.close()
pub.cfg['postgresql']['database'] = database_name
pub.write_cfg()
if not new_database:
return
cmd = [sys.argv[0]]
if self.base_options.configfile:
cmd.extend(['-f', self.base_options.configfile])
cmd.append('convert-to-sql')
for param in ('database', 'user', 'password', 'host', 'port'):
if postgresql_cfg.get(param):
if param == 'database':
cmd.append('--dbname')
else:
cmd.append('--' + param)
cmd.append(str(postgresql_cfg.get(param)))
cmd.append(str(self.get_instance_path(service)))
subprocess.call(cmd)
CmdCheckHobos.register()