hobo/hobo/environment/models.py

502 lines
16 KiB
Python
Raw Normal View History

import datetime
2014-06-25 17:55:18 +02:00
import json
2018-11-28 19:59:45 +01:00
import random
import requests
2018-09-14 14:36:47 +02:00
import socket
2014-11-05 12:29:57 +01:00
from django.conf import settings
2018-11-28 19:59:45 +01:00
from django.core.cache import cache
from django.db import models
from django.utils.crypto import get_random_string
from django.utils.encoding import force_text
2018-09-14 14:36:47 +02:00
from django.utils.six.moves.urllib.parse import urlparse
from django.utils.timezone import now
2014-03-24 21:53:42 +01:00
from django.utils.translation import ugettext_lazy as _
2014-06-25 17:55:18 +02:00
from django.core.exceptions import ValidationError
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from .utils import Zone, get_installed_services
from .mandayejs_app_settings import APP_SETTINGS_CLASSES, DEFAULT_APP_SETTINGS
SECRET_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
2014-03-24 21:53:42 +01:00
2014-06-25 17:55:18 +02:00
class Variable(models.Model):
name = models.CharField(max_length=100, verbose_name=_('name'))
label = models.CharField(max_length=100, blank=True, verbose_name=_('label'))
2014-06-25 17:55:18 +02:00
value = models.TextField(verbose_name=_('value'),
blank=True,
help_text=_('start with [ or { for a JSON document'))
auto = models.BooleanField(default=False)
service_type = models.ForeignKey(ContentType, null=True)
service_pk = models.PositiveIntegerField(null=True)
service = GenericForeignKey('service_type', 'service_pk')
last_update_timestamp = models.DateTimeField(auto_now=True, null=True)
2014-06-25 17:55:18 +02:00
def get_field_label(self):
if self.label:
return self.label
return self.name
2014-06-25 17:55:18 +02:00
@property
def json(self):
if self.value and (self.value[0] in '{[' or self.value in ('true', 'false')):
try:
return json.loads(self.value)
except ValueError:
pass
2014-06-25 17:55:18 +02:00
return self.value
def clean(self):
if self.value and (self.value[0] in '{[' or self.value in ('true', 'false')):
2014-06-25 17:55:18 +02:00
try:
json.loads(self.value)
except ValueError:
raise ValidationError('invalid JSON document')
class ServiceBase(models.Model):
class Meta:
abstract = True
2014-03-25 13:51:38 +01:00
title = models.CharField(_('Title'), max_length=50)
2015-03-09 09:13:14 +01:00
slug = models.SlugField(_('Slug'))
2014-03-25 13:51:38 +01:00
base_url = models.CharField(_('Base URL'), max_length=200)
2014-06-23 16:00:31 +02:00
secret_key = models.CharField(_('Secret Key'), max_length=60)
template_name = models.CharField(_('Template'), max_length=60, blank=True)
secondary = models.BooleanField(_('Secondary Service'), default=False)
2014-03-24 21:53:42 +01:00
last_operational_check_timestamp = models.DateTimeField(null=True)
last_operational_success_timestamp = models.DateTimeField(null=True)
last_update_timestamp = models.DateTimeField(auto_now=True, null=True)
variables = GenericRelation(Variable,
content_type_field='service_type', object_id_field='service_pk')
@classmethod
def is_enabled(cls):
return True
def is_operational(self):
return (self.last_operational_success_timestamp is not None and
self.last_operational_success_timestamp == self.last_operational_check_timestamp)
def check_operational(self):
once_now = now()
self.last_operational_check_timestamp = once_now
try:
zone = self.get_admin_zones()[0]
response = requests.get(zone.href, timeout=10, allow_redirects=False)
response.raise_for_status()
self.last_operational_success_timestamp = once_now
except requests.RequestException as e:
pass
self.save(update_fields=('last_operational_check_timestamp', 'last_operational_success_timestamp'))
def wants_frequent_checks(self):
# decides if a "being deployed..." spinner should be displayed (and
# automatically hidden) next to the service.
if self.last_operational_success_timestamp is not None:
# if the service has been marked as operational, we don't need a
# spinner at all.
return False
if self.last_operational_check_timestamp is None:
# if the service has never been checked, sure we wants a spinner.
return True
two_minutes = datetime.timedelta(minutes=2)
# monitor actively for two minutes max.
return (self.last_operational_check_timestamp - self.last_update_timestamp) < two_minutes
2014-03-25 17:00:18 +01:00
def as_dict(self):
as_dict = dict([(x, y) for (x, y) in self.__dict__.items()
2014-03-25 17:00:18 +01:00
if type(y) in (int, str, unicode)])
as_dict['base_url'] = self.get_base_url_path()
2014-03-25 17:00:18 +01:00
as_dict['service-id'] = self.Extra.service_id
as_dict['service-label'] = force_text(self.Extra.service_label)
as_dict['variables'] = dict(((v.name, v.json) for v in self.variables.all()))
as_dict['secondary'] = self.secondary
2014-12-09 13:50:35 +01:00
if self.get_saml_sp_metadata_url():
as_dict['saml-sp-metadata-url'] = self.get_saml_sp_metadata_url()
if self.get_saml_idp_metadata_url():
as_dict['saml-idp-metadata-url'] = self.get_saml_idp_metadata_url()
if self.get_backoffice_menu_url():
as_dict['backoffice-menu-url'] = self.get_backoffice_menu_url()
2014-03-25 17:00:18 +01:00
return as_dict
@property
def name(self):
return self.title
def clean(self, *args, **kwargs):
for service in get_installed_services():
if service.slug == self.slug and service.id != self.id:
raise ValidationError(_('This slug is already used. It must be unique.'))
return super(ServiceBase, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.base_url = self.base_url.strip().lower()
if not self.base_url.endswith('/'):
self.base_url += '/'
if not self.secret_key:
self.secret_key = get_random_string(50, SECRET_CHARS)
is_new = (self.id is None)
super(ServiceBase, self).save(*args, **kwargs)
if is_new and settings.SERVICE_EXTRA_VARIABLES:
for variable in settings.SERVICE_EXTRA_VARIABLES.get(self.Extra.service_id, []):
v = Variable()
if type(variable) is dict:
v.name = variable.get('name')
v.label = variable.get('label')
else:
v.name = variable
v.service = self
v.save()
2014-12-09 13:50:35 +01:00
def get_saml_sp_metadata_url(self):
return None
def get_saml_idp_metadata_url(self):
return None
def get_base_url_path(self):
base_url = self.base_url
if not base_url.endswith('/'):
base_url += '/'
return base_url
def get_backoffice_menu_url(self):
return None
2018-09-14 14:36:47 +02:00
def is_resolvable(self):
try:
netloc = urlparse(self.base_url).netloc
if netloc and socket.gethostbyname(netloc):
return True
except socket.gaierror:
return False
def has_valid_certificate(self):
if not self.is_resolvable():
return False
try:
requests.get(self.base_url, verify=True, allow_redirects=False)
2018-09-14 14:36:47 +02:00
return True
except requests.exceptions.SSLError:
return False
except requests.exceptions.ConnectionError:
return False
def is_running(self):
if not self.is_resolvable():
return False
r = requests.get(self.get_admin_zones()[0].href, verify=False, allow_redirects=False)
return (r.status_code >= 200 and r.status_code < 400)
2018-09-14 14:36:47 +02:00
2018-11-28 19:59:45 +01:00
def get_health_dict(self):
properties = [
('is_resolvable', 120),
('has_valid_certificate', 3600),
('is_running', 60),
('is_operational', 60),
]
result = {}
for name, cache_duration in properties:
cache_key = '%s_%s' % (self.slug, name)
value = cache.get(cache_key)
if value is None:
value = getattr(self, name)()
cache.set(cache_key, value, cache_duration * (0.5 + random.random()))
result[name] = value
return result
2014-03-25 17:00:18 +01:00
2014-04-10 14:49:01 +02:00
class Authentic(ServiceBase):
use_as_idp_for_self = models.BooleanField(
verbose_name=_('Use as IdP'),
default=False)
2014-03-24 21:53:42 +01:00
class Meta:
2014-04-10 14:49:01 +02:00
verbose_name = _('Authentic Identity Provider')
verbose_name_plural = _('Authentic Identity Providers')
ordering = ['title']
2014-03-24 21:53:42 +01:00
class Extra:
2014-06-17 17:12:28 +02:00
service_id = 'authentic'
service_label = _('Authentic')
service_default_slug = 'idp'
2014-03-24 21:53:42 +01:00
def get_admin_zones(self):
return [
Zone(_('User Management'), 'users', self.get_base_url_path() + 'manage/users/'),
Zone(_('Role Management'), 'roles', self.get_base_url_path() + 'manage/roles/'),
]
def get_saml_idp_metadata_url(self):
return self.get_base_url_path() + 'idp/saml2/metadata'
def get_backoffice_menu_url(self):
return self.get_base_url_path() + 'manage/menu.json'
2014-03-24 21:53:42 +01:00
2014-04-10 14:49:01 +02:00
class Wcs(ServiceBase):
2014-03-24 21:53:42 +01:00
class Meta:
2014-04-10 14:49:01 +02:00
verbose_name = _('w.c.s. Web Forms')
verbose_name_plural = _('w.c.s. Web Forms')
ordering = ['title']
2014-03-24 21:53:42 +01:00
class Extra:
service_id = 'wcs'
service_label = _('w.c.s.')
service_default_slug = 'eservices'
2014-03-24 21:53:42 +01:00
def get_backoffice_menu_url(self):
return self.get_base_url_path() + 'backoffice/menu.json'
def get_admin_zones(self):
return [
Zone(self.title, 'webforms', self.get_base_url_path() + 'admin/'),
]
2014-12-09 13:50:35 +01:00
def get_saml_sp_metadata_url(self):
return self.get_base_url_path() + 'saml/metadata'
2014-12-09 13:50:35 +01:00
2014-03-24 21:53:42 +01:00
2014-07-11 14:39:02 +02:00
class Passerelle(ServiceBase):
class Meta:
verbose_name = _('Passerelle')
verbose_name_plural = _('Passerelle')
ordering = ['title']
2014-07-11 14:39:02 +02:00
class Extra:
service_id = 'passerelle'
service_label = _('Passerelle')
service_default_slug = 'passerelle'
2014-07-11 14:39:02 +02:00
def get_admin_zones(self):
return [
Zone(self.title, 'webservices', self.get_base_url_path() + 'manage/')
2014-07-11 14:39:02 +02:00
]
def get_saml_sp_metadata_url(self):
return self.get_base_url_path() + 'accounts/mellon/metadata/'
def get_backoffice_menu_url(self):
return self.get_base_url_path() + 'manage/menu.json'
2015-02-10 11:19:15 +01:00
class Combo(ServiceBase):
class Meta:
verbose_name = _('Combo Portal')
verbose_name_plural = _('Combo Portals')
ordering = ['title']
2015-02-10 11:19:15 +01:00
class Extra:
service_id = 'combo'
service_label = _('Combo')
service_default_slug = 'portal'
2015-02-10 11:19:15 +01:00
def get_admin_zones(self):
return [
Zone(self.title, 'portal', self.get_base_url_path() + 'manage/')
2015-02-10 11:19:15 +01:00
]
2015-02-11 14:24:38 +01:00
def get_saml_sp_metadata_url(self):
return self.get_base_url_path() + 'accounts/mellon/metadata/'
2015-02-11 14:24:38 +01:00
def get_backoffice_menu_url(self):
return self.get_base_url_path() + 'manage/menu.json'
2015-03-11 15:26:20 +01:00
class Fargo(ServiceBase):
class Meta:
verbose_name = _('Fargo document box')
verbose_name_plural = _('Fargo document box')
ordering = ['title']
2015-03-11 15:26:20 +01:00
class Extra:
service_id = 'fargo'
service_label = _('Fargo')
service_default_slug = 'porte-doc'
2015-03-11 15:26:20 +01:00
def get_admin_zones(self):
return [
Zone(self.title, 'document-box', self.get_base_url_path() + 'admin/')
2015-03-11 15:26:20 +01:00
]
def get_saml_sp_metadata_url(self):
return self.get_base_url_path() + 'accounts/mellon/metadata/'
2015-03-11 15:26:20 +01:00
2015-07-10 11:25:57 +02:00
class Welco(ServiceBase):
class Meta:
verbose_name = _('Welco Multichannel Home')
ordering = ['title']
2015-07-10 11:25:57 +02:00
class Extra:
service_id = 'welco'
service_label = 'Welco'
service_default_slug = 'accueil'
2015-07-10 11:25:57 +02:00
def get_admin_zones(self):
return [
Zone(self.title, 'multichannel-guichet', self.get_base_url_path() + 'admin/')
]
def get_saml_sp_metadata_url(self):
return self.get_base_url_path() + 'accounts/mellon/metadata/'
2015-11-05 10:27:28 +01:00
def get_backoffice_menu_url(self):
return self.get_base_url_path() + 'menu.json'
class MandayeJS(ServiceBase):
site_app = models.CharField(_('Site Application'), max_length=128,
choices = APP_SETTINGS_CLASSES,
default = DEFAULT_APP_SETTINGS
)
class Meta:
verbose_name = _('Authentication Reverse Proxy')
ordering = ['title']
class Extra:
service_id = 'mandayejs'
service_label = _('mandayejs')
service_default_slug = 'mandayejs'
@classmethod
def is_enabled(cls):
return getattr(settings, 'MANDAYEJS_ENABLED', False)
def get_admin_zones(self):
return [
2016-12-05 12:27:30 +01:00
Zone(self.title, 'mandayejs', self.get_base_url_path() + '_mandaye/admin/')
]
def get_saml_sp_metadata_url(self):
return self.get_base_url_path()+ '_mandaye/accounts/mellon/metadata/'
class Chrono(ServiceBase):
class Meta:
verbose_name = _('Chrono Agendas')
verbose_name_plural = _('Chrono Agendas')
ordering = ['title']
class Extra:
service_id = 'chrono'
service_label = _('Chrono')
service_default_slug = 'agendas'
def get_admin_zones(self):
return [
Zone(self.title, 'calendar', self.get_base_url_path() + 'manage/')
]
def get_saml_sp_metadata_url(self):
return self.get_base_url_path() + 'accounts/mellon/metadata/'
def get_backoffice_menu_url(self):
return self.get_base_url_path() + 'manage/menu.json'
class Hobo(ServiceBase):
class Meta:
verbose_name = _('Hobo Deployment Server')
verbose_name_plural = _('Hobo Deployment Servers')
ordering = ['title']
class Extra:
service_id = 'hobo'
service_label = _('Deployment Server')
service_default_slug = 'hobo'
def get_admin_zones(self):
return [
Zone(self.title, 'hobo', self.get_base_url_path())
]
def get_saml_sp_metadata_url(self):
return self.get_base_url_path() + 'accounts/mellon/metadata/'
def get_backoffice_menu_url(self):
return None
2016-03-03 11:25:42 +01:00
class Piwik(ServiceBase):
admin_emails = models.TextField(_('Admins Emails'))
class Meta:
verbose_name = _('Piwik')
verbose_name_plural = _('Piwik')
ordering = ['title']
class Extra:
service_id = 'piwik'
service_label = _('piwik')
service_default_slug = 'piwik'
2016-03-03 11:25:42 +01:00
@classmethod
def is_enabled(cls):
return getattr(settings, 'PIWIK_ENABLED', False)
def get_admin_zones(self):
return [
Zone(self.title, 'piwik', self.get_base_url_path())
]
2016-05-11 09:46:30 +02:00
class Corbo(ServiceBase):
class Meta:
verbose_name = _('Announces Management')
verbose_name_plural = _('Announces Management')
ordering = ['title']
class Extra:
service_id = 'corbo'
service_label = _('Corbo')
service_default_slug = 'announces'
2016-05-11 09:46:30 +02:00
@classmethod
def is_enabled(cls):
return getattr(settings, 'CORBO_ENABLED', False)
2016-05-11 09:46:30 +02:00
def get_admin_zones(self):
return [
Zone(self.title, 'corbo', self.get_base_url_path() + 'admin/')
]
def get_saml_sp_metadata_url(self):
return self.get_base_url_path()+ 'accounts/mellon/metadata/'
2016-05-11 09:46:30 +02:00
def get_backoffice_menu_url(self):
return self.get_base_url_path() + 'manage/menu.json'
2016-07-11 16:57:15 +02:00
class BiJoe(ServiceBase):
class Meta:
verbose_name = _('Statistics')
verbose_name_plural = _('Statistics')
ordering = ['title']
class Extra:
service_id = 'bijoe'
service_label = _('Statistics')
service_default_slug = 'statistics'
2016-07-11 16:57:15 +02:00
def get_admin_zones(self):
return [
Zone(self.title, 'bijoe', self.get_base_url_path())
2016-07-11 16:57:15 +02:00
]
def get_saml_sp_metadata_url(self):
return self.get_base_url_path() + 'accounts/mellon/metadata/'
def get_backoffice_menu_url(self):
return self.get_base_url_path() + 'manage/menu.json'
AVAILABLE_SERVICES = [Authentic, Wcs, Passerelle, Combo, Fargo, Welco,
MandayeJS, Chrono, Piwik, Corbo, BiJoe, Hobo]