Projet

Général

Profil

« Précédent | Suivant » 

Révision 19b45165

Ajouté par Jérôme Schneider il y a presque 10 ans

cleanning debian branch

Voir les différences:

README
1
wcsinst
2
=======
3

  
4
wcsinst is a web application to deploy w.c.s. instances; it is released under
5
the GNU AGPL Licence.
6

  
7

  
8
Architecture
9
------------
10

  
11
This project is composed of two Django applications; wcsinst provides a model
12
and is expected to be installed as part of an administrative portal; wcsinstd
13
provides the web API to deploy w.c.s. instances and should be installed on its
14
own, on the host instances should be deployed.
15

  
16

  
17
Configuration
18
-------------
19

  
20
If the WCSINSTD_URL environment variable is set, ./manage.py will run itself as
21
a standalone wcsinst (admin) site. Go to /admin/.
22

  
23
Example: WCSINSTD_URL=http://127.0.0.1:8001/ ./manage.py runserver
24

  
25
The wcsinstd application expects a WCSINST_WCS_APP_DIR setting, that should
26
point to the w.c.s. application directory (typically /var/lib/wcs/).
27

  
28
Additionally it expects site skeletons to be made available from the
29
media/skeletons directory; the default skeleton should go in a subdirectory
30
named "default".
31

  
32
A skeleton directory has the following files:
33

  
34
 - export.wcs: as produced from wcs, admin/export
35
 - idff-metadata-template: template file for ID-FF 1.2 metadata (optional)
36
 - saml2-metadata-template: template file for SAMLv2 metadata (optional)
37
 - private-key.pem & public-key.pem: pair of keys for ID-FF/SAML (optional)
38
 - idp-metadata.xml: metadata of the identity provider (optional)
39
 - site-options.cfg: template file for the w.c.s. site-options.cfg file
40
 - apache-vhost.conf: tempalte file for the Apache virtual host configuration
41

  
42
The template files contains template strings (cf the Python documentation);
43
there is a single variable made available, named domain (hence ${domain} to
44
use it).
45

  
46
The wcsinstd application should be able to run "sudo -n /etc/init.d/apache2
47
reload", to reload virtual hosts configuration. Virtual host configuration
48
files are created in $media/vhosts.d
debian/debian_settings.py
1
# Django settings for wcsinst project.
2

  
3
import os
4
import json
5
import logging.handlers
6
import wcsinst
7

  
8
DEBUG = 'DEBUG' in os.environ
9
TEMPLATE_DEBUG = DEBUG
10

  
11
VAR_DIR = '/var/lib/wcsinstd'
12
PROJECT_PATH = os.path.dirname(wcsinst.__file__)
13

  
14
if 'ADMINS' in os.environ:
15
    ADMINS = [ x.split(';') for x in os.environ['ADMINS'].split(',') ]
16
else:
17
    ADMINS = ( ('root', 'root@localhost') )
18

  
19
MANAGERS = ADMINS
20

  
21
DATABASES = {
22
    'default': {
23
        'ENGINE': 'django.db.backends.sqlite3',
24
        'NAME': os.path.join(VAR_DIR, 'wcsinst.sqlite3'),
25
    }
26
}
27

  
28
# Hosts/domain names that are valid for this site; required if DEBUG is False
29
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
30
if 'ALLOWED_HOSTS' in os.environ:
31
    ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split(',')
32
else:
33
    ALLOWED_HOSTS = [ '*' ]
