Basic functionalities
This commit is contained in:
parent
2bd63098a7
commit
9668dad8c8
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wcsinst.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
|
@ -0,0 +1,160 @@
|
|||
# Django settings for wcsinst project.
|
||||
|
||||
import os
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
PROJECT_PATH = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@example.com'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(PROJECT_PATH, 'wcsinst.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
# Hosts/domain names that are valid for this site; required if DEBUG is False
|
||||
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
# In a Windows environment this must be set to your system time zone.
|
||||
TIME_ZONE = 'Europe/Brussels'
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'fr-fr'
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
|
||||
# If you set this to False, Django will not format dates, numbers and
|
||||
# calendars according to the current locale.
|
||||
USE_L10N = True
|
||||
|
||||
# If you set this to False, Django will not use timezone-aware datetimes.
|
||||
USE_TZ = True
|
||||
|
||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||
# Example: "/var/www/example.com/media/"
|
||||
MEDIA_ROOT = os.path.join(PROJECT_PATH, 'media')
|
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||
# trailing slash.
|
||||
# Examples: "http://example.com/media/", "http://media.example.com/"
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
# Absolute path to the directory static files should be collected to.
|
||||
# Don't put anything in this directory yourself; store your static files
|
||||
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
||||
# Example: "/var/www/example.com/static/"
|
||||
STATIC_ROOT = os.path.join(PROJECT_PATH, 'static')
|
||||
|
||||
# URL prefix for static files.
|
||||
# Example: "http://example.com/static/", "http://static.example.com/"
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
# Additional locations of static files
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(PROJECT_PATH, 'wcsinst', 'static'),
|
||||
)
|
||||
|
||||
# List of finder classes that know how to find static files in
|
||||
# various locations.
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
|
||||
)
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
SECRET_KEY = 'sw-v(^psaet3)44flti-zr!=u64mzfeaodkey(m=&^nz(=43!o'
|
||||
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
# 'django.template.loaders.eggs.Loader',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
# Uncomment the next line for simple clickjacking protection:
|
||||
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'wcsinst.urls'
|
||||
|
||||
# Python dotted path to the WSGI application used by Django's runserver.
|
||||
WSGI_APPLICATION = 'wcsinst.wsgi.application'
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
os.path.join(PROJECT_PATH, 'wcsinst', 'templates'),
|
||||
)
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.admin',
|
||||
)
|
||||
|
||||
# A sample logging configuration. The only tangible logging
|
||||
# performed by this configuration is to send an email to
|
||||
# the site admins on every HTTP 500 error when DEBUG=False.
|
||||
# See http://docs.djangoproject.com/en/dev/topics/logging for
|
||||
# more details on how to customize your logging configuration.
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'require_debug_false': {
|
||||
'()': 'django.utils.log.RequireDebugFalse'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'filters': ['require_debug_false'],
|
||||
'class': 'django.utils.log.AdminEmailHandler'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
WCSINSTD_URL = os.environ.get('WCSINSTD_URL')
|
||||
|
||||
if WCSINSTD_URL:
|
||||
INSTALLED_APPS += ('wcsinst.wcsinst',)
|
||||
else:
|
||||
INSTALLED_APPS += ('wcsinst.wcsinstd',)
|
||||
|
||||
try:
|
||||
from local_settings import *
|
||||
except ImportError:
|
||||
pass
|
|
@ -0,0 +1,13 @@
|
|||
from django.conf.urls import patterns, include, url
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
|
||||
if 'wcsinst.wcsinstd' in settings.INSTALLED_APPS:
|
||||
urlpatterns += patterns('',
|
||||
(r'^wcsinstd/', include('wcsinst.wcsinstd.urls')))
|
|
@ -0,0 +1,8 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from models import WcsInstance
|
||||
|
||||
class WcsInstanceAdmin(admin.ModelAdmin):
|
||||
prepopulated_fields = {'domain': ('title',)}
|
||||
|
||||
admin.site.register(WcsInstance, WcsInstanceAdmin)
|
|
@ -0,0 +1,41 @@
|
|||
import json
|
||||
import logging
|
||||
import urllib2
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class WcsInstance(models.Model):
|
||||
title = models.CharField(max_length=50)
|
||||
domain = models.SlugField()
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s (%s)' % (self.title, self.domain)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
created = (self.id is None)
|
||||
super(WcsInstance, self).save(*args, **kwargs)
|
||||
# notify wcsinstd
|
||||
if not settings.WCSINSTD_URL:
|
||||
return
|
||||
if created:
|
||||
url = settings.WCSINSTD_URL + 'wcsinstd/create'
|
||||
else:
|
||||
url = settings.WCSINSTD_URL + 'wcsinstd/%s/' % self.domain
|
||||
request = urllib2.Request(url)
|
||||
request.add_header('Accept', 'application/json')
|
||||
request.add_header('Content-Type', 'application/json;charset=UTF-8')
|
||||
request.add_data(json.dumps({'title': self.title, 'domain': self.domain}))
|
||||
try:
|
||||
p = urllib2.urlopen(request)
|
||||
except urllib2.HTTPError as e:
|
||||
logger.error('wcsinstd HTTP error (%s)', str(e))
|
||||
print e.read()
|
||||
except urllib2.URLError as e:
|
||||
print e
|
||||
logger.error('wcsinstd URL error (%s)', str(e))
|
||||
else:
|
||||
out_data = p.read()
|
||||
p.close()
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
This file demonstrates writing tests using the unittest module. These will pass
|
||||
when you run "manage.py test".
|
||||
|
||||
Replace this with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.assertEqual(1 + 1, 2)
|
|
@ -0,0 +1 @@
|
|||
# Create your views here.
|
|
@ -0,0 +1,4 @@
|
|||
from django.conf import settings
|
||||
|
||||
URL_TEMPLATE = getattr(settings, 'WCSINST_URL_TEMPLATE', 'https://%(domain)s')
|
||||
WCS_APP_DIR = getattr(settings, 'WCSINST_WCS_APP_DIR', None)
|
|
@ -0,0 +1,169 @@
|
|||
import string
|
||||
import cPickle
|
||||
import os
|
||||
import zipfile
|
||||
from urlparse import urlparse
|
||||
|
||||
from cStringIO import StringIO
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from . import app_settings
|
||||
|
||||
|
||||
def get_provider_key(provider_id):
|
||||
return provider_id.replace('://', '-').replace('/', '-').replace('?', '-').replace(':', '-')
|
||||
|
||||
|
||||
class DeployInstance(object):
|
||||
skeleton = 'default'
|
||||
|
||||
skel_dir = None
|
||||
collectivity_install_dir = None
|
||||
|
||||
def __init__(self, domain, title):
|
||||
self.domain = domain
|
||||
self.title = title
|
||||
|
||||
def make(self):
|
||||
self.skel_dir = os.path.join(settings.MEDIA_ROOT, 'skeletons', self.skeleton)
|
||||
|
||||
url_template = app_settings.URL_TEMPLATE
|
||||
self.url = url_template % {'domain': self.domain}
|
||||
|
||||
host, path = urlparse(self.url)[1:3]
|
||||
if path.endswith('/'):
|
||||
path = path[:-1]
|
||||
|
||||
coldir = host
|
||||
if path:
|
||||
coldir += path.replace('/', '+')
|
||||
|
||||
self.collectivity_install_dir = os.path.join(app_settings.WCS_APP_DIR, coldir)
|
||||
|
||||
if os.path.exists(self.collectivity_install_dir):
|
||||
# site exists, let's update it
|
||||
pass
|
||||
anew = False
|
||||
else:
|
||||
anew = True
|
||||
os.mkdir(self.collectivity_install_dir)
|
||||
|
||||
z = zipfile.ZipFile(os.path.join(self.skel_dir, 'base.zip'), 'r')
|
||||
|
||||
for f in z.namelist():
|
||||
path = os.path.join(self.collectivity_install_dir, f)
|
||||
data = z.read(f)
|
||||
if not os.path.exists(os.path.dirname(path)):
|
||||
os.mkdir(os.path.dirname(path))
|
||||
open(path, 'w').write(data)
|
||||
z.close()
|
||||
|
||||
wcs_cfg = cPickle.load(file(os.path.join(self.collectivity_install_dir, 'config.pck')))
|
||||
# TODO: there are some settings to change in wcs_cfg
|
||||
# (like the name of the database)
|
||||
|
||||
self.make_sso_config(wcs_cfg)
|
||||
self.make_site_options()
|
||||
|
||||
cPickle.dump(wcs_cfg, file(os.path.join(self.collectivity_install_dir, 'config.pck'), 'w'))
|
||||
|
||||
self.make_apache_vhost()
|
||||
self.reload_apache()
|
||||
|
||||
|
||||
def make_sso_config(self, wcs_cfg):
|
||||
has_idff = False
|
||||
has_saml2 = False
|
||||
|
||||
service_provider_configuration = {}
|
||||
|
||||
if self.url.endswith('/'):
|
||||
url_stripped = self.url[:-1]
|
||||
else:
|
||||
url_stripped = self.url
|
||||
|
||||
if os.path.exists(os.path.join(self.skel_dir, 'idff-metadata-template')):
|
||||
# there's a ID-FF metadata template, so we do the ID-FF stuff
|
||||
has_idff = True
|
||||
service_provider_configuration.update({
|
||||
'base_url': '%s/liberty' % url_stripped,
|
||||
'metadata': 'metadata.xml',
|
||||
'providerid': '%s/liberty/metadata' % url_stripped,
|
||||
})
|
||||
|
||||
idff_metadata_template = file(
|
||||
os.path.join(self.skel_dir, 'idff-metadata-template')).read()
|
||||
file(os.path.join(self.collectivity_install_dir, 'metadata.xml'), 'w').write(
|
||||
string.Template(idff_metadata_template).substitute({'url': url_stripped}))
|
||||
|
||||
if os.path.exists(os.path.join(self.skel_dir, 'saml2-metadata-template')):
|
||||
# there's a SAMLv2 metadata template, so we do the SAMLv2 stuff
|
||||
has_saml2 = True
|
||||
service_provider_configuration.update({
|
||||
'saml2_base_url': '%s/saml' % url_stripped,
|
||||
'saml2_metadata': 'saml2-metadata.xml',
|
||||
'saml2_providerid': '%s/saml/metadata' % url_stripped
|
||||
})
|
||||
|
||||
saml2_metadata_template = file(
|
||||
os.path.join(self.skel_dir, 'saml2-metadata-template')).read()
|
||||
file(os.path.join(self.collectivity_install_dir, 'saml2-metadata.xml'), 'w').write(
|
||||
string.Template(saml2_metadata_template).substitute({'url': url_stripped}))
|
||||
|
||||
if has_idff or has_saml2:
|
||||
idp_metadata = ET.parse(file(os.path.join(self.skel_dir, 'idp-metadata.xml')))
|
||||
entity_id = idp_metadata.getroot().attrib['entityID']
|
||||
idp_key = get_provider_key(entity_id)
|
||||
|
||||
wcs_cfg['identification'] = {'methods': ['idp']}
|
||||
wcs_cfg['idp'] = {
|
||||
idp_key: {
|
||||
'metadata': 'provider-%s-metadata.xml' % idp_key,
|
||||
'metadata_url': entity_id,
|
||||
'publickey_url': None,
|
||||
'role': 2}}
|
||||
wcs_cfg['sp'] = {
|
||||
'common_domain': None,
|
||||
'common_domain_getter_url': None,
|
||||
'organization_name': '%s' % self.title,
|
||||
'privatekey': 'private-key.pem',
|
||||
'publickey': 'public-key.pem'}
|
||||
wcs_cfg['sp'].update(service_provider_configuration)
|
||||
|
||||
file(os.path.join(self.collectivity_install_dir, 'provider-%s-metadata.xml' % idp_key), 'w').write(
|
||||
file(os.path.join(self.skel_dir, 'idp-metadata.xml')).read())
|
||||
file(os.path.join(self.collectivity_install_dir, 'public-key.pem'), 'w').write(
|
||||
file(os.path.join(self.skel_dir, 'public-key.pem')).read())
|
||||
file(os.path.join(self.collectivity_install_dir, 'private-key.pem'), 'w').write(
|
||||
file(os.path.join(self.skel_dir, 'private-key.pem')).read())
|
||||
else:
|
||||
wcs_cfg['identification'] = {'methods': ['password']}
|
||||
|
||||
|
||||
def make_site_options(self):
|
||||
options_template_path = os.path.join(self.skel_dir, 'site-options.cfg')
|
||||
if not os.path.exists(options_template_path):
|
||||
return
|
||||
options_template = file(options_template_path).read()
|
||||
file(os.path.join(self.collectivity_install_dir, 'site-options.cfg'), 'w').write(
|
||||
string.Template(options_template).substitute({'domain': self.domain}))
|
||||
|
||||
|
||||
def make_apache_vhost(self):
|
||||
pass
|
||||
|
||||
|
||||
def reload_apache(self):
|
||||
fd = None
|
||||
try:
|
||||
fd = os.popen('sudo -S /etc/init.d/apache2 reload', 'w')
|
||||
print >> fd, 'plop' # this is not the password
|
||||
except Exception, e:
|
||||
pass
|
||||
if fd:
|
||||
try:
|
||||
fd.close()
|
||||
except IOError:
|
||||
pass
|
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
|
@ -0,0 +1,7 @@
|
|||
from django.conf.urls import patterns, url, include
|
||||
|
||||
urlpatterns = patterns('wcsinst.wcsinstd.views',
|
||||
url(r'^create$', 'create'),
|
||||
url(r'^(?P<instance>[\w-]+)/$', 'update'),
|
||||
)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import json
|
||||
import threading
|
||||
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from jsonresponse import to_json
|
||||
|
||||
from .deploy import DeployInstance
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@to_json('api')
|
||||
@require_POST
|
||||
def create(request):
|
||||
data = json.loads(request.body)
|
||||
deploy = DeployInstance(**data)
|
||||
threading.Thread(target=deploy.make).start()
|
||||
return {}
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@to_json('api')
|
||||
@require_POST
|
||||
def update(request, instance):
|
||||
print 'updating instance:', instance
|
||||
data = json.loads(request.body)
|
||||
if data.get('domain') != instance:
|
||||
raise Exception('domain mismatch') # -> should remove/add ?
|
||||
deploy = DeployInstance(**data)
|
||||
threading.Thread(target=deploy.make).start()
|
||||
return {}
|
|
@ -0,0 +1,32 @@
|
|||
"""
|
||||
WSGI config for wcsinst project.
|
||||
|
||||
This module contains the WSGI application used by Django's development server
|
||||
and any production WSGI deployments. It should expose a module-level variable
|
||||
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
|
||||
this application via the ``WSGI_APPLICATION`` setting.
|
||||
|
||||
Usually you will have the standard Django WSGI application here, but it also
|
||||
might make sense to replace the whole Django WSGI application with a custom one
|
||||
that later delegates to the Django one. For example, you could introduce WSGI
|
||||
middleware here, or combine a Django application with an application of another
|
||||
framework.
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
|
||||
# if running multiple sites in the same mod_wsgi process. To fix this, use
|
||||
# mod_wsgi daemon mode with each site in its own daemon process, or use
|
||||
# os.environ["DJANGO_SETTINGS_MODULE"] = "wcsinst.settings"
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wcsinst.settings")
|
||||
|
||||
# This application object is used by any WSGI server configured to use this
|
||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||
# setting points here.
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
application = get_wsgi_application()
|
||||
|
||||
# Apply WSGI middleware here.
|
||||
# from helloworld.wsgi import HelloWorldApplication
|
||||
# application = HelloWorldApplication(application)
|
Reference in New Issue