34

  
35
# Local time zone for this installation. Choices can be found here:
36
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
37
# although not all choices may be available on all operating systems.
38
# In a Windows environment this must be set to your system time zone.
39
TIME_ZONE = 'Europe/Brussels'
40

  
41
# Language code for this installation. All choices can be found here:
42
# http://www.i18nguy.com/unicode/language-identifiers.html
43
LANGUAGE_CODE = 'fr-fr'
44

  
45
SITE_ID = 1
46

  
47
# If you set this to False, Django will make some optimizations so as not
48
# to load the internationalization machinery.
49
USE_I18N = True
50

  
51
# If you set this to False, Django will not format dates, numbers and
52
# calendars according to the current locale.
53
USE_L10N = True
54

  
55
# If you set this to False, Django will not use timezone-aware datetimes.
56
USE_TZ = True
57

  
58
# Absolute filesystem path to the directory that will hold user-uploaded files.
59
# Example: "/var/www/example.com/media/"
60
MEDIA_ROOT = os.path.join(VAR_DIR, 'media')
61

  
62
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
63
# trailing slash.
64
# Examples: "http://example.com/media/", "http://media.example.com/"
65
MEDIA_URL = '/media/'
66

  
67
# Absolute path to the directory static files should be collected to.
68
# Don't put anything in this directory yourself; store your static files
69
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
70
# Example: "/var/www/example.com/static/"
71
STATIC_ROOT = os.path.join(VAR_DIR, 'static')
72

  
73
# URL prefix for static files.
74
# Example: "http://example.com/static/", "http://static.example.com/"
75
STATIC_URL = '/static/'
76

  
77
# Additional locations of static files
78
STATICFILES_DIRS = (
79
    os.path.join(VAR_DIR, 'extra-static'),
80
)
81

  
82
# List of finder classes that know how to find static files in
83
# various locations.
84
STATICFILES_FINDERS = (
85
    'django.contrib.staticfiles.finders.FileSystemFinder',
86
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
87
#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
88
)
89

  
90
# Make this unique, and don't share it with anybody.
91
SECRET_KEY = os.environ.get('SECRET_KEY') or file('/etc/wcsinstd/secret').read()
92

  
93
# List of callables that know how to import templates from various sources.
94
TEMPLATE_LOADERS = (
95
    'django.template.loaders.filesystem.Loader',
96
    'django.template.loaders.app_directories.Loader',
97
#     'django.template.loaders.eggs.Loader',
98
)
99

  
100
MIDDLEWARE_CLASSES = (
101
    'django.middleware.common.CommonMiddleware',
102
    'django.contrib.sessions.middleware.SessionMiddleware',
103
    'django.middleware.csrf.CsrfViewMiddleware',
104
    'django.contrib.auth.middleware.AuthenticationMiddleware',
105
    'django.contrib.messages.middleware.MessageMiddleware',
106
    # Uncomment the next line for simple clickjacking protection:
107
    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
108
)
109

  
110
ROOT_URLCONF = 'wcsinst.urls'
111

  
112
# Python dotted path to the WSGI application used by Django's runserver.
113
WSGI_APPLICATION = 'wcsinst.wsgi.application'
114

  
115
TEMPLATE_DIRS = (
116
    os.path.join(PROJECT_PATH, 'wcsinst', 'templates'),
117
    os.path.join(VAR_DIR, 'templates'),
118
)
119

  
120
INSTALLED_APPS = (
121
    'south',
122
    'django.contrib.auth',
123
    'django.contrib.contenttypes',
124
    'django.contrib.sessions',
125
    'django.contrib.sites',
126
    'django.contrib.messages',
127
    'django.contrib.staticfiles',
128
    'django.contrib.admin',
129
)
130

  
131
# A sample logging configuration. The only tangible logging
132
# performed by this configuration is to send an email to
133
# the site admins on every HTTP 500 error when DEBUG=False.
134
# See http://docs.djangoproject.com/en/dev/topics/logging for
135
# more details on how to customize your logging configuration.
136
LOGGING = {
137
    'version': 1,
138
    'disable_existing_loggers': True,
139
    'formatters': {
140
        'syslog': {
141
            'format': 'wcsinstd(pid=%(process)d) %(levelname)s %(name)s: %(message)s',
142
        },
143
    },
144
    'handlers': {
145
        'mail_admins': {
146
            'level': 'ERROR',
147
            'filters': [],
148
            'class': 'django.utils.log.AdminEmailHandler'
149
        },
150
        'syslog': {
151
            'level': 'DEBUG',
152
            'class': 'entrouvert.logging.handlers.SysLogHandler',
153
            'formatter': 'syslog',
154
            'facility': logging.handlers.SysLogHandler.LOG_LOCAL0,
155
            'address': '/dev/log',
156
            'max_length': 999,
157
        },
158
    },
159
    'loggers': {
160
        '': {
161
            'handlers': ['mail_admins','syslog'],
162
            'level': 'DEBUG' if DEBUG else 'INFO',
163
            'propagate': True,
164
        },
165
    }
166
}
167

  
168
INSTALLED_APPS += ('wcsinst.wcsinstd',)
169

  
170
if 'SENTRY_DSN' in os.environ:
171
    INSTALLED_APPS += ('raven.contrib.django.raven_compat',)
172
    RAVEN_CONFIG = {
173
        'dsn': os.environ.get('SENTRY_DSN'),
174
    }
175

  
176
for var in ('WCSINST_WCSCTL_SCRIPT', 'WCSINST_URL_TEMPLATE', 'WCSINST_WCS_APP_DIR'):
177
    if var in os.environ:
178
        globals()[var] = os.environ[var]
debian/debian_wsgi.py
1
"""
2
WSGI config for wcsinst project.
3

  
4
This module contains the WSGI application used by Django's development server
5
and any production WSGI deployments. It should expose a module-level variable
6
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7
this application via the ``WSGI_APPLICATION`` setting.
8

  
9
Usually you will have the standard Django WSGI application here, but it also
10
might make sense to replace the whole Django WSGI application with a custom one
11
that later delegates to the Django one. For example, you could introduce WSGI
12
middleware here, or combine a Django application with an application of another
13
framework.
14

  
15
"""
16
import os
17

  
18
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
19
# if running multiple sites in the same mod_wsgi process. To fix this, use
20
# mod_wsgi daemon mode with each site in its own daemon process, or use
21
# os.environ["DJANGO_SETTINGS_MODULE"] = "wcsinst.settings"
22
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "debian_settings")
23

  
24
# This application object is used by any WSGI server configured to use this
25
# file. This includes Django's development server, if the WSGI_APPLICATION
26
# setting points here.
27
from django.core.wsgi import get_wsgi_application
28
application = get_wsgi_application()
29

  
30
# Apply WSGI middleware here.
31
# from helloworld.wsgi import HelloWorldApplication
32
# application = HelloWorldApplication(application)
debian/wcsinstd.init
31 31
--workers=1 \
32 32
--worker-class=sync \
33 33
--timeout=60 \
34
debian_wsgi:application"
34
wcsinst.wsgi:application"
35 35

  
36 36
export PYTHONPATH=/usr/lib/$NAME
37 37

  
jenkins.sh
1
#!/bin/sh
2
pip install --upgrade pip
3
pip install --upgrade pylint
4
pip install --upgrade -v -r requirements.txt
5
echo Nothing to test for now
6
(pylint -f parseable --rcfile /var/lib/jenkins/pylint.django.rc wcsinst/ | tee pylint.out) || /bin/true
manage.py
1
#!/usr/bin/env python
2
import os
3
import sys
4

  
5
if __name__ == "__main__":
6
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wcsinst.settings")
7

  
8
    from django.core.management import execute_from_command_line
9

  
10
    execute_from_command_line(sys.argv)
requirements.txt
1
django < 1.6
2
south>=0.8,<0.9
3
http://pypi.python.org/packages/source/d/django-jsonresponse/django-jsonresponse-0.5.tar.gz
setup.py
1
#! /usr/bin/python
2

  
3
from setuptools import setup, find_packages
4
import os
5
import subprocess
6

  
7
VERSION='0.1'
8

  
9
def get_version():
10
    if os.path.exists('.git'):
11
        p = subprocess.Popen(['git','describe','--dirty'], stdout=subprocess.PIPE)
12
        result = p.communicate()[0]
13
        return result.split()[0].replace('-','.')
14
    return VERSION
15

  
16
setup(name='wcsinst',
17
        version=get_version(),
18
        license='AGPLv3',
19
        description='',
20
        url='https://dev.entrouvert.org/projects/wcsinst/',
21
        download_url='http://repos.entrouvert.org/wcsinst.git/',
22
        author="Entr'ouvert",
23
        author_email="info@entrouvert.com",
24
        packages=find_packages(os.path.dirname(__file__) or '.'),
25
        scripts=['manage.py'],
26
        include_package_data = True,
27
        install_requires=[
28
            'django >= 1.5.1, < 1.6',
29
        ],
30
        dependency_links = [
31
            'http://pypi.python.org/packages/source/d/django-jsonresponse/django-jsonresponse-0.5.tar.gz',
32
        ],
33
)
wcsinst/settings.py
1
# Django settings for wcsinst project.
2

  
3
import os
4

  
5
DEBUG = True
6
TEMPLATE_DEBUG = DEBUG
7

  
8
PROJECT_PATH = os.path.dirname(os.path.dirname(__file__))
9

  
10
ADMINS = (
11
    # ('Your Name', 'your_email@example.com'),
12
)
13

  
14
MANAGERS = ADMINS
15

  
16
DATABASES = {
17
    'default': {
18
        'ENGINE': 'django.db.backends.sqlite3',
19
        'NAME': os.path.join(PROJECT_PATH, 'wcsinst.sqlite3'),
20
    }
21
}
22

  
23
# Hosts/domain names that are valid for this site; required if DEBUG is False
24
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
25
ALLOWED_HOSTS = []
26

  
27
# Local time zone for this installation. Choices can be found here:
28
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
29
# although not all choices may be available on all operating systems.
30
# In a Windows environment this must be set to your system time zone.
31
TIME_ZONE = 'Europe/Brussels'
32

  
33
# Language code for this installation. All choices can be found here:
34
# http://www.i18nguy.com/unicode/language-identifiers.html
35
LANGUAGE_CODE = 'fr-fr'
36

  
37
SITE_ID = 1
38

  
39
# If you set this to False, Django will make some optimizations so as not
40
# to load the internationalization machinery.
41
USE_I18N = True
42

  
43
# If you set this to False, Django will not format dates, numbers and
44
# calendars according to the current locale.
45
USE_L10N = True
46

  
47
# If you set this to False, Django will not use timezone-aware datetimes.
48
USE_TZ = True
49

  
50
# Absolute filesystem path to the directory that will hold user-uploaded files.
51
# Example: "/var/www/example.com/media/"
52
MEDIA_ROOT = os.path.join(PROJECT_PATH, 'media')
53

  
54
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
55
# trailing slash.
56
# Examples: "http://example.com/media/", "http://media.example.com/"
57
MEDIA_URL = '/media/'
58

  
59
# Absolute path to the directory static files should be collected to.
60
# Don't put anything in this directory yourself; store your static files
61
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
62
# Example: "/var/www/example.com/static/"
63
STATIC_ROOT = os.path.join(PROJECT_PATH, 'static')
64

  
65
# URL prefix for static files.
66
# Example: "http://example.com/static/", "http://static.example.com/"
67
STATIC_URL = '/static/'
68

  
69
# Additional locations of static files
70
STATICFILES_DIRS = (
71
    os.path.join(PROJECT_PATH, 'wcsinst', 'static'),
72
)
73

  
74
# List of finder classes that know how to find static files in
75
# various locations.
76
STATICFILES_FINDERS = (
77
    'django.contrib.staticfiles.finders.FileSystemFinder',
78
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
79
#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
80
)
81

  
82
# Make this unique, and don't share it with anybody.
83
SECRET_KEY = 'sw-v(^psaet3)44flti-zr!=u64mzfeaodkey(m=&^nz(=43!o'
84

  
85
# List of callables that know how to import templates from various sources.
86
TEMPLATE_LOADERS = (
87
    'django.template.loaders.filesystem.Loader',
88
    'django.template.loaders.app_directories.Loader',
89
#     'django.template.loaders.eggs.Loader',
90
)
91

  
92
MIDDLEWARE_CLASSES = (
93
    'django.middleware.common.CommonMiddleware',
94
    'django.contrib.sessions.middleware.SessionMiddleware',
95
    'django.middleware.csrf.CsrfViewMiddleware',
96
    'django.contrib.auth.middleware.AuthenticationMiddleware',
97
    'django.contrib.messages.middleware.MessageMiddleware',
98
    # Uncomment the next line for simple clickjacking protection:
99
    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
100
)
101

  
102
ROOT_URLCONF = 'wcsinst.urls'
103

  
104
# Python dotted path to the WSGI application used by Django's runserver.
105
WSGI_APPLICATION = 'wcsinst.wsgi.application'
106

  
107
TEMPLATE_DIRS = (
108
    os.path.join(PROJECT_PATH, 'wcsinst', 'templates'),
109
)
110

  
111
INSTALLED_APPS = (
112
    'south',
113
    'django.contrib.auth',
114
    'django.contrib.contenttypes',
115
    'django.contrib.sessions',
116
    'django.contrib.sites',
117
    'django.contrib.messages',
118
    'django.contrib.staticfiles',
119
    'django.contrib.admin',
120
)
121

  
122
# A sample logging configuration. The only tangible logging
123
# performed by this configuration is to send an email to
124
# the site admins on every HTTP 500 error when DEBUG=False.
125
# See http://docs.djangoproject.com/en/dev/topics/logging for
126
# more details on how to customize your logging configuration.
127
LOGGING = {
128
    'version': 1,
129
    'disable_existing_loggers': False,
130
    'filters': {
131
        'require_debug_false': {
132
            '()': 'django.utils.log.RequireDebugFalse'
133
        }
134
    },
135
    'handlers': {
136
        'mail_admins': {
137
            'level': 'ERROR',
138
            'filters': ['require_debug_false'],
139
            'class': 'django.utils.log.AdminEmailHandler'
140
        }
141
    },
142
    'loggers': {
143
        'django.request': {
144
            'handlers': ['mail_admins'],
145
            'level': 'ERROR',
146
            'propagate': True,
147
        },
148
    }
149
}
150

  
151
WCSINSTD_URL = os.environ.get('WCSINSTD_URL')
152

  
153
if WCSINSTD_URL:
154
    INSTALLED_APPS += ('wcsinst.wcsinst',)
155
else:
156
    INSTALLED_APPS += ('wcsinst.wcsinstd',)
157

  
158
WCSINST_WCSCTL_SCRIPT = os.environ.get('WCSINST_WCSCTL_SCRIPT', 'wcsctl')
159

  
160
try:
161
    from local_settings import *
162
except ImportError:
163
    pass
wcsinst/urls.py
1
from django.conf.urls import patterns, include, url
2
from django.conf import settings
3

  
4
from django.contrib import admin
5
admin.autodiscover()
6

  
7
urlpatterns = patterns('',
8
    url(r'^admin/', include(admin.site.urls)),
9
)
10

  
11
if 'wcsinst.wcsinstd' in settings.INSTALLED_APPS:
12
    urlpatterns += patterns('',
13
            (r'^wcsinstd/', include('wcsinst.wcsinstd.urls')))
wcsinst/wcsinst/admin.py
1
from django.contrib import admin
2
from django.utils.translation import ugettext_lazy as _
3

  
4
from models import WcsInstance, ApiSecret, Variable
5

  
6
class ApiSecretsInline(admin.TabularInline):
7
    model = ApiSecret
8
    verbose_name = _('API secret')
9
    verbose_name_plural = _('API secrets')
10

  
11
class VariablesInline(admin.TabularInline):
12
    model = Variable
13
    verbose_name = _('variable')
14
    verbose_name_plural = _('variables')
15

  
16
class WcsInstanceAdmin(admin.ModelAdmin):
17
    prepopulated_fields = {'domain': ('title',)}
18
    fieldsets = (
19
        (None, {'fields': ('title', 'domain'),}),
20
        ('site-options.cfg',
21
            {'fields': ('postgresql', ('saml2_use_role', 'saml2_role_prefix',), 'backoffice_feed_url' )}
22
        ),
23
        ('site-options.cfg au-quotidien',
24
            {'fields': ('drupal', 'ezldap', 'strongbox', 'clicrdv', 'domino' )}
25
        ),
26
    )
27
    inlines = [VariablesInline, ApiSecretsInline]
28
    save_as = True
29

  
30

  
31
admin.site.register(WcsInstance, WcsInstanceAdmin)
wcsinst/wcsinst/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
import datetime
3
from south.db import db
4
from south.v2 import SchemaMigration
5
from django.db import models
6

  
7

  
8
class Migration(SchemaMigration):
9

  
10
    def forwards(self, orm):
11
        # Adding model 'WcsInstance'
12
        db.create_table(u'wcsinst_wcsinstance', (
13
            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14
            ('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
15
            ('domain', self.gf('django.db.models.fields.CharField')(max_length=100)),
16
        ))
17
        db.send_create_signal(u'wcsinst', ['WcsInstance'])
18

  
19

  
20
    def backwards(self, orm):
21
        # Deleting model 'WcsInstance'
22
        db.delete_table(u'wcsinst_wcsinstance')
23

  
24

  
25
    models = {
26
        u'wcsinst.wcsinstance': {
27
            'Meta': {'object_name': 'WcsInstance'},
28
            'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
29
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
30
            'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
31
        }
32
    }
33

  
34
    complete_apps = ['wcsinst']
wcsinst/wcsinst/migrations/0002_auto__add_variable__add_apisecret__add_field_wcsinstance_postgresql__a.py
1
# -*- coding: utf-8 -*-
2
import datetime
3
from south.db import db
4
from south.v2 import SchemaMigration
5
from django.db import models
6

  
7

  
8
class Migration(SchemaMigration):
9

  
10
    def forwards(self, orm):
11
        # Adding model 'Variable'
12
        db.create_table(u'wcsinst_variable', (
13
            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14
            ('key', self.gf('django.db.models.fields.CharField')(max_length=128)),
15
            ('value', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)),
16
            ('wcs_instance', self.gf('django.db.models.fields.related.ForeignKey')(related_name='variables', to=orm['wcsinst.WcsInstance'])),
17
        ))
18
        db.send_create_signal(u'wcsinst', ['Variable'])
19

  
20
        # Adding model 'ApiSecret'
21
        db.create_table(u'wcsinst_apisecret', (
22
            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
23
            ('key', self.gf('django.db.models.fields.CharField')(max_length=128)),
24
            ('value', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)),
25
            ('wcs_instance', self.gf('django.db.models.fields.related.ForeignKey')(related_name='api_secrets', to=orm['wcsinst.WcsInstance'])),
26
        ))
27
        db.send_create_signal(u'wcsinst', ['ApiSecret'])
28

  
29
        # Adding field 'WcsInstance.postgresql'
30
        db.add_column(u'wcsinst_wcsinstance', 'postgresql',
31
                      self.gf('django.db.models.fields.BooleanField')(default=False),
32
                      keep_default=False)
33

  
34
        # Adding field 'WcsInstance.saml2_use_role'
35
        db.add_column(u'wcsinst_wcsinstance', 'saml2_use_role',
36
                      self.gf('django.db.models.fields.BooleanField')(default=False),
37
                      keep_default=False)
38

  
39
        # Adding field 'WcsInstance.saml2_role_prefix'
40
        db.add_column(u'wcsinst_wcsinstance', 'saml2_role_prefix',
41
                      self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True),
42
                      keep_default=False)
43

  
44
        # Adding field 'WcsInstance.backoffice_feed_url'
45
        db.add_column(u'wcsinst_wcsinstance', 'backoffice_feed_url',
46
                      self.gf('django.db.models.fields.URLField')(default='', max_length=128, blank=True),
47
                      keep_default=False)
48

  
49
        # Adding field 'WcsInstance.drupal'
50
        db.add_column(u'wcsinst_wcsinstance', 'drupal',
51
                      self.gf('django.db.models.fields.BooleanField')(default=False),
52
                      keep_default=False)
53

  
54
        # Adding field 'WcsInstance.ezldap'
55
        db.add_column(u'wcsinst_wcsinstance', 'ezldap',
56
                      self.gf('django.db.models.fields.BooleanField')(default=False),
57
                      keep_default=False)
58

  
59
        # Adding field 'WcsInstance.strongbox'
60
        db.add_column(u'wcsinst_wcsinstance', 'strongbox',
61
                      self.gf('django.db.models.fields.BooleanField')(default=False),
62
                      keep_default=False)
63

  
64
        # Adding field 'WcsInstance.clicrdv'
65
        db.add_column(u'wcsinst_wcsinstance', 'clicrdv',
66
                      self.gf('django.db.models.fields.BooleanField')(default=False),
67
                      keep_default=False)
68

  
69
        # Adding field 'WcsInstance.domino'
70
        db.add_column(u'wcsinst_wcsinstance', 'domino',
71
                      self.gf('django.db.models.fields.BooleanField')(default=False),
72
                      keep_default=False)
73

  
74

  
75
    def backwards(self, orm):
76
        # Deleting model 'Variable'
77
        db.delete_table(u'wcsinst_variable')
78

  
79
        # Deleting model 'ApiSecret'
80
        db.delete_table(u'wcsinst_apisecret')
81

  
82
        # Deleting field 'WcsInstance.postgresql'
83
        db.delete_column(u'wcsinst_wcsinstance', 'postgresql')
84

  
85
        # Deleting field 'WcsInstance.saml2_use_role'
86
        db.delete_column(u'wcsinst_wcsinstance', 'saml2_use_role')
87

  
88
        # Deleting field 'WcsInstance.saml2_role_prefix'
89
        db.delete_column(u'wcsinst_wcsinstance', 'saml2_role_prefix')
90

  
91
        # Deleting field 'WcsInstance.backoffice_feed_url'
92
        db.delete_column(u'wcsinst_wcsinstance', 'backoffice_feed_url')
93

  
94
        # Deleting field 'WcsInstance.drupal'
95
        db.delete_column(u'wcsinst_wcsinstance', 'drupal')
96

  
97
        # Deleting field 'WcsInstance.ezldap'
98
        db.delete_column(u'wcsinst_wcsinstance', 'ezldap')
99

  
100
        # Deleting field 'WcsInstance.strongbox'
101
        db.delete_column(u'wcsinst_wcsinstance', 'strongbox')
102

  
103
        # Deleting field 'WcsInstance.clicrdv'
104
        db.delete_column(u'wcsinst_wcsinstance', 'clicrdv')
105

  
106
        # Deleting field 'WcsInstance.domino'
107
        db.delete_column(u'wcsinst_wcsinstance', 'domino')
108

  
109

  
110
    models = {
111
        u'wcsinst.apisecret': {
112
            'Meta': {'object_name': 'ApiSecret'},
113
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114
            'key': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
115
            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
116
            'wcs_instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'api_secrets'", 'to': u"orm['wcsinst.WcsInstance']"})
117
        },
118
        u'wcsinst.variable': {
119
            'Meta': {'object_name': 'Variable'},
120
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
121
            'key': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
122
            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
123
            'wcs_instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variables'", 'to': u"orm['wcsinst.WcsInstance']"})
124
        },
125
        u'wcsinst.wcsinstance': {
126
            'Meta': {'object_name': 'WcsInstance'},
127
            'backoffice_feed_url': ('django.db.models.fields.URLField', [], {'max_length': '128', 'blank': 'True'}),
128
            'clicrdv': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
129
            'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
130
            'domino': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
131
            'drupal': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
132
            'ezldap': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
133
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
134
            'postgresql': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
135
            'saml2_role_prefix': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
136
            'saml2_use_role': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
137
            'strongbox': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
138
            'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
139
        }
140
    }
141

  
142
    complete_apps = ['wcsinst']
wcsinst/wcsinst/models.py
1
import json
2
import logging
3
import urllib2
4

  
5
from django.conf import settings
6
from django.db import models
7
from django.utils.translation import ugettext_lazy as _
8

  
9
logger = logging.getLogger(__name__)
10

  
11
class WcsInstance(models.Model):
12
    title = models.CharField(max_length=50)
13
    domain = models.CharField(max_length=100)
14

  
15
    # site-options.cfg options
16
    postgresql = models.BooleanField(verbose_name=_('postgresql'),
17
            blank=True)
18
    saml2_use_role = models.BooleanField(verbose_name=_('use saml2 roles'),
19
            blank=True)
20
    saml2_role_prefix = models.CharField(verbose_name=_('role prefix'),
21
            blank=True, max_length=128)
22
    backoffice_feed_url = models.URLField(verbose_name=_('backoffice feed url'),
23
            blank=True, max_length=128)
24
    drupal = models.BooleanField(verbose_name=_('drupal'),
25
            blank=True)
26
    ezldap = models.BooleanField(verbose_name=_('ezldap'),
27
            blank=True)
28
    strongbox = models.BooleanField(verbose_name=_('strongbox'),
29
            blank=True)
30
    clicrdv = models.BooleanField(verbose_name=_('clicrdv'),
31
            blank=True)
32
    domino = models.BooleanField(verbose_name=_('domino'),
33
            blank=True)
34

  
35

  
36
    def __unicode__(self):
37
        return '%s (%s)' % (self.title, self.domain)
38

  
39
    def site_options_cfg(self):
40
        d = {
41
                'postgresql': self.postgresql,
42
                'saml2_use_role': self.saml2_use_role,
43
                'saml2_role_prefix': self.saml2_role_prefix,
44
                'backoffice_feed_url': self.backoffice_feed_url,
45
                'drupal': self.drupal,
46
                'ezldap': self.ezldap,
47
                'strongbox': self.strongbox,
48
                'clicrdv': self.clicrdv,
49
                'domino': self.domino,
50
        }
51
        d['api_secrets'] = dict((kv.key, kv.value) for kv in self.api_secrets.all())
52
        d['variables'] = dict((kv.key, kv.value) for kv in self.variables.all())
53
        return d
54

  
55
    def to_json(self):
56
        return {
57
                'title': self.title,
58
                'domain': self.domain,
59
                'site_options_cfg': self.site_options_cfg(),
60
        }
61

  
62
    def save(self, *args, **kwargs):
63
        created = (self.id is None)
64
        super(WcsInstance, self).save(*args, **kwargs)
65
        # notify wcsinstd
66
        if not settings.WCSINSTD_URL:
67
            return
68
        if created:
69
            url = settings.WCSINSTD_URL + 'wcsinstd/create'
70
        else:
71
            url = settings.WCSINSTD_URL + 'wcsinstd/%s/' % self.domain
72
        post_data = json.dumps(self.to_json())
73
        request = urllib2.Request(url)
74
        request.add_header('Accept', 'application/json')
75
        request.add_header('Content-Type', 'application/json;charset=UTF-8')
76
        request.add_data(post_data)
77
        try:
78
            p = urllib2.urlopen(request)
79
        except urllib2.HTTPError as e:
80
            logger.error('wcsinstd HTTP error (%s)', str(e))
81
            print e.read()
82
        except urllib2.URLError as e:
83
            print e
84
            logger.error('wcsinstd URL error (%s)', str(e))
85
        else:
86
            out_data = p.read()
87
            p.close()
88

  
89
    class Meta:
90
        verbose_name = _('wcs instance')
91
        verbose_name_plural = _('wcs instances')
92

  
93

  
94
class KeyValue(models.Model):
95
    key = models.CharField(max_length=128)
96
    value = models.CharField(max_length=1024, blank=True)
97

  
98
    class Meta:
99
        abstract = True
100

  
101

  
102
class Variable(KeyValue):
103
    wcs_instance = models.ForeignKey(WcsInstance, related_name='variables')
104

  
105
    class Meta:
106
        verbose_name = _('variable')
107
        verbose_name_plural = _('variables')
108

  
109

  
110
class ApiSecret(KeyValue):
111
    wcs_instance = models.ForeignKey(WcsInstance, related_name='api_secrets')
112

  
113
    class Meta:
114
        verbose_name = _('api secret')
115
        verbose_name_plural = _('api secrets')
116

  
117

  
wcsinst/wcsinst/tests.py
1
"""
2
This file demonstrates writing tests using the unittest module. These will pass
3
when you run "manage.py test".
4

  
5
Replace this with more appropriate tests for your application.
6
"""
7

  
8
from django.test import TestCase
9

  
10

  
11
class SimpleTest(TestCase):
12
    def test_basic_addition(self):
13
        """
14
        Tests that 1 + 1 always equals 2.
15
        """
16
        self.assertEqual(1 + 1, 2)
wcsinst/wcsinst/views.py
1
# Create your views here.
wcsinst/wcsinstd/app_settings.py
1
from django.conf import settings
2

  
3
URL_TEMPLATE = getattr(settings, 'WCSINST_URL_TEMPLATE', 'https://%(domain)s')
4
WCS_APP_DIR = getattr(settings, 'WCSINST_WCS_APP_DIR', None)
5
WCSCTL_SCRIPT = getattr(settings, 'WCSINST_WCSCTL_SCRIPT', 'wcsctl')
wcsinst/wcsinstd/deploy.py
1
import string
2
import cPickle
3
import os
4
import zipfile
5
import subprocess
6
from urlparse import urlparse
7

  
8
from cStringIO import StringIO
9
import xml.etree.ElementTree as ET
10

  
11
from django.conf import settings
12

  
13
import psycopg2
14

  
15
from . import app_settings
16

  
17

  
18
def get_provider_key(provider_id):
19
    return provider_id.replace('://', '-').replace('/', '-').replace('?', '-').replace(':', '-')
20

  
21

  
22
class DeployInstance(object):
23
    skeleton = 'default'
24

  
25
    skel_dir = None
26
    collectivity_install_dir = None
27

  
28
    def __init__(self, domain, title, site_options_cfg):
29
        self.domain = domain
30
        self.title = title
31
        self.site_options_cfg = site_options_cfg
32

  
33
    def make(self):
34
        self.skel_dir = os.path.join(settings.MEDIA_ROOT, 'skeletons', self.skeleton)
35

  
36
        url_template = app_settings.URL_TEMPLATE
37
        self.url = str(url_template % {'domain': self.domain})
38

  
39
        host, path = urlparse(self.url)[1:3]
40
        if path.endswith('/'):
41
            path = path[:-1]
42

  
43
        coldir = host
44
        if path:
45
            coldir += path.replace('/', '+')
46

  
47
        self.collectivity_install_dir = os.path.join(app_settings.WCS_APP_DIR, coldir)
48

  
49
        if os.path.exists(self.collectivity_install_dir):
50
            # site exists, let's update it
51
            pass
52
            anew = False
53
        else:
54
            anew = True
55
            os.mkdir(self.collectivity_install_dir)
56

  
57
        z = zipfile.ZipFile(os.path.join(self.skel_dir, 'export.wcs'), 'r')
58

  
59
        for f in z.namelist():
60
            path = os.path.join(self.collectivity_install_dir, f)
61
            data = z.read(f)
62
            if not os.path.exists(os.path.dirname(path)):
63
                os.mkdir(os.path.dirname(path))
64
            open(path, 'w').write(data)
65
        z.close()
66

  
67
        config_file = os.path.join(self.collectivity_install_dir, 'config.pck')
68
        if os.path.exists(config_file):
69
            wcs_cfg = cPickle.load(file(os.path.join(self.collectivity_install_dir, 'config.pck')))
70
        else:
71
            wcs_cfg = {}
72

  
73
        has_sql = self.make_sql_config(wcs_cfg)
74
        self.make_sso_config(wcs_cfg)
75
        self.make_site_options()
76

  
77
        cPickle.dump(wcs_cfg, file(config_file, 'w'))
78

  
79
        if has_sql:
80
            self.make_sql_tables(wcs_cfg)
81

  
82
        self.make_apache_vhost()
83
        self.reload_apache()
84

  
85

  
86
    def make_sql_config(self, wcs_cfg):
87
        if not wcs_cfg.get('postgresql'):
88
            # this is not a site configured to use SQL
89
            return False
90

  
91
        database_name = wcs_cfg['postgresql'].get('database', 'wcs')
92
        domain_table_name = self.domain.replace('-', '_').replace('.', '_')
93
        if '_' in database_name:
94
            database_name = '%s_%s' % (database_name.split('_')[0], domain_table_name)
95
        else:
96
            database_name = '%s_%s' % (database_name, domain_table_name)
97

  
98
        postgresql_cfg = {}
99
        for k, v in wcs_cfg['postgresql'].items():
100
            if v:
101
                postgresql_cfg[k] = v
102
        try:
103
            pgconn = psycopg2.connect(**postgresql_cfg)
104
        except psycopg2.Error:
105
            # XXX: log
106
            raise
107

  
108
        pgconn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
109
        cur = pgconn.cursor()
110
        try:
111
            cur.execute('''CREATE DATABASE %s''' % database_name)
112
        except psycopg2.Error as e:
113
            print 'got psycopg2 error:', e
114
        cur.close()
115

  
116
        wcs_cfg['postgresql']['database'] = database_name
117

  
118
        return True
119

  
120
    def make_sql_tables(self, wcs_cfg):
121
        params = []
122
        for param in ('database', 'user', 'password', 'host', 'port'):
123
            if wcs_cfg.get('postgresql').get(param):
124
                if param == 'database':
125
                    params.append('--dbname')
126
                else:
127
                    params.append('--' + param)
128
                params.append(wcs_cfg.get('postgresql').get(param))
129
        os.system('%s convert-to-sql %s %s' % (app_settings.WCSCTL_SCRIPT, ' '.join(params), self.domain))
130

  
131
    def make_sso_config(self, wcs_cfg):
132
        has_idff = False
133
        has_saml2 = False
134

  
135
        service_provider_configuration = {}
136

  
137
        if self.url.endswith('/'):
138
            url_stripped = self.url[:-1]
139
        else:
140
            url_stripped = self.url
141

  
142
        if os.path.exists(os.path.join(self.skel_dir, 'idff-metadata-template')):
143
            # there's a ID-FF metadata template, so we do the ID-FF stuff
144
            has_idff = True
145
            service_provider_configuration.update({
146
                'base_url': '%s/liberty' % url_stripped,
147
                'metadata': 'metadata.xml',
148
                'providerid': '%s/liberty/metadata' % url_stripped,
149
                })
150

  
151
            idff_metadata_template = file(
152
                    os.path.join(self.skel_dir, 'idff-metadata-template')).read()
153
            file(os.path.join(self.collectivity_install_dir, 'metadata.xml'), 'w').write(
154
                    string.Template(idff_metadata_template).substitute({'url': url_stripped}))
155

  
156
        if os.path.exists(os.path.join(self.skel_dir, 'saml2-metadata-template')):
157
            # there's a SAMLv2 metadata template, so we do the SAMLv2 stuff
158
            has_saml2 = True
159
            service_provider_configuration.update({
160
                'saml2_base_url': '%s/saml' % url_stripped,
161
                'saml2_metadata': 'saml2-metadata.xml',
162
                'saml2_providerid': '%s/saml/metadata' % url_stripped
163
                })
164

  
165
            saml2_metadata_template = file(
166
                    os.path.join(self.skel_dir, 'saml2-metadata-template')).read()
167
            file(os.path.join(self.collectivity_install_dir, 'saml2-metadata.xml'), 'w').write(
168
                    string.Template(saml2_metadata_template).substitute({'url': url_stripped}))
169

  
170
        if has_idff or has_saml2:
171
            idp_metadata = ET.parse(file(os.path.join(self.skel_dir, 'idp-metadata.xml')))
172
            entity_id = idp_metadata.getroot().attrib['entityID']
173
            idp_key = get_provider_key(entity_id)
174

  
175
            wcs_cfg['identification'] = {'methods': ['idp']}
176
            wcs_cfg['idp'] = {
177
                    idp_key: {
178
                        'metadata': 'provider-%s-metadata.xml' % idp_key,
179
                        'metadata_url': entity_id,
180
                        'publickey_url': None,
181
                        'role': 2}}
182
            wcs_cfg['sp'] = {
183
                    'common_domain': None,
184
                    'common_domain_getter_url': None,
185
                    'organization_name': self.title.encode('utf-8'),
186
                    'privatekey': 'private-key.pem',
187
                    'publickey': 'public-key.pem'}
188
            wcs_cfg['sp'].update(service_provider_configuration)
189

  
190
            file(os.path.join(self.collectivity_install_dir, 'provider-%s-metadata.xml' % idp_key), 'w').write(
191
                    file(os.path.join(self.skel_dir, 'idp-metadata.xml')).read())
192
            file(os.path.join(self.collectivity_install_dir, 'public-key.pem'), 'w').write(
193
                    file(os.path.join(self.skel_dir, 'public-key.pem')).read())
194
            file(os.path.join(self.collectivity_install_dir, 'private-key.pem'), 'w').write(
195
                    file(os.path.join(self.skel_dir, 'private-key.pem')).read())
196
        else:
197
            wcs_cfg['identification'] = {'methods': ['password']}
198

  
199

  
200
    def make_site_options(self):
201
        options_template_path = os.path.join(self.skel_dir, 'site-options.cfg')
202
        if not os.path.exists(options_template_path):
203
            return
204
        options_template = file(options_template_path).read()
205
        file(os.path.join(self.collectivity_install_dir, 'site-options.cfg'), 'w').write(
206
                string.Template(options_template).substitute({
207
                    'domain': self.domain, 
208
                    'options': self.site_options_cfg
209
                })
210
        )
211

  
212

  
213
    def make_apache_vhost(self):
214
        apache_vhost_template_path = os.path.join(self.skel_dir, 'apache-vhost.conf')
215
        if not os.path.exists(apache_vhost_template_path):
216
            return
217
        apache_vhost_template = file(apache_vhost_template_path).read()
218
        apache_dir = os.path.join(settings.MEDIA_ROOT, 'vhosts.d')
219
        if not os.path.exists(apache_dir):
220
            os.mkdir(apache_dir)
221
        file(os.path.join(apache_dir, '%s.conf' % self.domain), 'w').write(
222
                string.Template(apache_vhost_template).substitute({'domain': self.domain}))
223

  
224

  
225
    def reload_apache(self):
226
        os.system('sudo -n /etc/init.d/apache2 reload')
wcsinst/wcsinstd/models.py
1
from django.db import models
2

  
3
# Create your models here.
wcsinst/wcsinstd/urls.py
1
from django.conf.urls import patterns, url, include
2

  
3
urlpatterns = patterns('wcsinst.wcsinstd.views',
4
    url(r'^create$', 'create'),
5
    url(r'^(?P<instance>[\w\.-]+)/$', 'update'),
6
)
7

  
wcsinst/wcsinstd/views.py
1
import json
2
import threading
3

  
4
from django.views.decorators.csrf import csrf_exempt
5
from django.views.decorators.http import require_POST
6

  
7
from jsonresponse import to_json
8

  
9
from .deploy import DeployInstance
10

  
11

  
12
@csrf_exempt
13
@to_json('api')
14
@require_POST
15
def create(request):
16
    data = json.loads(request.body)
17
    deploy = DeployInstance(**data)
18
    threading.Thread(target=deploy.make).start()
19
    return {}
20

  
21

  
22
@csrf_exempt
23
@to_json('api')
24
@require_POST
25
def update(request, instance):
26
    print 'updating instance:', instance
27
    data = json.loads(request.body)
28
    if data.get('domain') != instance:
29
        raise Exception('domain mismatch') # -> should remove/add ?
30
    deploy = DeployInstance(**data)
31
    threading.Thread(target=deploy.make).start()
32
    return {}
wcsinst/wsgi.py
1
"""
2
WSGI config for wcsinst project.
3

  
4
This module contains the WSGI application used by Django's development server
5
and any production WSGI deployments. It should expose a module-level variable
6
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7
this application via the ``WSGI_APPLICATION`` setting.
8

  
9
Usually you will have the standard Django WSGI application here, but it also
10
might make sense to replace the whole Django WSGI application with a custom one
11
that later delegates to the Django one. For example, you could introduce WSGI
12
middleware here, or combine a Django application with an application of another
13
framework.
14

  
15
"""
16
import os
17

  
18
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
19
# if running multiple sites in the same mod_wsgi process. To fix this, use
20
# mod_wsgi daemon mode with each site in its own daemon process, or use
21
# os.environ["DJANGO_SETTINGS_MODULE"] = "wcsinst.settings"
22
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wcsinst.settings")
23

  
24
# This application object is used by any WSGI server configured to use this
25
# file. This includes Django's development server, if the WSGI_APPLICATION
26
# setting points here.
27
from django.core.wsgi import get_wsgi_application
28
application = get_wsgi_application()
29

  
30
# Apply WSGI middleware here.
31
# from helloworld.wsgi import HelloWorldApplication
32
# application = HelloWorldApplication(application)

Formats disponibles : Unified diff