trivial: apply black
This commit is contained in:
parent
567ed17306
commit
872f39774a
|
@ -7,11 +7,11 @@ MEDIA_ROOT = 'media'
|
|||
STATIC_ROOT = 'collected-static'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
## Django Mellon configuration
|
||||
# you need to generate SSL certificates in your current directory to make it functionnal :
|
||||
|
@ -21,13 +21,13 @@ DATABASES = {
|
|||
#
|
||||
# you also need to get the idp metadata and call it idp-metadata.xml
|
||||
# Uncomment the following lines to enable SAML support
|
||||
#INSTALLED_APPS += ('mellon',)
|
||||
#AUTHENTICATION_BACKENDS = ( 'mellon.backends.SAMLBackend',)
|
||||
#LOGIN_URL = '/accounts/mellon/login/'
|
||||
#LOGOUT_URL = '/accounts/mellon/logout/'
|
||||
#MELLON_PUBLIC_KEYS = ['cert.pem']
|
||||
#MELLON_PRIVATE_KEY = 'key.cert'
|
||||
#MELLON_IDENTITY_PROVIDERS = [
|
||||
# INSTALLED_APPS += ('mellon',)
|
||||
# AUTHENTICATION_BACKENDS = ( 'mellon.backends.SAMLBackend',)
|
||||
# LOGIN_URL = '/accounts/mellon/login/'
|
||||
# LOGOUT_URL = '/accounts/mellon/logout/'
|
||||
# MELLON_PUBLIC_KEYS = ['cert.pem']
|
||||
# MELLON_PRIVATE_KEY = 'key.cert'
|
||||
# MELLON_IDENTITY_PROVIDERS = [
|
||||
# {'METADATA': 'idp-metadata.xml',
|
||||
# 'GROUP_ATTRIBUTE': 'role'},
|
||||
# ]
|
||||
|
|
|
@ -29,9 +29,7 @@ HOBO_MANAGE_COMMAND = 'sudo -u hobo /usr/bin/hobo-manage'
|
|||
|
||||
CELERY_SETTINGS = {
|
||||
'CELERY_SEND_TASK_ERROR_EMAILS': True,
|
||||
'ADMINS': (
|
||||
('Admins', 'root@localhost'),
|
||||
),
|
||||
'ADMINS': (('Admins', 'root@localhost'),),
|
||||
}
|
||||
|
||||
# run additional settings snippets
|
||||
|
|
|
@ -21,9 +21,7 @@ DEBUG = False
|
|||
|
||||
SECRET_KEY = open('/etc/%s/secret' % PROJECT_NAME).read()
|
||||
|
||||
ADMINS = (
|
||||
('Tous', 'root@localhost'),
|
||||
)
|
||||
ADMINS = (('Tous', 'root@localhost'),)
|
||||
|
||||
EMAIL_SUBJECT_PREFIX = ''
|
||||
|
||||
|
@ -74,12 +72,12 @@ LOGGING = {
|
|||
'clamp_to_warning': {
|
||||
'()': 'hobo.logger.ClampLogLevel',
|
||||
'level': 'WARNING',
|
||||
}
|
||||
},
|
||||
},
|
||||
'formatters': {
|
||||
'syslog': {
|
||||
'format': '%(application)s %(levelname)s %(tenant)s %(ip)s %(user)s %(request_id)s'
|
||||
' %(message)s',
|
||||
' %(message)s',
|
||||
},
|
||||
'debug': {
|
||||
'format': DEBUG_LOG_FORMAT,
|
||||
|
@ -119,7 +117,7 @@ LOGGING = {
|
|||
'backupCount': 1,
|
||||
'interval': 1,
|
||||
'filters': ['request_context', 'debug_log'],
|
||||
}
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django.db': {
|
||||
|
@ -232,13 +230,14 @@ if 'TEMPLATE_DEBUG' in globals():
|
|||
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS
|
||||
|
||||
if not 'django.core.context_processors.request' in TEMPLATE_CONTEXT_PROCESSORS:
|
||||
TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.request', )\
|
||||
+ TEMPLATE_CONTEXT_PROCESSORS
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.core.context_processors.request',
|
||||
) + TEMPLATE_CONTEXT_PROCESSORS
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'hobo.context_processors.template_vars',
|
||||
'hobo.context_processors.theme_base',
|
||||
'hobo.context_processors.portal_agent_url',
|
||||
) + TEMPLATE_CONTEXT_PROCESSORS
|
||||
'hobo.context_processors.template_vars',
|
||||
'hobo.context_processors.theme_base',
|
||||
'hobo.context_processors.portal_agent_url',
|
||||
) + TEMPLATE_CONTEXT_PROCESSORS
|
||||
else:
|
||||
assert len(TEMPLATES)
|
||||
assert TEMPLATES[0]['BACKEND'] == 'django.template.backends.django.DjangoTemplates'
|
||||
|
@ -246,31 +245,30 @@ else:
|
|||
if not 'loaders' in TEMPLATES[0]['OPTIONS']:
|
||||
TEMPLATES[0]['APP_DIRS'] = False
|
||||
TEMPLATES[0]['OPTIONS']['loaders'] = [
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader']
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
]
|
||||
if PROJECT_NAME != 'wcs':
|
||||
TEMPLATES[0]['OPTIONS']['loaders'].insert(0,
|
||||
'hobo.multitenant.template_loader.FilesystemLoader')
|
||||
TEMPLATES[0]['OPTIONS']['loaders'].insert(0, 'hobo.multitenant.template_loader.FilesystemLoader')
|
||||
|
||||
if not 'django.template.context_processors.request' in TEMPLATES[0]['OPTIONS']['context_processors']:
|
||||
TEMPLATES[0]['OPTIONS']['context_processors'].insert(0,
|
||||
'django.template.context_processors.request')
|
||||
TEMPLATES[0]['OPTIONS']['context_processors'].insert(0, 'django.template.context_processors.request')
|
||||
|
||||
TEMPLATES[0]['OPTIONS']['context_processors'] = [
|
||||
'hobo.context_processors.template_vars',
|
||||
'hobo.context_processors.theme_base',
|
||||
'hobo.context_processors.portal_agent_url',
|
||||
] + TEMPLATES[0]['OPTIONS']['context_processors']
|
||||
'hobo.context_processors.template_vars',
|
||||
'hobo.context_processors.theme_base',
|
||||
'hobo.context_processors.portal_agent_url',
|
||||
] + TEMPLATES[0]['OPTIONS']['context_processors']
|
||||
|
||||
# needed by hobo.context_processors.theme_base:
|
||||
#THEME_SKELETON_URL = 'https://www.example.net/__skeleton__'
|
||||
# THEME_SKELETON_URL = 'https://www.example.net/__skeleton__'
|
||||
|
||||
# Browsers may ensure that cookies are only sent under an HTTPS connection
|
||||
CSRF_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_SECURE = True
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
||||
SESSION_COOKIE_AGE = 36000 # 10h
|
||||
SESSION_COOKIE_AGE = 36000 # 10h
|
||||
|
||||
CSRF_COOKIE_SAMESITE = None
|
||||
# Apply sessionNotOnOrAfter on session expiration date
|
||||
|
@ -297,9 +295,7 @@ if PROJECT_NAME != 'wcs' and 'authentic2' not in INSTALLED_APPS:
|
|||
)
|
||||
|
||||
if 'authentic2' in INSTALLED_APPS:
|
||||
MIDDLEWARE = MIDDLEWARE + (
|
||||
'hobo.agent.authentic2.middleware.ProvisionningMiddleware',
|
||||
)
|
||||
MIDDLEWARE = MIDDLEWARE + ('hobo.agent.authentic2.middleware.ProvisionningMiddleware',)
|
||||
|
||||
|
||||
if PROJECT_NAME != 'wcs':
|
||||
|
@ -317,13 +313,11 @@ if PROJECT_NAME != 'wcs':
|
|||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'tenant_schemas.postgresql_backend',
|
||||
'NAME': PROJECT_NAME.replace('-', '_'),
|
||||
'NAME': PROJECT_NAME.replace('-', '_'),
|
||||
}
|
||||
}
|
||||
|
||||
DATABASE_ROUTERS = (
|
||||
'tenant_schemas.routers.TenantSyncRouter',
|
||||
)
|
||||
DATABASE_ROUTERS = ('tenant_schemas.routers.TenantSyncRouter',)
|
||||
|
||||
TENANT_SETTINGS_LOADERS = (
|
||||
'hobo.multitenant.settings_loaders.TemplateVars',
|
||||
|
@ -386,8 +380,7 @@ MIDDLEWARE = (
|
|||
'hobo.middleware.xforwardedfor.XForwardedForMiddleware',
|
||||
) + MIDDLEWARE
|
||||
|
||||
MIDDLEWARE = MIDDLEWARE + (
|
||||
'hobo.middleware.PrometheusStatsMiddleware',)
|
||||
MIDDLEWARE = MIDDLEWARE + ('hobo.middleware.PrometheusStatsMiddleware',)
|
||||
|
||||
|
||||
HOBO_MANAGER_HOMEPAGE_URL_VAR = 'portal_agent_url'
|
||||
|
|
|
@ -11,7 +11,7 @@ MELLON_ADAPTER = ('hobo.utils.MellonAdapter',)
|
|||
MELLON_ADD_AUTHNREQUEST_NEXT_URL_EXTENSION = True
|
||||
|
||||
# add custom hobo agent module
|
||||
INSTALLED_APPS = ('hobo.agent.hobo', ) + INSTALLED_APPS
|
||||
INSTALLED_APPS = ('hobo.agent.hobo',) + INSTALLED_APPS
|
||||
|
||||
exec(open(os.path.join(ETC_DIR, 'settings.py')).read())
|
||||
|
||||
|
|
|
@ -15,15 +15,15 @@
|
|||
# DEBUG = False
|
||||
# TEMPLATE_DEBUG = False
|
||||
|
||||
#ADMINS = (
|
||||
# ADMINS = (
|
||||
# ('User 1', 'poulpe@example.org'),
|
||||
# ('User 2', 'janitor@example.net'),
|
||||
#)
|
||||
# )
|
||||
|
||||
# ALLOWED_HOSTS must be correct in production!
|
||||
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = [
|
||||
'*',
|
||||
'*',
|
||||
]
|
||||
|
||||
# If a tenant doesn't exist, the tenant middleware raise a 404 error. If you
|
||||
|
|
|
@ -15,13 +15,14 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db.models.signals import pre_save, pre_delete, m2m_changed, post_save
|
||||
from django.conf import settings
|
||||
from django.db.models.signals import m2m_changed, post_save, pre_delete, pre_save
|
||||
|
||||
|
||||
class Plugin:
|
||||
def get_before_urls(self):
|
||||
from . import urls
|
||||
|
||||
return urls.urlpatterns
|
||||
|
||||
|
||||
|
|
|
@ -1,28 +1,25 @@
|
|||
import requests
|
||||
import logging
|
||||
import os
|
||||
from time import sleep
|
||||
import xml.etree.ElementTree as ET
|
||||
from time import sleep
|
||||
|
||||
import requests
|
||||
from authentic2 import app_settings
|
||||
from authentic2.a2_rbac.utils import get_default_ou
|
||||
from authentic2.compat_lasso import lasso
|
||||
from authentic2.models import Attribute
|
||||
from authentic2.saml.models import LibertyProvider, SPOptionsIdPPolicy, \
|
||||
SAMLAttribute, LibertyServiceProvider
|
||||
from authentic2.a2_rbac.utils import get_default_ou
|
||||
|
||||
from authentic2.saml.models import LibertyProvider, LibertyServiceProvider, SAMLAttribute, SPOptionsIdPPolicy
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _, activate
|
||||
from django.core import serializers
|
||||
|
||||
from django_rbac.utils import get_role_model, get_ou_model
|
||||
from django.conf import settings
|
||||
|
||||
from django.utils.translation import activate
|
||||
from django.utils.translation import ugettext as _
|
||||
from django_rbac.utils import get_ou_model, get_role_model
|
||||
from tenant_schemas.utils import tenant_context
|
||||
|
||||
from hobo.agent.common.management.commands import hobo_deploy
|
||||
from hobo.agent.authentic2.provisionning import Provisionning
|
||||
from hobo.agent.common.management.commands import hobo_deploy
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
@ -61,9 +58,7 @@ class Command(hobo_deploy.Command):
|
|||
user_dict['is_staff'] = True
|
||||
user_dict['is_superuser'] = True
|
||||
|
||||
user, created = User.objects.get_or_create(
|
||||
defaults=user_dict,
|
||||
username=user_dict['username'])
|
||||
user, created = User.objects.get_or_create(defaults=user_dict, username=user_dict['username'])
|
||||
|
||||
# create/update user attributes
|
||||
fields = []
|
||||
|
@ -76,11 +71,18 @@ class Command(hobo_deploy.Command):
|
|||
fields.append(attribute['name'])
|
||||
continue
|
||||
attr, created = Attribute.all_objects.update_or_create(
|
||||
name=attribute['name'],
|
||||
defaults={'kind': attribute['kind']})
|
||||
for key in ('label', 'description', 'asked_on_registration',
|
||||
'user_editable', 'user_visible', 'required',
|
||||
'searchable', 'disabled'):
|
||||
name=attribute['name'], defaults={'kind': attribute['kind']}
|
||||
)
|
||||
for key in (
|
||||
'label',
|
||||
'description',
|
||||
'asked_on_registration',
|
||||
'user_editable',
|
||||
'user_visible',
|
||||
'required',
|
||||
'searchable',
|
||||
'disabled',
|
||||
):
|
||||
if key in attribute:
|
||||
setattr(attr, key, attribute[key])
|
||||
attr.order = i
|
||||
|
@ -106,9 +108,12 @@ class Command(hobo_deploy.Command):
|
|||
names = ['username', 'is_superuser'] + fields + disabled_fields
|
||||
for name in names:
|
||||
attribute, created = SAMLAttribute.objects.get_or_create(
|
||||
name=name, name_format='basic',
|
||||
name=name,
|
||||
name_format='basic',
|
||||
attribute_name='django_user_%s' % name,
|
||||
object_id=policy.id, content_type=policy_type)
|
||||
object_id=policy.id,
|
||||
content_type=policy_type,
|
||||
)
|
||||
attribute.enabled = not (name in disabled_fields)
|
||||
attribute.save()
|
||||
|
||||
|
@ -118,7 +123,8 @@ class Command(hobo_deploy.Command):
|
|||
name_format='basic',
|
||||
attribute_name='@verified_attributes@',
|
||||
object_id=policy.id,
|
||||
content_type=policy_type)
|
||||
content_type=policy_type,
|
||||
)
|
||||
|
||||
# create or update Service Providers
|
||||
services = hobo_environment['services']
|
||||
|
@ -135,8 +141,7 @@ class Command(hobo_deploy.Command):
|
|||
sp_url = service['saml-sp-metadata-url']
|
||||
metadata_text = None
|
||||
try:
|
||||
metadata_response = requests.get(
|
||||
sp_url, verify=app_settings.A2_VERIFY_SSL, timeout=5)
|
||||
metadata_response = requests.get(sp_url, verify=app_settings.A2_VERIFY_SSL, timeout=5)
|
||||
metadata_response.raise_for_status()
|
||||
# verify metadata is correct
|
||||
if self.check_saml_metadata(metadata_response.text):
|
||||
|
@ -149,10 +154,9 @@ class Command(hobo_deploy.Command):
|
|||
continue
|
||||
metadata_text = metadata_response.text
|
||||
|
||||
provider, service_created = \
|
||||
LibertyProvider.objects.get_or_create(
|
||||
entity_id=sp_url,
|
||||
protocol_conformance=lasso.PROTOCOL_SAML_2_0)
|
||||
provider, service_created = LibertyProvider.objects.get_or_create(
|
||||
entity_id=sp_url, protocol_conformance=lasso.PROTOCOL_SAML_2_0
|
||||
)
|
||||
provider.name = service['title']
|
||||
provider.slug = service['slug']
|
||||
provider.federation_source = 'hobo'
|
||||
|
@ -161,7 +165,8 @@ class Command(hobo_deploy.Command):
|
|||
variables = service.get('variables', {})
|
||||
if variables.get('ou-slug'):
|
||||
ou, created = get_ou_model().objects.get_or_create(
|
||||
slug=service['variables']['ou-slug'])
|
||||
slug=service['variables']['ou-slug']
|
||||
)
|
||||
ou.name = service['variables']['ou-label']
|
||||
ou.save()
|
||||
if service.get('secondary') and variables.get('ou-label'):
|
||||
|
@ -178,55 +183,54 @@ class Command(hobo_deploy.Command):
|
|||
continue
|
||||
if s['slug'] == service['slug']:
|
||||
continue
|
||||
if LibertyProvider.objects.filter(
|
||||
slug=s['slug']).exists():
|
||||
if LibertyProvider.objects.filter(slug=s['slug']).exists():
|
||||
create_ou = True
|
||||
break
|
||||
if create_ou:
|
||||
ou, created = get_ou_model().objects.get_or_create(
|
||||
name=service['title'])
|
||||
ou, created = get_ou_model().objects.get_or_create(name=service['title'])
|
||||
if service_created or not provider.ou:
|
||||
provider.ou = ou
|
||||
provision_target_ous[provider.ou.id] = provider.ou
|
||||
provider.save()
|
||||
if service_created:
|
||||
service_provider = LibertyServiceProvider(
|
||||
enabled=True, liberty_provider=provider,
|
||||
enabled=True,
|
||||
liberty_provider=provider,
|
||||
sp_options_policy=policy,
|
||||
users_can_manage_federations=False)
|
||||
users_can_manage_federations=False,
|
||||
)
|
||||
service_provider.save()
|
||||
|
||||
# add a superuser role for the service
|
||||
Role = get_role_model()
|
||||
name = _('Superuser of %s') % service['title']
|
||||
su_role, created = Role.objects.get_or_create(
|
||||
service=provider, slug='_a2-hobo-superuser',
|
||||
defaults={'name': name})
|
||||
service=provider, slug='_a2-hobo-superuser', defaults={'name': name}
|
||||
)
|
||||
if su_role.name != name:
|
||||
su_role.name = name
|
||||
su_role.save()
|
||||
su_role.attributes.get_or_create(name='is_superuser',
|
||||
kind='string',
|
||||
value='true')
|
||||
su_role.attributes.get_or_create(name='is_superuser', kind='string', value='true')
|
||||
# pass the new attribute to the service
|
||||
SAMLAttribute.objects.get_or_create(
|
||||
name='is_superuser',
|
||||
name_format='basic',
|
||||
attribute_name='is_superuser',
|
||||
object_id=provider.pk,
|
||||
content_type=provider_type)
|
||||
content_type=provider_type,
|
||||
)
|
||||
SAMLAttribute.objects.get_or_create(
|
||||
name='role-slug',
|
||||
name_format='basic',
|
||||
attribute_name='a2_service_ou_role_uuids',
|
||||
object_id=provider.pk,
|
||||
content_type=provider_type)
|
||||
content_type=provider_type,
|
||||
)
|
||||
# load skeleton if service is new
|
||||
if service.get('template_name'):
|
||||
# if there are more of the same servie, we will create an
|
||||
# ou
|
||||
self.load_skeleton(provider, service['service-id'],
|
||||
service['template_name'])
|
||||
self.load_skeleton(provider, service['service-id'], service['template_name'])
|
||||
service['$done'] = True
|
||||
|
||||
if all(service.get('$done') for service in services):
|
||||
|
@ -249,17 +253,14 @@ class Command(hobo_deploy.Command):
|
|||
sp_url = service['saml-sp-metadata-url']
|
||||
self.stderr.write(self.style.WARNING('Error registering %s: %s\n' % (sp_url, last_error)))
|
||||
|
||||
def load_skeleton(self, provider, service_id, template_name,
|
||||
create_ou=False):
|
||||
def load_skeleton(self, provider, service_id, template_name, create_ou=False):
|
||||
if not getattr(settings, 'HOBO_SKELETONS_DIR', None):
|
||||
self.logger.debug('no skeleton: no HOBO_SKELETONS_DIR setting')
|
||||
return
|
||||
# ex.: /var/lib/authentic2-multitenant/skeletons/communes/wcs/
|
||||
skeleton_dir = os.path.join(settings.HOBO_SKELETONS_DIR, template_name,
|
||||
service_id)
|
||||
skeleton_dir = os.path.join(settings.HOBO_SKELETONS_DIR, template_name, service_id)
|
||||
if not os.path.exists(skeleton_dir):
|
||||
self.logger.debug('no skeleton: skeleton dir %r does not exist',
|
||||
skeleton_dir)
|
||||
self.logger.debug('no skeleton: skeleton dir %r does not exist', skeleton_dir)
|
||||
return
|
||||
self.load_skeleton_roles(skeleton_dir, provider)
|
||||
|
||||
|
@ -267,8 +268,7 @@ class Command(hobo_deploy.Command):
|
|||
'''Load default roles based on a template'''
|
||||
roles_filename = os.path.join(skeleton_dir, 'roles.json')
|
||||
if not os.path.exists(roles_filename):
|
||||
self.logger.debug('no skeleton roles: roles file %r does not '
|
||||
'exist', roles_filename)
|
||||
self.logger.debug('no skeleton roles: roles file %r does not ' 'exist', roles_filename)
|
||||
return
|
||||
Role = get_role_model()
|
||||
if Role.objects.filter(ou=provider.ou).exclude(slug__startswith='_').exists():
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import time
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from django_rbac.utils import get_role_model, get_ou_model
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management.base import BaseCommand
|
||||
from django_rbac.utils import get_ou_model, get_role_model
|
||||
|
||||
from hobo.agent.authentic2.provisionning import Provisionning
|
||||
|
||||
|
@ -26,7 +25,13 @@ class Command(BaseCommand):
|
|||
self.provision_roles(engine, ous)
|
||||
|
||||
if options['users']:
|
||||
self.provision_users(engine, ous, batch_size=options['batch_size'], batch_sleep=options['batch_sleep'], verbosity=options['verbosity'])
|
||||
self.provision_users(
|
||||
engine,
|
||||
ous,
|
||||
batch_size=options['batch_size'],
|
||||
batch_sleep=options['batch_sleep'],
|
||||
verbosity=options['verbosity'],
|
||||
)
|
||||
if self.verbosity > 0:
|
||||
self.stdout.write('Done.')
|
||||
|
||||
|
@ -47,7 +52,10 @@ class Command(BaseCommand):
|
|||
users = list(qs[:batch_size])
|
||||
while users:
|
||||
if verbosity > 0:
|
||||
self.stdout.write(' batch provisionning %d users and sleeping for %d seconds' % (len(users), batch_sleep))
|
||||
self.stdout.write(
|
||||
' batch provisionning %d users and sleeping for %d seconds'
|
||||
% (len(users), batch_sleep)
|
||||
)
|
||||
engine.notify_users(ous, users)
|
||||
users = list(qs.filter(id__gt=users[-1].pk)[:batch_size])
|
||||
if users:
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
from itertools import chain, islice
|
||||
import json
|
||||
from django.utils.six.moves.urllib.parse import urljoin
|
||||
import threading
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import threading
|
||||
from itertools import chain, islice
|
||||
|
||||
import requests
|
||||
from authentic2.a2_rbac.models import RoleAttribute
|
||||
from authentic2.models import AttributeValue
|
||||
from authentic2.saml.models import LibertyProvider
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import connection
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.six.moves.urllib.parse import urljoin
|
||||
from django_rbac.utils import get_ou_model, get_role_model, get_role_parenting_model
|
||||
|
||||
from django_rbac.utils import get_role_model, get_ou_model, get_role_parenting_model
|
||||
from hobo.agent.common import notify_agents
|
||||
from hobo.signature import sign_url
|
||||
from authentic2.saml.models import LibertyProvider
|
||||
from authentic2.a2_rbac.models import RoleAttribute
|
||||
from authentic2.models import AttributeValue
|
||||
|
||||
User = get_user_model()
|
||||
Role = get_role_model()
|
||||
|
@ -44,10 +44,12 @@ class Provisionning(threading.local):
|
|||
self.stack = []
|
||||
|
||||
def start(self):
|
||||
self.stack.append({
|
||||
'saved': {},
|
||||
'deleted': {},
|
||||
})
|
||||
self.stack.append(
|
||||
{
|
||||
'saved': {},
|
||||
'deleted': {},
|
||||
}
|
||||
)
|
||||
|
||||
def clear(self):
|
||||
self.stack = []
|
||||
|
@ -101,8 +103,11 @@ class Provisionning(threading.local):
|
|||
allowed_technical_roles_prefixes = getattr(settings, 'HOBO_PROVISION_ROLE_PREFIXES', []) or []
|
||||
|
||||
if mode == 'provision':
|
||||
users = (User.objects.filter(id__in=[u.id for u in users])
|
||||
.select_related('ou').prefetch_related('attribute_values__attribute'))
|
||||
users = (
|
||||
User.objects.filter(id__in=[u.id for u in users])
|
||||
.select_related('ou')
|
||||
.prefetch_related('attribute_values__attribute')
|
||||
)
|
||||
else:
|
||||
self.resolve_ou(users, ous)
|
||||
|
||||
|
@ -112,32 +117,44 @@ class Provisionning(threading.local):
|
|||
ous.setdefault(ou, set()).add(user)
|
||||
|
||||
def is_forbidden_technical_role(role):
|
||||
return role.slug.startswith('_') and not role.slug.startswith(tuple(allowed_technical_roles_prefixes))
|
||||
return role.slug.startswith('_') and not role.slug.startswith(
|
||||
tuple(allowed_technical_roles_prefixes)
|
||||
)
|
||||
|
||||
issuer = force_text(self.get_entity_id())
|
||||
if mode == 'provision':
|
||||
|
||||
def user_to_json(ou, service, user, user_roles):
|
||||
from authentic2.api_views import BaseUserSerializer
|
||||
|
||||
data = {}
|
||||
# filter user's roles visible by the service's ou
|
||||
roles = [role for role in user_roles.get(user.id, [])
|
||||
if (not is_forbidden_technical_role(role)
|
||||
and (role.ou_id is None or (ou and role.ou_id == ou.id)))]
|
||||
data.update({
|
||||
'uuid': user.uuid,
|
||||
'username': user.username,
|
||||
'first_name': user.first_name,
|
||||
'last_name': user.last_name,
|
||||
'email': user.email,
|
||||
'is_active': user.is_active,
|
||||
'roles': [
|
||||
{
|
||||
'uuid': role.uuid,
|
||||
'name': role.name,
|
||||
'slug': role.slug,
|
||||
} for role in roles],
|
||||
})
|
||||
roles = [
|
||||
role
|
||||
for role in user_roles.get(user.id, [])
|
||||
if (
|
||||
not is_forbidden_technical_role(role)
|
||||
and (role.ou_id is None or (ou and role.ou_id == ou.id))
|
||||
)
|
||||
]
|
||||
data.update(
|
||||
{
|
||||
'uuid': user.uuid,
|
||||
'username': user.username,
|
||||
'first_name': user.first_name,
|
||||
'last_name': user.last_name,
|
||||
'email': user.email,
|
||||
'is_active': user.is_active,
|
||||
'roles': [
|
||||
{
|
||||
'uuid': role.uuid,
|
||||
'name': role.name,
|
||||
'slug': role.slug,
|
||||
}
|
||||
for role in roles
|
||||
],
|
||||
}
|
||||
)
|
||||
data.update(BaseUserSerializer(user).data)
|
||||
# check if user is superuser through a role
|
||||
role_is_superuser = False
|
||||
|
@ -150,13 +167,16 @@ class Provisionning(threading.local):
|
|||
role_is_superuser = True
|
||||
data['is_superuser'] = user.is_superuser or role_is_superuser
|
||||
return data
|
||||
|
||||
# Find roles giving a superuser attribute
|
||||
# If there is any role of this kind, we do one provisionning message for each user and
|
||||
# each service.
|
||||
roles_with_attributes = (Role.objects.filter(members__in=users)
|
||||
.parents(include_self=True)
|
||||
.filter(attributes__name='is_superuser')
|
||||
.exists())
|
||||
roles_with_attributes = (
|
||||
Role.objects.filter(members__in=users)
|
||||
.parents(include_self=True)
|
||||
.filter(attributes__name='is_superuser')
|
||||
.exists()
|
||||
)
|
||||
|
||||
all_roles = Role.objects.all().prefetch_related('attributes')
|
||||
roles = dict((r.id, r) for r in all_roles)
|
||||
|
@ -184,56 +204,73 @@ class Provisionning(threading.local):
|
|||
batched_users = list(batched_users)
|
||||
for user in batched_users:
|
||||
logger.info('provisionning user %s to %s', user, audience)
|
||||
self.notify_agents({
|
||||
'@type': 'provision',
|
||||
'issuer': issuer,
|
||||
'audience': [audience],
|
||||
'full': False,
|
||||
'objects': {
|
||||
'@type': 'user',
|
||||
'data': [user_to_json(ou, service, user, user_roles) for user in batched_users],
|
||||
self.notify_agents(
|
||||
{
|
||||
'@type': 'provision',
|
||||
'issuer': issuer,
|
||||
'audience': [audience],
|
||||
'full': False,
|
||||
'objects': {
|
||||
'@type': 'user',
|
||||
'data': [
|
||||
user_to_json(ou, service, user, user_roles)
|
||||
for user in batched_users
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
else:
|
||||
for ou, users in ous.items():
|
||||
audience = [a for service, a in self.get_audience(ou)]
|
||||
if not audience:
|
||||
continue
|
||||
logger.info(u'provisionning users %s to %s', u', '.join(
|
||||
map(force_text, users)), u', '.join(audience))
|
||||
self.notify_agents({
|
||||
'@type': 'provision',
|
||||
'issuer': issuer,
|
||||
'audience': audience,
|
||||
'full': False,
|
||||
'objects': {
|
||||
'@type': 'user',
|
||||
'data': [user_to_json(ou, None, user, user_roles) for user in users],
|
||||
logger.info(
|
||||
u'provisionning users %s to %s',
|
||||
u', '.join(map(force_text, users)),
|
||||
u', '.join(audience),
|
||||
)
|
||||
self.notify_agents(
|
||||
{
|
||||
'@type': 'provision',
|
||||
'issuer': issuer,
|
||||
'audience': audience,
|
||||
'full': False,
|
||||
'objects': {
|
||||
'@type': 'user',
|
||||
'data': [user_to_json(ou, None, user, user_roles) for user in users],
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
elif users:
|
||||
audience = [audience for ou in ous.keys()
|
||||
for s, audience in self.get_audience(ou)]
|
||||
logger.info(u'deprovisionning users %s from %s', u', '.join(
|
||||
map(force_text, users)), u', '.join(audience))
|
||||
self.notify_agents({
|
||||
'@type': 'deprovision',
|
||||
'issuer': issuer,
|
||||
'audience': audience,
|
||||
'full': False,
|
||||
'objects': {
|
||||
'@type': 'user',
|
||||
'data': [{
|
||||
'uuid': user.uuid,
|
||||
} for user in users]
|
||||
audience = [audience for ou in ous.keys() for s, audience in self.get_audience(ou)]
|
||||
logger.info(
|
||||
u'deprovisionning users %s from %s', u', '.join(map(force_text, users)), u', '.join(audience)
|
||||
)
|
||||
self.notify_agents(
|
||||
{
|
||||
'@type': 'deprovision',
|
||||
'issuer': issuer,
|
||||
'audience': audience,
|
||||
'full': False,
|
||||
'objects': {
|
||||
'@type': 'user',
|
||||
'data': [
|
||||
{
|
||||
'uuid': user.uuid,
|
||||
}
|
||||
for user in users
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
def notify_roles(self, ous, roles, mode='provision', full=False):
|
||||
allowed_technical_roles_prefixes = getattr(settings, 'HOBO_PROVISION_ROLE_PREFIXES', []) or []
|
||||
|
||||
def is_forbidden_technical_role(role):
|
||||
return role.slug.startswith('_') and not role.slug.startswith(tuple(allowed_technical_roles_prefixes))
|
||||
return role.slug.startswith('_') and not role.slug.startswith(
|
||||
tuple(allowed_technical_roles_prefixes)
|
||||
)
|
||||
|
||||
roles = set([role for role in roles if not is_forbidden_technical_role(role)])
|
||||
if mode == 'provision':
|
||||
|
@ -258,26 +295,30 @@ class Provisionning(threading.local):
|
|||
'details': role.details,
|
||||
'emails': role.emails,
|
||||
'emails_to_members': role.emails_to_members,
|
||||
} for role in roles
|
||||
}
|
||||
for role in roles
|
||||
]
|
||||
else:
|
||||
data = [
|
||||
{
|
||||
'uuid': role.uuid,
|
||||
} for role in roles
|
||||
}
|
||||
for role in roles
|
||||
]
|
||||
|
||||
audience = [entity_id for service, entity_id in self.get_audience(ou)]
|
||||
logger.info(u'%sning roles %s to %s', mode, roles, audience)
|
||||
self.notify_agents({
|
||||
'@type': mode,
|
||||
'audience': audience,
|
||||
'full': full,
|
||||
'objects': {
|
||||
'@type': 'role',
|
||||
'data': data,
|
||||
self.notify_agents(
|
||||
{
|
||||
'@type': mode,
|
||||
'audience': audience,
|
||||
'full': full,
|
||||
'objects': {
|
||||
'@type': 'role',
|
||||
'data': data,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
global_roles = set(ous.get(None, []))
|
||||
for ou, ou_roles in ous.items():
|
||||
|
@ -289,17 +330,18 @@ class Provisionning(threading.local):
|
|||
# - we are not in a tenant
|
||||
# - provsionning is disabled
|
||||
# - there is nothing to do
|
||||
if (not hasattr(connection, 'tenant') or not connection.tenant or not
|
||||
hasattr(connection.tenant, 'domain_url')):
|
||||
if (
|
||||
not hasattr(connection, 'tenant')
|
||||
or not connection.tenant
|
||||
or not hasattr(connection.tenant, 'domain_url')
|
||||
):
|
||||
return
|
||||
if not getattr(settings, 'HOBO_ROLE_EXPORT', True):
|
||||
return
|
||||
if not (saved or deleted):
|
||||
return
|
||||
|
||||
t = threading.Thread(
|
||||
target=self.do_provision,
|
||||
kwargs={'saved': saved, 'deleted': deleted})
|
||||
t = threading.Thread(target=self.do_provision, kwargs={'saved': saved, 'deleted': deleted})
|
||||
t.start()
|
||||
self.threads.add(t)
|
||||
|
||||
|
@ -341,8 +383,7 @@ class Provisionning(threading.local):
|
|||
role.emails_to_members = True
|
||||
role.details = u''
|
||||
for attribute in role.attributes.all():
|
||||
if (attribute.name in ('emails', 'emails_to_members', 'details')
|
||||
and attribute.kind == 'json'):
|
||||
if attribute.name in ('emails', 'emails_to_members', 'details') and attribute.kind == 'json':
|
||||
setattr(role, attribute.name, json.loads(attribute.value))
|
||||
|
||||
def get_entity_id(self):
|
||||
|
@ -452,7 +493,8 @@ class Provisionning(threading.local):
|
|||
try:
|
||||
response = requests.put(
|
||||
sign_url(service['provisionning-url'] + '?orig=%s' % service['orig'], service['secret']),
|
||||
json=data)
|
||||
json=data,
|
||||
)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
logger.error(u'error provisionning to %s (%s)', audience, e)
|
||||
|
|
|
@ -16,16 +16,15 @@
|
|||
|
||||
import json
|
||||
|
||||
from authentic2.a2_rbac.models import Role, RoleAttribute
|
||||
from authentic2.manager.forms import RoleEditForm
|
||||
from authentic2.validators import EmailValidator
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import six
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from authentic2.a2_rbac.models import RoleAttribute, Role
|
||||
from authentic2.validators import EmailValidator
|
||||
from authentic2.manager.forms import RoleEditForm
|
||||
|
||||
|
||||
class ListValidator(object):
|
||||
def __init__(self, item_validator):
|
||||
|
@ -36,8 +35,7 @@ class ListValidator(object):
|
|||
try:
|
||||
self.item_validator(item)
|
||||
except ValidationError as e:
|
||||
raise ValidationError(
|
||||
_('Item {0} is invalid: {1}').format(i, e.args[0]))
|
||||
raise ValidationError(_('Item {0} is invalid: {1}').format(i, e.args[0]))
|
||||
|
||||
|
||||
class CommaSeparatedInput(forms.TextInput):
|
||||
|
@ -52,8 +50,7 @@ class CommaSeparatedInput(forms.TextInput):
|
|||
class CommaSeparatedCharField(forms.Field):
|
||||
widget = CommaSeparatedInput
|
||||
|
||||
def __init__(self, dedup=True, max_length=None, min_length=None, *args,
|
||||
**kwargs):
|
||||
def __init__(self, dedup=True, max_length=None, min_length=None, *args, **kwargs):
|
||||
self.dedup = dedup
|
||||
self.max_length = max_length
|
||||
self.min_length = min_length
|
||||
|
@ -80,24 +77,23 @@ class CommaSeparatedCharField(forms.Field):
|
|||
|
||||
|
||||
class RoleForm(RoleEditForm):
|
||||
details = forms.CharField(label=_('Role details (frontoffice)'),
|
||||
widget=forms.Textarea, initial='',
|
||||
required=False)
|
||||
emails = CommaSeparatedCharField(label=_('Emails'),
|
||||
item_validators=[EmailValidator()],
|
||||
required=False,
|
||||
help_text=_('Emails must be separated by commas.'))
|
||||
emails_to_members = forms.BooleanField(required=False,
|
||||
initial=True,
|
||||
label=_('Emails to members'))
|
||||
details = forms.CharField(
|
||||
label=_('Role details (frontoffice)'), widget=forms.Textarea, initial='', required=False
|
||||
)
|
||||
emails = CommaSeparatedCharField(
|
||||
label=_('Emails'),
|
||||
item_validators=[EmailValidator()],
|
||||
required=False,
|
||||
help_text=_('Emails must be separated by commas.'),
|
||||
)
|
||||
emails_to_members = forms.BooleanField(required=False, initial=True, label=_('Emails to members'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
instance = kwargs.get('instance')
|
||||
if instance:
|
||||
fields = [x.name for x in Role._meta.get_fields()]
|
||||
initial = kwargs.setdefault('initial', {})
|
||||
role_attributes = RoleAttribute.objects.filter(role=instance,
|
||||
kind='json')
|
||||
role_attributes = RoleAttribute.objects.filter(role=instance, kind='json')
|
||||
for role_attribute in role_attributes:
|
||||
if role_attribute.name in fields:
|
||||
continue
|
||||
|
@ -113,8 +109,8 @@ class RoleForm(RoleEditForm):
|
|||
continue
|
||||
value = json.dumps(self.cleaned_data[field])
|
||||
ra, created = RoleAttribute.objects.get_or_create(
|
||||
role=instance, name=field, kind='json',
|
||||
defaults={'value': value})
|
||||
role=instance, name=field, kind='json', defaults={'value': value}
|
||||
)
|
||||
if not created and ra.value != value:
|
||||
ra.value = value
|
||||
ra.save()
|
||||
|
|
|
@ -15,10 +15,9 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from rest_framework import permissions, serializers, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
from . import provisionning
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ComboAgentConfig(AppConfig):
|
||||
name = 'hobo.agent.combo'
|
||||
label = 'combo_agent'
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
from hobo.agent.common.management.commands import import_template
|
||||
|
||||
|
||||
class Command(import_template.Command):
|
||||
def handle(self, *args, **kwargs):
|
||||
try:
|
||||
|
@ -24,6 +25,5 @@ class Command(import_template.Command):
|
|||
except import_template.UnknownTemplateError:
|
||||
# ignore errors if template name contains portal-user or portal-agent as
|
||||
# those names do not actually require an existing file to work.
|
||||
if not any(name in kwargs.get('template_name')
|
||||
for name in ('portal-user', 'portal-agent')):
|
||||
if not any(name in kwargs.get('template_name') for name in ('portal-user', 'portal-agent')):
|
||||
raise
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
from celery import Celery
|
||||
from kombu.common import Broadcast
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
from kombu.common import Broadcast
|
||||
|
||||
|
||||
def notify_agents(data):
|
||||
|
@ -20,11 +19,10 @@ def notify_agents(data):
|
|||
CELERY_TASK_SERIALIZER='json',
|
||||
CELERY_ACCEPT_CONTENT=['json'],
|
||||
CELERY_RESULT_SERIALIZER='json',
|
||||
CELERY_QUEUES=(Broadcast('broadcast_tasks'), ),
|
||||
CELERY_QUEUES=(Broadcast('broadcast_tasks'),),
|
||||
CELERY_EVENT_QUEUE_TTL=10,
|
||||
)
|
||||
# see called method in hobo.agent.worker.celery
|
||||
app.send_task('hobo-notify',
|
||||
(notification,),
|
||||
expires=settings.BROKER_TASK_EXPIRES,
|
||||
queue='broadcast_tasks')
|
||||
app.send_task(
|
||||
'hobo-notify', (notification,), expires=settings.BROKER_TASK_EXPIRES, queue='broadcast_tasks'
|
||||
)
|
||||
|
|
|
@ -2,30 +2,28 @@ from __future__ import print_function
|
|||
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management import call_command, get_commands
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.six.moves.urllib import parse as urlparse
|
||||
|
||||
from tenant_schemas.utils import tenant_context
|
||||
|
||||
from hobo.multitenant.middleware import TenantMiddleware, TenantNotFound
|
||||
from hobo.theme.utils import get_theme
|
||||
|
||||
|
||||
KEY_SIZE = 2048
|
||||
DAYS = 3652
|
||||
|
||||
|
||||
def replace_file(path, content):
|
||||
dirname = os.path.dirname(path)
|
||||
fd, temp = tempfile.mkstemp(dir=dirname,
|
||||
prefix='.tmp-'+os.path.basename(path)+'-')
|
||||
fd, temp = tempfile.mkstemp(dir=dirname, prefix='.tmp-' + os.path.basename(path) + '-')
|
||||
f = os.fdopen(fd, 'w')
|
||||
f.write(force_text(content))
|
||||
f.flush()
|
||||
|
@ -42,12 +40,12 @@ class Command(BaseCommand):
|
|||
def add_arguments(self, parser):
|
||||
parser.add_argument('base_url', metavar='BASE_URL', nargs='?', type=str)
|
||||
parser.add_argument('json_filename', metavar='JSON_FILENAME', nargs='?', type=str)
|
||||
parser.add_argument('--ignore-timestamp', dest='ignore_timestamp',
|
||||
action="store_true", default=False)
|
||||
parser.add_argument('--ignore-timestamp', dest='ignore_timestamp', action="store_true", default=False)
|
||||
parser.add_argument('--redeploy', action="store_true", default=False)
|
||||
|
||||
def handle(self, base_url=None, json_filename=None, ignore_timestamp=None,
|
||||
redeploy=None, *args, **kwargs):
|
||||
def handle(
|
||||
self, base_url=None, json_filename=None, ignore_timestamp=None, redeploy=None, *args, **kwargs
|
||||
):
|
||||
self.redeploy = redeploy
|
||||
if redeploy:
|
||||
for tenant in TenantMiddleware.get_tenants():
|
||||
|
@ -107,7 +105,6 @@ class Command(BaseCommand):
|
|||
self.configure_template(hobo_environment, tenant)
|
||||
|
||||
def generate_saml_keys(self, tenant, prefix=''):
|
||||
|
||||
def openssl(*args):
|
||||
with open('/dev/null', 'w') as dev_null:
|
||||
subprocess.check_call(['openssl'] + list(args), stdout=dev_null, stderr=dev_null)
|
||||
|
@ -119,9 +116,23 @@ class Command(BaseCommand):
|
|||
if os.path.exists(key_file) and os.path.exists(cert_file):
|
||||
return
|
||||
|
||||
openssl('req', '-x509', '-sha256', '-newkey', 'rsa:%s' % KEY_SIZE, '-nodes',
|
||||
'-keyout', key_file, '-out', cert_file, '-batch',
|
||||
'-subj', '/CN=%s' % tenant.domain_url[:60], '-days', str(DAYS))
|
||||
openssl(
|
||||
'req',
|
||||
'-x509',
|
||||
'-sha256',
|
||||
'-newkey',
|
||||
'rsa:%s' % KEY_SIZE,
|
||||
'-nodes',
|
||||
'-keyout',
|
||||
key_file,
|
||||
'-out',
|
||||
cert_file,
|
||||
'-batch',
|
||||
'-subj',
|
||||
'/CN=%s' % tenant.domain_url[:60],
|
||||
'-days',
|
||||
str(DAYS),
|
||||
)
|
||||
|
||||
def configure_service_provider(self, hobo_environment, tenant):
|
||||
# configure authentication against identity provider
|
||||
|
@ -135,8 +146,9 @@ class Command(BaseCommand):
|
|||
continue
|
||||
if response.status_code != 200:
|
||||
continue
|
||||
tenant_idp_metadata = os.path.join(tenant.get_directory(),
|
||||
'idp-metadata-%s.xml' % service.get('id'))
|
||||
tenant_idp_metadata = os.path.join(
|
||||
tenant.get_directory(), 'idp-metadata-%s.xml' % service.get('id')
|
||||
)
|
||||
replace_file(tenant_idp_metadata, response.text)
|
||||
# break now, only a single IdP is supported
|
||||
break
|
||||
|
@ -161,8 +173,7 @@ class Command(BaseCommand):
|
|||
|
||||
tenant_dir = tenant.get_directory()
|
||||
theme_dir = os.path.join(tenant_dir, 'theme')
|
||||
target_dir = os.path.join(settings.THEMES_DIRECTORY,
|
||||
theme.get('module'))
|
||||
target_dir = os.path.join(settings.THEMES_DIRECTORY, theme.get('module'))
|
||||
|
||||
def atomic_symlink(src, dst):
|
||||
if os.path.exists(dst) and os.readlink(dst) == src:
|
||||
|
@ -175,8 +186,9 @@ class Command(BaseCommand):
|
|||
atomic_symlink(target_dir, theme_dir)
|
||||
|
||||
for part in ('static', 'templates'):
|
||||
if not os.path.islink(os.path.join(tenant_dir, part)) and \
|
||||
os.path.isdir(os.path.join(tenant_dir, part)):
|
||||
if not os.path.islink(os.path.join(tenant_dir, part)) and os.path.isdir(
|
||||
os.path.join(tenant_dir, part)
|
||||
):
|
||||
try:
|
||||
os.rmdir(os.path.join(tenant_dir, part))
|
||||
except OSError:
|
||||
|
@ -188,8 +200,7 @@ class Command(BaseCommand):
|
|||
except OSError:
|
||||
pass
|
||||
else:
|
||||
target_dir = os.path.join(settings.THEMES_DIRECTORY,
|
||||
theme.get('overlay'), part)
|
||||
target_dir = os.path.join(settings.THEMES_DIRECTORY, theme.get('overlay'), part)
|
||||
atomic_symlink(target_dir, os.path.join(tenant_dir, part))
|
||||
|
||||
def configure_template(self, hobo_environment, tenant):
|
||||
|
|
|
@ -19,10 +19,9 @@ import os
|
|||
import sys
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from tenant_schemas.utils import tenant_context
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
from hobo.provisionning.utils import NotificationProcessing, TryAgain
|
||||
|
||||
|
||||
|
@ -50,8 +49,7 @@ class Command(BaseCommand, NotificationProcessing):
|
|||
|
||||
@classmethod
|
||||
def process_notification(cls, tenant, notification):
|
||||
assert cls.check_valid_notification(notification), \
|
||||
'invalid notification'
|
||||
assert cls.check_valid_notification(notification), 'invalid notification'
|
||||
service = tenant.get_service()
|
||||
action = notification['@type']
|
||||
audience = notification['audience']
|
||||
|
@ -64,7 +62,9 @@ class Command(BaseCommand, NotificationProcessing):
|
|||
object_type = notification['objects']['@type']
|
||||
for i in range(20):
|
||||
try:
|
||||
getattr(cls, 'provision_' + object_type)(issuer, action, notification['objects']['data'], full=full)
|
||||
getattr(cls, 'provision_' + object_type)(
|
||||
issuer, action, notification['objects']['data'], full=full
|
||||
)
|
||||
except TryAgain:
|
||||
continue
|
||||
break
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management import call_command, get_commands
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
class UnknownTemplateError(CommandError):
|
||||
|
@ -26,7 +26,6 @@ class UnknownTemplateError(CommandError):
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('template_name', type=str)
|
||||
parser.add_argument('--basepath', type=str)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,12 +14,21 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Role',
|
||||
fields=[
|
||||
('group_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='auth.Group', on_delete=models.CASCADE)),
|
||||
(
|
||||
'group_ptr',
|
||||
models.OneToOneField(
|
||||
parent_link=True,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to='auth.Group',
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
('uuid', models.CharField(max_length=32)),
|
||||
('description', models.TextField()),
|
||||
],
|
||||
options={
|
||||
},
|
||||
options={},
|
||||
bases=('auth.group',),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
|
@ -16,7 +16,9 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='role',
|
||||
name='emails',
|
||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), default=list, size=None),
|
||||
field=django.contrib.postgres.fields.ArrayField(
|
||||
base_field=models.CharField(max_length=128), default=list, size=None
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='role',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib.auth.models import Group
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
class Role(Group):
|
||||
uuid = models.CharField(max_length=32)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HoboAgentConfig(AppConfig):
|
||||
name = 'hobo.agent.hobo'
|
||||
label = 'hobo_agent'
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.timezone import now
|
||||
|
||||
from tenant_schemas.utils import tenant_context
|
||||
|
||||
from hobo.agent.common.management.commands import hobo_deploy
|
||||
|
@ -58,16 +57,21 @@ class Command(hobo_deploy.Command):
|
|||
# this is the primary hobo, receiving a notification
|
||||
# because something changed in secondary hobo environment.
|
||||
# mark services with ou-label/ou-slug
|
||||
secondary_hobo = [x for x in services if x['service-id'] == 'hobo' and not x.get('secondary')][0]
|
||||
secondary_hobo = [
|
||||
x for x in services if x['service-id'] == 'hobo' and not x.get('secondary')
|
||||
][0]
|
||||
slug_prefix = '_%s_' % hobo_environment['variables']['ou-slug']
|
||||
|
||||
service_slug = '%s%s' % (slug_prefix, service_dict['slug'])
|
||||
service, created = service_klass.objects.get_or_create(
|
||||
base_url=service_dict['base_url'],
|
||||
secondary=True,
|
||||
defaults={'title': service_dict['title'],
|
||||
'slug': service_slug,
|
||||
'secret_key': service_dict.get('secret_key')})
|
||||
base_url=service_dict['base_url'],
|
||||
secondary=True,
|
||||
defaults={
|
||||
'title': service_dict['title'],
|
||||
'slug': service_slug,
|
||||
'secret_key': service_dict.get('secret_key'),
|
||||
},
|
||||
)
|
||||
service.title = service_dict['title']
|
||||
service.secret_key = service_dict.get('secret_key')
|
||||
service.template_name = service_dict.get('template_name') or ''
|
||||
|
@ -82,13 +86,13 @@ class Command(hobo_deploy.Command):
|
|||
# organizational unit
|
||||
service_type = ContentType.objects.get_for_model(service_klass)
|
||||
variable, created = Variable.objects.get_or_create(
|
||||
name='ou-label', auto=True,
|
||||
service_type=service_type, service_pk=service.id)
|
||||
name='ou-label', auto=True, service_type=service_type, service_pk=service.id
|
||||
)
|
||||
variable.value = hobo_environment['variables']['ou-label']
|
||||
variable.save()
|
||||
variable, created = Variable.objects.get_or_create(
|
||||
name='ou-slug', auto=True,
|
||||
service_type=service_type, service_pk=service.id)
|
||||
name='ou-slug', auto=True, service_type=service_type, service_pk=service.id
|
||||
)
|
||||
variable.value = hobo_environment['variables']['ou-slug']
|
||||
variable.save()
|
||||
|
||||
|
@ -97,11 +101,13 @@ class Command(hobo_deploy.Command):
|
|||
# global variables so they can later be used to identify
|
||||
# this "organizational unit".
|
||||
variable, created = Variable.objects.get_or_create(
|
||||
name='ou-label', auto=True, service_pk=None)
|
||||
name='ou-label', auto=True, service_pk=None
|
||||
)
|
||||
variable.value = me['title']
|
||||
variable.save()
|
||||
variable, created = Variable.objects.get_or_create(
|
||||
name='ou-slug', auto=True, service_pk=None)
|
||||
name='ou-slug', auto=True, service_pk=None
|
||||
)
|
||||
variable.value = me['slug']
|
||||
variable.save()
|
||||
|
||||
|
@ -111,8 +117,8 @@ class Command(hobo_deploy.Command):
|
|||
changes = False
|
||||
for i, field in enumerate(hobo_environment['profile']['fields']):
|
||||
attribute, created = AttributeDefinition.objects.get_or_create(
|
||||
name=field['name'],
|
||||
defaults={'label': field['label']})
|
||||
name=field['name'], defaults={'label': field['label']}
|
||||
)
|
||||
if created:
|
||||
changes = True
|
||||
for k, v in field.items():
|
||||
|
|
|
@ -18,20 +18,21 @@ from __future__ import absolute_import
|
|||
|
||||
from celery import Celery
|
||||
from kombu.common import Broadcast
|
||||
from . import settings
|
||||
from . import services
|
||||
|
||||
from . import services, settings
|
||||
|
||||
app = Celery('hobo', broker=settings.BROKER_URL)
|
||||
app.conf.update(
|
||||
CELERY_TASK_SERIALIZER='json',
|
||||
CELERY_ACCEPT_CONTENT=['json'],
|
||||
CELERY_RESULT_SERIALIZER='json',
|
||||
CELERY_QUEUES=(Broadcast('broadcast_tasks'), )
|
||||
CELERY_QUEUES=(Broadcast('broadcast_tasks'),),
|
||||
)
|
||||
|
||||
if hasattr(settings, 'CELERY_SETTINGS'):
|
||||
app.conf.update(settings.CELERY_SETTINGS)
|
||||
|
||||
|
||||
@app.task(name='hobo-deploy', bind=True)
|
||||
def deploy(self, environment):
|
||||
services.deploy(environment)
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import fnmatch
|
||||
import json
|
||||
import multiprocessing
|
||||
|
@ -79,8 +80,9 @@ class BaseService(object):
|
|||
if not os.path.exists(self.service_manage_try_cmd):
|
||||
return
|
||||
cmd = self.service_manage_cmd + ' hobo_deploy ' + self.base_url + ' -'
|
||||
cmd_process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
cmd_process = subprocess.Popen(
|
||||
cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = cmd_process.communicate(input=force_bytes(json.dumps(environment)))
|
||||
if cmd_process.returncode != 0:
|
||||
raise RuntimeError('command "%s" failed: %r %r' % (cmd, stdout, stderr))
|
||||
|
@ -100,8 +102,9 @@ class BaseService(object):
|
|||
return
|
||||
cmd = cls.service_manage_cmd + ' hobo_notify -'
|
||||
try:
|
||||
cmd_process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
cmd_process = subprocess.Popen(
|
||||
cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
except OSError:
|
||||
return
|
||||
stdout, stderr = cmd_process.communicate(input=force_bytes(json.dumps(data)))
|
||||
|
@ -209,6 +212,7 @@ def deploy(environment):
|
|||
pool = multiprocessing.pool.ThreadPool()
|
||||
list(pool.imap_unordered(lambda x: x.execute(environment), services))
|
||||
|
||||
|
||||
def notify(data):
|
||||
services = []
|
||||
for klassname, service in globals().items():
|
||||
|
|
|
@ -48,7 +48,8 @@ BIJOE_TENANTS_DIRECTORY = '/var/lib/bijoe/tenants'
|
|||
HOBO_TENANTS_DIRECTORY = '/var/lib/hobo/tenants'
|
||||
|
||||
|
||||
local_settings_file = os.environ.get('HOBO_AGENT_SETTINGS_FILE',
|
||||
os.path.join(os.path.dirname(__file__), 'local_settings.py'))
|
||||
local_settings_file = os.environ.get(
|
||||
'HOBO_AGENT_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
|
||||
)
|
||||
if os.path.exists(local_settings_file):
|
||||
exec(open(local_settings_file).read())
|
||||
|
|
|
@ -2,10 +2,10 @@ import datetime
|
|||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import threading
|
||||
import os
|
||||
import threading
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.template import Template
|
||||
|
@ -66,14 +66,16 @@ class RemoteTemplate(object):
|
|||
|
||||
@property
|
||||
def cache_key(self):
|
||||
return hashlib.md5(urlparse.urlunparse(
|
||||
urlparse.urlparse(self.source)[:3] + ('', '', '')).encode('ascii')).hexdigest()
|
||||
return hashlib.md5(
|
||||
urlparse.urlunparse(urlparse.urlparse(self.source)[:3] + ('', '', '')).encode('ascii')
|
||||
).hexdigest()
|
||||
|
||||
def get_template(self):
|
||||
item = self.get_cached_item()
|
||||
if 'hobo.environment' in settings.INSTALLED_APPS:
|
||||
from hobo.multitenant.settings_loaders import TemplateVars
|
||||
from hobo.deploy.utils import get_hobo_json
|
||||
from hobo.multitenant.settings_loaders import TemplateVars
|
||||
|
||||
context = TemplateVars.get_hobo_json_variables(get_hobo_json())
|
||||
if 'portal_url' not in context:
|
||||
# serve a minimalistic template if no portal have been
|
||||
|
@ -126,15 +128,13 @@ class RemoteTemplate(object):
|
|||
page_cache[page_redirect_url] = r.text
|
||||
|
||||
expiry_time = datetime.datetime.now() + datetime.timedelta(seconds=CACHE_REFRESH_TIMEOUT)
|
||||
cache.set(self.PAGE_CACHE_KEY,
|
||||
(page_cache, expiry_time),
|
||||
2592000) # bypass cache level expiration time
|
||||
cache.set(
|
||||
self.PAGE_CACHE_KEY, (page_cache, expiry_time), 2592000
|
||||
) # bypass cache level expiration time
|
||||
|
||||
def cache(self, template_body):
|
||||
expiry_time = datetime.datetime.now() + datetime.timedelta(seconds=CACHE_REFRESH_TIMEOUT)
|
||||
cache.set(self.cache_key,
|
||||
(template_body, expiry_time),
|
||||
2592000) # bypass cache level expiration time
|
||||
cache.set(self.cache_key, (template_body, expiry_time), 2592000) # bypass cache level expiration time
|
||||
|
||||
|
||||
def theme_base(request):
|
||||
|
@ -152,18 +152,20 @@ def theme_base(request):
|
|||
# template by combo, it will be taken from template_vars and will
|
||||
# default to theme.html.
|
||||
source = request.build_absolute_uri()
|
||||
return {'theme_base': RemoteTemplate(source).get_template,
|
||||
'theme_404': RemoteTemplate('404').get_template,
|
||||
'theme_base_filename': template_vars(request).get('theme_base_filename') or 'theme.html'
|
||||
}
|
||||
return {
|
||||
'theme_base': RemoteTemplate(source).get_template,
|
||||
'theme_404': RemoteTemplate('404').get_template,
|
||||
'theme_base_filename': template_vars(request).get('theme_base_filename') or 'theme.html',
|
||||
}
|
||||
|
||||
|
||||
def portal_agent_url(request):
|
||||
def get_portal_agent_url():
|
||||
portal_agents = []
|
||||
if 'authentic2' in settings.INSTALLED_APPS:
|
||||
portal_agents = [x for x in settings.KNOWN_SERVICES.get('combo', {}).values()
|
||||
if x.get('is-portal-agent')]
|
||||
portal_agents = [
|
||||
x for x in settings.KNOWN_SERVICES.get('combo', {}).values() if x.get('is-portal-agent')
|
||||
]
|
||||
if len(portal_agents) > 1 and request.user and request.user.is_authenticated and request.user.ou_id:
|
||||
ou_slug = request.user.ou.slug
|
||||
for portal_agent in portal_agents:
|
||||
|
@ -179,8 +181,9 @@ def portal_agent_url(request):
|
|||
def hobo_json(request):
|
||||
# this context processor gives Hobo itself variables that would be defined
|
||||
# from settings loaders based on hobo.json.
|
||||
from hobo.multitenant.settings_loaders import TemplateVars
|
||||
from hobo.deploy.utils import get_hobo_json
|
||||
from hobo.multitenant.settings_loaders import TemplateVars
|
||||
|
||||
context = TemplateVars.get_hobo_json_variables(get_hobo_json())
|
||||
context['manager_homepage_url'] = context.get(settings.HOBO_MANAGER_HOMEPAGE_URL_VAR)
|
||||
return context
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django import forms
|
||||
from django.core.validators import validate_ipv46_address
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_ipv46_address
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
|
@ -48,10 +48,9 @@ class MultipleIPAddressField(forms.CharField):
|
|||
|
||||
|
||||
class SettingsForm(forms.Form):
|
||||
debug_log = forms.BooleanField(
|
||||
required=False,
|
||||
label=_('Enable Debug Logs'))
|
||||
debug_log = forms.BooleanField(required=False, label=_('Enable Debug Logs'))
|
||||
debug_ips = MultipleIPAddressField(
|
||||
label=_('Internal IP addresses'),
|
||||
required=False,
|
||||
help_text=_('List of IP addresses for which to enable debugging'))
|
||||
help_text=_('List of IP addresses for which to enable debugging'),
|
||||
)
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import FormView
|
||||
from django.utils.functional import cached_property
|
||||
from django.views.generic import FormView
|
||||
|
||||
from hobo.environment.utils import get_setting_variable
|
||||
|
||||
|
@ -69,4 +69,5 @@ class HomeView(FormView):
|
|||
self.debug_ips_variable.save()
|
||||
return super(HomeView, self).form_valid(form)
|
||||
|
||||
|
||||
home = HomeView.as_view()
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
import django.apps
|
||||
|
||||
|
||||
class AppConfig(django.apps.AppConfig):
|
||||
name = 'hobo.deploy'
|
||||
|
||||
def ready(self):
|
||||
from . import signals
|
||||
|
||||
|
||||
default_app_config = 'hobo.deploy.AppConfig'
|
||||
|
|
|
@ -17,22 +17,22 @@
|
|||
import threading
|
||||
|
||||
from celery import Celery
|
||||
from kombu.common import Broadcast
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.signals import request_finished, request_started
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.db.models.signals import post_delete, post_save
|
||||
from django.dispatch import receiver
|
||||
from kombu.common import Broadcast
|
||||
|
||||
from hobo.environment.models import AVAILABLE_SERVICES, Variable
|
||||
from hobo.profile.models import AttributeDefinition
|
||||
|
||||
from .utils import get_hobo_json
|
||||
|
||||
from hobo.environment.models import Variable, AVAILABLE_SERVICES
|
||||
from hobo.profile.models import AttributeDefinition
|
||||
|
||||
|
||||
class Local(threading.local):
|
||||
MUST_NOTIFY = False
|
||||
|
||||
|
||||
tls = Local()
|
||||
|
||||
|
||||
|
@ -59,10 +59,9 @@ def notify_agents(sender, **kwargs):
|
|||
CELERY_TASK_SERIALIZER='json',
|
||||
CELERY_ACCEPT_CONTENT=['json'],
|
||||
CELERY_RESULT_SERIALIZER='json',
|
||||
CELERY_QUEUES=(Broadcast('broadcast_tasks'), )
|
||||
CELERY_QUEUES=(Broadcast('broadcast_tasks'),),
|
||||
)
|
||||
# see called method in hobo.agent.worker.celery
|
||||
app.send_task('hobo-deploy',
|
||||
(get_hobo_json(),),
|
||||
expires=settings.BROKER_TASK_EXPIRES,
|
||||
queue='broadcast_tasks')
|
||||
app.send_task(
|
||||
'hobo-deploy', (get_hobo_json(),), expires=settings.BROKER_TASK_EXPIRES, queue='broadcast_tasks'
|
||||
)
|
||||
|
|
|
@ -20,7 +20,7 @@ import time
|
|||
from django.contrib.auth.models import User
|
||||
from django.db.models import Max
|
||||
|
||||
from hobo.environment.models import Variable, AVAILABLE_SERVICES
|
||||
from hobo.environment.models import AVAILABLE_SERVICES, Variable
|
||||
from hobo.environment.utils import get_installed_services_dict
|
||||
from hobo.profile.models import AttributeDefinition
|
||||
from hobo.profile.utils import get_profile_dict
|
||||
|
@ -32,11 +32,11 @@ def get_hobo_json():
|
|||
# include the list of admin users so an agent can create them when
|
||||
# deploying a service (according to its policy)
|
||||
users = []
|
||||
for user in User.objects.filter(is_superuser=True,
|
||||
is_active=True, password__isnull=False).exclude(password=''):
|
||||
for user in User.objects.filter(is_superuser=True, is_active=True, password__isnull=False).exclude(
|
||||
password=''
|
||||
):
|
||||
user_dict = {}
|
||||
for attribute in ('username', 'first_name', 'last_name', 'email',
|
||||
'password'):
|
||||
for attribute in ('username', 'first_name', 'last_name', 'email', 'password'):
|
||||
user_dict[attribute] = getattr(user, attribute)
|
||||
users.append(user_dict)
|
||||
|
||||
|
@ -48,12 +48,11 @@ def get_hobo_json():
|
|||
# set a timestamp
|
||||
timestamp = None
|
||||
for klass in [AttributeDefinition, Variable] + AVAILABLE_SERVICES:
|
||||
ts = klass.objects.all().aggregate(Max('last_update_timestamp')
|
||||
).get('last_update_timestamp__max')
|
||||
ts = klass.objects.all().aggregate(Max('last_update_timestamp')).get('last_update_timestamp__max')
|
||||
if timestamp is None or (ts and ts > timestamp):
|
||||
timestamp = ts
|
||||
if timestamp is None:
|
||||
timestamp = datetime.datetime.now()
|
||||
|
||||
hobo_json['timestamp'] = str(time.mktime(timestamp.timetuple()) + timestamp.microsecond/1e6)
|
||||
hobo_json['timestamp'] = str(time.mktime(timestamp.timetuple()) + timestamp.microsecond / 1e6)
|
||||
return hobo_json
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.conf import settings
|
||||
import django.core.mail.backends.smtp
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class EmailBackend(django.core.mail.backends.smtp.EmailBackend):
|
||||
|
@ -32,7 +32,9 @@ class EmailBackend(django.core.mail.backends.smtp.EmailBackend):
|
|||
try:
|
||||
url = settings.TEMPLATE_VARS['email_abuse_report_url']
|
||||
if url:
|
||||
email_message.extra_headers['X-Report-Abuse'] = 'Please report abuse for this email here: %s' % url
|
||||
email_message.extra_headers['X-Report-Abuse'] = (
|
||||
'Please report abuse for this email here: %s' % url
|
||||
)
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
return super()._send(email_message)
|
||||
|
|
|
@ -28,19 +28,26 @@ class ValidEmailField(forms.EmailField):
|
|||
|
||||
|
||||
class EmailsForm(forms.Form):
|
||||
default_from_email = ValidEmailField(label=_('Default From'),
|
||||
help_text=_('Sender email address'))
|
||||
email_sender_name = forms.CharField(label=_('Sender Name'), required=False,
|
||||
help_text=_('Custom sender name (defaults to global title)'))
|
||||
global_email_prefix = forms.CharField(label=_('Prefix'), required=False,
|
||||
help_text=_('Custom prefix for emails subject (defaults to global title)'))
|
||||
email_signature = forms.CharField(label=_('Signature'), required=False,
|
||||
widget=forms.Textarea)
|
||||
default_from_email = ValidEmailField(label=_('Default From'), help_text=_('Sender email address'))
|
||||
email_sender_name = forms.CharField(
|
||||
label=_('Sender Name'), required=False, help_text=_('Custom sender name (defaults to global title)')
|
||||
)
|
||||
global_email_prefix = forms.CharField(
|
||||
label=_('Prefix'),
|
||||
required=False,
|
||||
help_text=_('Custom prefix for emails subject (defaults to global title)'),
|
||||
)
|
||||
email_signature = forms.CharField(label=_('Signature'), required=False, widget=forms.Textarea)
|
||||
email_unsubscribe_info_url = forms.CharField(
|
||||
label=_('URL with informations about emails sent by the platform (List-Unsubscribe header)'), required=False,
|
||||
help_text=_('It should contain details such as what emails are sent and how not to receive them.'))
|
||||
email_abuse_report_url = forms.CharField(label=_('URL for email abuse reports (X-Report-Abuse header)'), required=False,
|
||||
help_text=_('It should contain a form to submit email abuse reports'))
|
||||
label=_('URL with informations about emails sent by the platform (List-Unsubscribe header)'),
|
||||
required=False,
|
||||
help_text=_('It should contain details such as what emails are sent and how not to receive them.'),
|
||||
)
|
||||
email_abuse_report_url = forms.CharField(
|
||||
label=_('URL for email abuse reports (X-Report-Abuse header)'),
|
||||
required=False,
|
||||
help_text=_('It should contain a form to submit email abuse reports'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EmailsForm, self).__init__(*args, **kwargs)
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import dns.resolver
|
||||
import smtplib
|
||||
import socket
|
||||
|
||||
import dns.resolver
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.encoding import force_bytes
|
||||
|
@ -29,7 +29,9 @@ def validate_email_address(value):
|
|||
return
|
||||
email_domain = value.split('@')[-1]
|
||||
try:
|
||||
mx_server = sorted(dns.resolver.query(email_domain, 'MX'), key=lambda rdata: rdata.preference)[0].exchange.to_text()
|
||||
mx_server = sorted(dns.resolver.query(email_domain, 'MX'), key=lambda rdata: rdata.preference)[
|
||||
0
|
||||
].exchange.to_text()
|
||||
except dns.exception.DNSException as e:
|
||||
raise ValidationError(_('Error: %s') % str(e))
|
||||
try:
|
||||
|
@ -44,7 +46,9 @@ def validate_email_address(value):
|
|||
finally:
|
||||
smtp.close()
|
||||
except (socket.error, IOError, OSError) as e:
|
||||
raise ValidationError(_('Error while connecting to %(server)s: %(msg)s') % {'server': mx_server, 'msg': e})
|
||||
raise ValidationError(
|
||||
_('Error while connecting to %(server)s: %(msg)s') % {'server': mx_server, 'msg': e}
|
||||
)
|
||||
if status // 100 == 5:
|
||||
raise ValidationError(_('Email address not found on %s') % mx_server)
|
||||
|
||||
|
|
|
@ -19,17 +19,22 @@ from django.utils.translation import ugettext as _
|
|||
from django.views.generic import TemplateView
|
||||
|
||||
from hobo.environment.forms import VariablesFormMixin
|
||||
|
||||
from .forms import EmailsForm
|
||||
|
||||
|
||||
class HomeView(VariablesFormMixin, TemplateView):
|
||||
template_name = 'hobo/emails_home.html'
|
||||
variables = ['default_from_email', 'email_sender_name',
|
||||
'global_email_prefix', 'email_signature',
|
||||
'email_unsubscribe_info_url',
|
||||
'email_abuse_report_url']
|
||||
variables = [
|
||||
'default_from_email',
|
||||
'email_sender_name',
|
||||
'global_email_prefix',
|
||||
'email_signature',
|
||||
'email_unsubscribe_info_url',
|
||||
'email_abuse_report_url',
|
||||
]
|
||||
form_class = EmailsForm
|
||||
success_message = _('Emails settings have been updated. '
|
||||
'It will take a few seconds to be effective.')
|
||||
success_message = _('Emails settings have been updated. ' 'It will take a few seconds to be effective.')
|
||||
|
||||
|
||||
home = HomeView.as_view()
|
||||
|
|
|
@ -21,15 +21,16 @@ from django.http import HttpResponseRedirect
|
|||
from django.template.defaultfilters import slugify
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
from .models import (Authentic, Wcs, Passerelle, Variable, Combo, Fargo, Welco,
|
||||
Chrono, BiJoe, Hobo)
|
||||
from .models import Authentic, BiJoe, Chrono, Combo, Fargo, Hobo, Passerelle, Variable, Wcs, Welco
|
||||
from .utils import get_variable
|
||||
from .validators import validate_service_url
|
||||
|
||||
EXCLUDED_FIELDS = ('last_operational_check_timestamp',
|
||||
'last_operational_success_timestamp', 'secret_key',
|
||||
'secondary')
|
||||
EXCLUDED_FIELDS = (
|
||||
'last_operational_check_timestamp',
|
||||
'last_operational_success_timestamp',
|
||||
'secret_key',
|
||||
'secondary',
|
||||
)
|
||||
|
||||
|
||||
class BaseForm(forms.ModelForm):
|
||||
|
@ -46,8 +47,8 @@ class BaseForm(forms.ModelForm):
|
|||
# choice that was selected as an additional, disabled, <select> widget.
|
||||
if self.instance.id and len(choices) > 1:
|
||||
self.fields['template_name_readonly'] = forms.fields.CharField(
|
||||
label=_('Template'), required=False,
|
||||
initial=self.instance.template_name)
|
||||
label=_('Template'), required=False, initial=self.instance.template_name
|
||||
)
|
||||
self.fields['template_name_readonly'].widget = forms.Select(choices=choices)
|
||||
self.fields['template_name_readonly'].widget.attrs['disabled'] = 'disabled'
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.core.management.base import BaseCommand
|
|||
|
||||
from hobo.environment import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Checks if all services are operational'''
|
||||
|
||||
|
@ -19,4 +20,8 @@ class Command(BaseCommand):
|
|||
else:
|
||||
print(self.style.NOTICE('%s is NOT operational' % obj.title))
|
||||
if obj.last_operational_success_timestamp:
|
||||
print(self.style.NOTICE(' last operational success: %s' % obj.last_operational_success_timestamp))
|
||||
print(
|
||||
self.style.NOTICE(
|
||||
' last operational success: %s' % obj.last_operational_success_timestamp
|
||||
)
|
||||
)
|
||||
|
|
|
@ -17,33 +17,44 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import json
|
||||
import os
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import connection
|
||||
from django.db.models import Max
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import six
|
||||
from django.utils.six.moves.urllib import parse as urlparse
|
||||
from django.utils.text import slugify
|
||||
|
||||
from hobo.agent.common.management.commands.hobo_deploy import (
|
||||
Command as HoboDeployCommand)
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
from hobo.environment.models import (AVAILABLE_SERVICES, Authentic, Wcs, Hobo,
|
||||
Passerelle, Combo, Fargo, Welco, Chrono, BiJoe,
|
||||
Variable, AUTO_VARIABLES)
|
||||
from hobo.environment.validators import validate_service_url
|
||||
from hobo.agent.common.management.commands.hobo_deploy import Command as HoboDeployCommand
|
||||
from hobo.deploy.signals import notify_agents
|
||||
from hobo.theme.utils import set_theme
|
||||
from hobo.environment.models import (
|
||||
AUTO_VARIABLES,
|
||||
AVAILABLE_SERVICES,
|
||||
Authentic,
|
||||
BiJoe,
|
||||
Chrono,
|
||||
Combo,
|
||||
Fargo,
|
||||
Hobo,
|
||||
Passerelle,
|
||||
Variable,
|
||||
Wcs,
|
||||
Welco,
|
||||
)
|
||||
from hobo.environment.validators import validate_service_url
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
from hobo.profile.models import AttributeDefinition
|
||||
from hobo.theme.utils import set_theme
|
||||
|
||||
|
||||
def get_domain(url):
|
||||
return urlparse.urlparse(url).netloc.split(':')[0]
|
||||
|
@ -56,8 +67,12 @@ class Command(BaseCommand):
|
|||
def add_arguments(self, parser):
|
||||
parser.add_argument('recipe', metavar='RECIPE', type=str)
|
||||
parser.add_argument(
|
||||
'--timeout', type=int, action='store', default=120,
|
||||
help='set the timeout for the wait_operationals method')
|
||||
'--timeout',
|
||||
type=int,
|
||||
action='store',
|
||||
default=120,
|
||||
help='set the timeout for the wait_operationals method',
|
||||
)
|
||||
parser.add_argument('--permissive', action='store_true', help='ignore integrity checks')
|
||||
|
||||
def handle(self, recipe, *args, **kwargs):
|
||||
|
@ -78,16 +93,16 @@ class Command(BaseCommand):
|
|||
variables = {}
|
||||
steps = []
|
||||
if 'load-variables-from' in recipe:
|
||||
variables.update(json.load(open(
|
||||
os.path.join(os.path.dirname(filename), recipe['load-variables-from']))))
|
||||
variables.update(
|
||||
json.load(open(os.path.join(os.path.dirname(filename), recipe['load-variables-from'])))
|
||||
)
|
||||
variables.update(recipe.get('variables', {}))
|
||||
for step in recipe.get('steps', []):
|
||||
action, action_args = list(step.items())[0]
|
||||
for arg in action_args:
|
||||
if not isinstance(action_args[arg], six.string_types):
|
||||
continue
|
||||
action_args[arg] = string.Template(
|
||||
action_args[arg]).substitute(variables)
|
||||
action_args[arg] = string.Template(action_args[arg]).substitute(variables)
|
||||
if not self.permissive:
|
||||
self.check_action(action, action_args)
|
||||
steps.append((action, action_args))
|
||||
|
@ -137,8 +152,7 @@ class Command(BaseCommand):
|
|||
if time.time() - t0 > timeout:
|
||||
if self.verbosity:
|
||||
sys.stderr.write('\n')
|
||||
raise CommandError('timeout waiting for %s' % ', '.join(
|
||||
[x.base_url for x in services]))
|
||||
raise CommandError('timeout waiting for %s' % ', '.join([x.base_url for x in services]))
|
||||
|
||||
def create_hobo(self, url, primary=False, title=None, slug=None, **kwargs):
|
||||
if connection.get_tenant().schema_name == 'public':
|
||||
|
@ -149,8 +163,7 @@ class Command(BaseCommand):
|
|||
if not primary:
|
||||
if not slug:
|
||||
slug = 'hobo-%s' % slugify(title)
|
||||
self.create_site(Hobo, url, title, slug,
|
||||
template_name='', variables=None)
|
||||
self.create_site(Hobo, url, title, slug, template_name='', variables=None)
|
||||
# deploy and wait for new site
|
||||
notify_agents(None)
|
||||
self.wait_operationals(timeout=self.timeout)
|
||||
|
@ -175,10 +188,10 @@ class Command(BaseCommand):
|
|||
|
||||
connection.set_tenant(tenant)
|
||||
|
||||
def create_superuser(self, username='admin', email='admin@localhost',
|
||||
first_name='Admin', last_name='', password=None):
|
||||
user, created = User.objects.get_or_create(
|
||||
username=username, email=email, is_superuser=True)
|
||||
def create_superuser(
|
||||
self, username='admin', email='admin@localhost', first_name='Admin', last_name='', password=None
|
||||
):
|
||||
user, created = User.objects.get_or_create(username=username, email=email, is_superuser=True)
|
||||
if created:
|
||||
user.first_name = first_name
|
||||
user.last_name = last_name
|
||||
|
@ -193,19 +206,17 @@ class Command(BaseCommand):
|
|||
if slug is None:
|
||||
slug = klass.Extra.service_default_slug
|
||||
obj, must_save = klass.objects.get_or_create(
|
||||
slug=slug,
|
||||
defaults={
|
||||
'title': title,
|
||||
'base_url': base_url,
|
||||
'template_name': template_name
|
||||
})
|
||||
slug=slug, defaults={'title': title, 'base_url': base_url, 'template_name': template_name}
|
||||
)
|
||||
for attr in ('title', 'base_url', 'template_name'):
|
||||
if getattr(obj, attr) != locals().get(attr):
|
||||
setattr(obj, attr, locals().get(attr))
|
||||
must_save = True
|
||||
if must_save:
|
||||
try:
|
||||
obj.full_clean(exclude=['last_operational_success_timestamp', 'last_operational_check_timestamp'])
|
||||
obj.full_clean(
|
||||
exclude=['last_operational_success_timestamp', 'last_operational_check_timestamp']
|
||||
)
|
||||
except ValidationError as e:
|
||||
raise CommandError(str(e))
|
||||
|
||||
|
@ -215,10 +226,12 @@ class Command(BaseCommand):
|
|||
obj_type = ContentType.objects.get_for_model(klass)
|
||||
for variable_name in variables.keys():
|
||||
label = variables[variable_name].get('label')
|
||||
variable, created = Variable.objects.get_or_create(name=variable_name,
|
||||
service_type=obj_type,
|
||||
service_pk=obj.id,
|
||||
defaults={'label': label or variable_name})
|
||||
variable, created = Variable.objects.get_or_create(
|
||||
name=variable_name,
|
||||
service_type=obj_type,
|
||||
service_pk=obj.id,
|
||||
defaults={'label': label or variable_name},
|
||||
)
|
||||
if label:
|
||||
variable.label = label
|
||||
value = variables[variable_name].get('value')
|
||||
|
@ -262,17 +275,16 @@ class Command(BaseCommand):
|
|||
|
||||
def set_theme(self, theme):
|
||||
set_theme(theme)
|
||||
HoboDeployCommand().configure_theme(
|
||||
{'variables': {'theme': theme}},
|
||||
connection.get_tenant())
|
||||
HoboDeployCommand().configure_theme({'variables': {'theme': theme}}, connection.get_tenant())
|
||||
|
||||
def set_variable(self, name, value, label=None, auto=None):
|
||||
if auto is None:
|
||||
auto = bool(name in AUTO_VARIABLES)
|
||||
else:
|
||||
auto = bool(auto)
|
||||
variable, created = Variable.objects.get_or_create(name=name,
|
||||
defaults={'label': label or name, 'auto': auto})
|
||||
variable, created = Variable.objects.get_or_create(
|
||||
name=name, defaults={'label': label or name, 'auto': auto}
|
||||
)
|
||||
if isinstance(value, dict) or isinstance(value, list):
|
||||
value = json.dumps(value)
|
||||
if variable.label != label or variable.value != value or variable.auto != auto or created:
|
||||
|
@ -304,7 +316,8 @@ class Command(BaseCommand):
|
|||
# possible keys in kwargs are: description, required,
|
||||
# asked_on_registration, user_editable, user_visible, kind, order
|
||||
attribute, created = AttributeDefinition.objects.get_or_create(
|
||||
name=name, defaults={'label': label, 'order': 0})
|
||||
name=name, defaults={'label': label, 'order': 0}
|
||||
)
|
||||
kwargs['label'] = label
|
||||
attribute_fields = [x.name for x in AttributeDefinition._meta.fields]
|
||||
for arg in kwargs:
|
||||
|
@ -312,8 +325,7 @@ class Command(BaseCommand):
|
|||
setattr(attribute, arg, kwargs.get(arg))
|
||||
|
||||
if created and not attribute.order:
|
||||
attribute.order = AttributeDefinition.objects.all().aggregate(
|
||||
Max('order')).get('order__max') + 1
|
||||
attribute.order = AttributeDefinition.objects.all().aggregate(Max('order')).get('order__max') + 1
|
||||
attribute.save()
|
||||
|
||||
def cook(self, filename):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Authentic',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField()),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
@ -33,7 +36,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Combo',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField()),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
@ -52,7 +58,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Passerelle',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField()),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
@ -71,21 +80,34 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Variable',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('name', models.CharField(max_length=100, verbose_name='name')),
|
||||
('value', models.TextField(help_text='start with [ or { for a JSON document', verbose_name='value', blank=True)),
|
||||
(
|
||||
'value',
|
||||
models.TextField(
|
||||
help_text='start with [ or { for a JSON document', verbose_name='value', blank=True
|
||||
),
|
||||
),
|
||||
('service_pk', models.PositiveIntegerField(null=True)),
|
||||
('last_update_timestamp', models.DateTimeField(auto_now=True, null=True)),
|
||||
('service_type', models.ForeignKey(to='contenttypes.ContentType', null=True, on_delete=models.CASCADE)),
|
||||
(
|
||||
'service_type',
|
||||
models.ForeignKey(to='contenttypes.ContentType', null=True, on_delete=models.CASCADE),
|
||||
),
|
||||
],
|
||||
options={
|
||||
},
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Wcs',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField()),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Fargo',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField(verbose_name='Slug')),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Welco',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField(verbose_name='Slug')),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -13,23 +13,43 @@ class Migration(migrations.Migration):
|
|||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='authentic',
|
||||
options={'ordering': ['title'], 'verbose_name': 'Authentic Identity Provider', 'verbose_name_plural': 'Authentic Identity Providers'},
|
||||
options={
|
||||
'ordering': ['title'],
|
||||
'verbose_name': 'Authentic Identity Provider',
|
||||
'verbose_name_plural': 'Authentic Identity Providers',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='combo',
|
||||
options={'ordering': ['title'], 'verbose_name': 'Combo Portal', 'verbose_name_plural': 'Combo Portals'},
|
||||
options={
|
||||
'ordering': ['title'],
|
||||
'verbose_name': 'Combo Portal',
|
||||
'verbose_name_plural': 'Combo Portals',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='fargo',
|
||||
options={'ordering': ['title'], 'verbose_name': 'Fargo document box', 'verbose_name_plural': 'Fargo document box'},
|
||||
options={
|
||||
'ordering': ['title'],
|
||||
'verbose_name': 'Fargo document box',
|
||||
'verbose_name_plural': 'Fargo document box',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='passerelle',
|
||||
options={'ordering': ['title'], 'verbose_name': 'Passerelle', 'verbose_name_plural': 'Passerelle'},
|
||||
options={
|
||||
'ordering': ['title'],
|
||||
'verbose_name': 'Passerelle',
|
||||
'verbose_name_plural': 'Passerelle',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='wcs',
|
||||
options={'ordering': ['title'], 'verbose_name': 'w.c.s. Web Forms', 'verbose_name_plural': 'w.c.s. Web Forms'},
|
||||
options={
|
||||
'ordering': ['title'],
|
||||
'verbose_name': 'w.c.s. Web Forms',
|
||||
'verbose_name_plural': 'w.c.s. Web Forms',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='welco',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='MandayeJS',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField(verbose_name='Slug')),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Chrono',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField(verbose_name='Slug')),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,18 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='mandayejs',
|
||||
name='site_app',
|
||||
field=models.CharField(default=b'mandayejs.applications.Test', max_length=128, verbose_name='App Settings', choices=[(b'mandayejs.applications.Test', b'Test'), (b'mandayejs.applications.Duonet', b'Duonet'), (b'mandayejs.applications.Sezhame', b'Sezhame'), (b'mandayejs.applications.Archimed', b'Archimed'), (b'mandayejs.applications.ImuseFamilyMontpellier', b'ImuseFamilyMontpellier')]),
|
||||
field=models.CharField(
|
||||
default=b'mandayejs.applications.Test',
|
||||
max_length=128,
|
||||
verbose_name='App Settings',
|
||||
choices=[
|
||||
(b'mandayejs.applications.Test', b'Test'),
|
||||
(b'mandayejs.applications.Duonet', b'Duonet'),
|
||||
(b'mandayejs.applications.Sezhame', b'Sezhame'),
|
||||
(b'mandayejs.applications.Archimed', b'Archimed'),
|
||||
(b'mandayejs.applications.ImuseFamilyMontpellier', b'ImuseFamilyMontpellier'),
|
||||
],
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,18 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='mandayejs',
|
||||
name='site_app',
|
||||
field=models.CharField(default=b'mandayejs.applications.Test', max_length=128, verbose_name='Site Application', choices=[(b'mandayejs.applications.Test', b'Test'), (b'mandayejs.applications.Duonet', b'Duonet'), (b'mandayejs.applications.Sezhame', b'Sezhame'), (b'mandayejs.applications.Archimed', b'Archimed'), (b'mandayejs.applications.ImuseFamilyMontpellier', b'ImuseFamilyMontpellier')]),
|
||||
field=models.CharField(
|
||||
default=b'mandayejs.applications.Test',
|
||||
max_length=128,
|
||||
verbose_name='Site Application',
|
||||
choices=[
|
||||
(b'mandayejs.applications.Test', b'Test'),
|
||||
(b'mandayejs.applications.Duonet', b'Duonet'),
|
||||
(b'mandayejs.applications.Sezhame', b'Sezhame'),
|
||||
(b'mandayejs.applications.Archimed', b'Archimed'),
|
||||
(b'mandayejs.applications.ImuseFamilyMontpellier', b'ImuseFamilyMontpellier'),
|
||||
],
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Piwik',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField(verbose_name='Slug')),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Corbo',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField(verbose_name='Slug')),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,7 +14,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='BiJoe',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField(verbose_name='Slug')),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
|
|
@ -14,7 +14,10 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Hobo',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||
('slug', models.SlugField(verbose_name='Slug')),
|
||||
('base_url', models.CharField(max_length=200, verbose_name='Base URL')),
|
||||
|
|
|
@ -14,27 +14,26 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
import datetime
|
||||
import json
|
||||
import random
|
||||
import requests
|
||||
import re
|
||||
import socket
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import URLValidator
|
||||
from django.db import models
|
||||
from django.utils import six
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.six.moves.urllib.parse import urlparse
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import six
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import URLValidator
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
|
||||
from .utils import Zone, get_installed_services
|
||||
|
||||
|
@ -43,25 +42,25 @@ FLOAT_RE = re.compile(r'^\s*[0-9]+\.[0-9]+\s*')
|
|||
|
||||
|
||||
AUTO_VARIABLES = (
|
||||
'default_from_email',
|
||||
'global_email_prefix',
|
||||
'email_signature',
|
||||
'email_sender_name',
|
||||
'global_title',
|
||||
'robots_txt',
|
||||
'meta_description',
|
||||
'meta_keywords',
|
||||
'sms_url',
|
||||
'sms_sender',
|
||||
'default_from_email',
|
||||
'global_email_prefix',
|
||||
'email_signature',
|
||||
'email_sender_name',
|
||||
'global_title',
|
||||
'robots_txt',
|
||||
'meta_description',
|
||||
'meta_keywords',
|
||||
'sms_url',
|
||||
'sms_sender',
|
||||
)
|
||||
|
||||
|
||||
class Variable(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name=_('name'))
|
||||
label = models.CharField(max_length=100, blank=True, verbose_name=_('label'))
|
||||
value = models.TextField(
|
||||
verbose_name=_('value'),
|
||||
blank=True,
|
||||
help_text=_('start with [ or { for a JSON document'))
|
||||
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, on_delete=models.CASCADE)
|
||||
service_pk = models.PositiveIntegerField(null=True)
|
||||
|
@ -75,9 +74,8 @@ class Variable(models.Model):
|
|||
|
||||
def _parse_value_as_json(self):
|
||||
if self.value and (
|
||||
self.value[0] in '{['
|
||||
or self.value in ('true', 'false', 'null')
|
||||
or FLOAT_RE.match(self.value)):
|
||||
self.value[0] in '{[' or self.value in ('true', 'false', 'null') or FLOAT_RE.match(self.value)
|
||||
):
|
||||
try:
|
||||
return json.loads(self.value)
|
||||
except ValueError:
|
||||
|
@ -114,18 +112,17 @@ class ServiceBase(models.Model):
|
|||
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')
|
||||
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)
|
||||
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()
|
||||
|
@ -154,8 +151,13 @@ class ServiceBase(models.Model):
|
|||
return (self.last_operational_check_timestamp - self.last_update_timestamp) < two_minutes
|
||||
|
||||
def as_dict(self):
|
||||
as_dict = dict([(x, y) for (x, y) in self.__dict__.items()
|
||||
if isinstance(y, six.integer_types + six.string_types)])
|
||||
as_dict = dict(
|
||||
[
|
||||
(x, y)
|
||||
for (x, y) in self.__dict__.items()
|
||||
if isinstance(y, six.integer_types + six.string_types)
|
||||
]
|
||||
)
|
||||
as_dict['base_url'] = self.get_base_url_path()
|
||||
as_dict['service-id'] = self.Extra.service_id
|
||||
as_dict['service-label'] = force_text(self.Extra.service_label)
|
||||
|
@ -192,7 +194,7 @@ class ServiceBase(models.Model):
|
|||
if not self.secret_key:
|
||||
self.secret_key = get_random_string(50, SECRET_CHARS)
|
||||
|
||||
is_new = (self.id is None)
|
||||
is_new = self.id is None
|
||||
super(ServiceBase, self).save(*args, **kwargs)
|
||||
|
||||
if is_new and settings.SERVICE_EXTRA_VARIABLES:
|
||||
|
@ -247,7 +249,7 @@ class ServiceBase(models.Model):
|
|||
if not self.is_resolvable():
|
||||
return False
|
||||
r = requests.get(self.get_admin_zones()[0].href, timeout=5, verify=False, allow_redirects=False)
|
||||
return (r.status_code >= 200 and r.status_code < 400)
|
||||
return r.status_code >= 200 and r.status_code < 400
|
||||
|
||||
def security_data(self):
|
||||
security_data = {
|
||||
|
@ -259,7 +261,12 @@ class ServiceBase(models.Model):
|
|||
security_data['level'] = 0
|
||||
resp = requests.get(self.base_url, timeout=5, verify=False, allow_redirects=False)
|
||||
missing_headers = []
|
||||
for header in ('X-Content-Type-Options', 'X-Frame-Options', 'X-XSS-Protection', 'Strict-Transport-Security'):
|
||||
for header in (
|
||||
'X-Content-Type-Options',
|
||||
'X-Frame-Options',
|
||||
'X-XSS-Protection',
|
||||
'Strict-Transport-Security',
|
||||
):
|
||||
if not resp.headers.get(header):
|
||||
missing_headers.append(header)
|
||||
if missing_headers:
|
||||
|
@ -289,9 +296,7 @@ class ServiceBase(models.Model):
|
|||
|
||||
|
||||
class Authentic(ServiceBase):
|
||||
use_as_idp_for_self = models.BooleanField(
|
||||
verbose_name=_('Use as IdP'),
|
||||
default=False)
|
||||
use_as_idp_for_self = models.BooleanField(verbose_name=_('Use as IdP'), default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Authentic Identity Provider')
|
||||
|
@ -354,9 +359,7 @@ class Passerelle(ServiceBase):
|
|||
service_default_slug = 'passerelle'
|
||||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'webservices', self.get_base_url_path() + 'manage/')
|
||||
]
|
||||
return [Zone(self.title, 'webservices', self.get_base_url_path() + 'manage/')]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -377,9 +380,7 @@ class Combo(ServiceBase):
|
|||
service_default_slug = 'portal'
|
||||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'portal', self.get_base_url_path() + 'manage/')
|
||||
]
|
||||
return [Zone(self.title, 'portal', self.get_base_url_path() + 'manage/')]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -400,9 +401,7 @@ class Fargo(ServiceBase):
|
|||
service_default_slug = 'porte-doc'
|
||||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'document-box', self.get_base_url_path() + 'admin/')
|
||||
]
|
||||
return [Zone(self.title, 'document-box', self.get_base_url_path() + 'admin/')]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -419,9 +418,7 @@ class Welco(ServiceBase):
|
|||
service_default_slug = 'courrier'
|
||||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'multichannel-guichet', self.get_base_url_path() + 'admin/')
|
||||
]
|
||||
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/'
|
||||
|
@ -442,9 +439,7 @@ class Chrono(ServiceBase):
|
|||
service_default_slug = 'agendas'
|
||||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'calendar', self.get_base_url_path() + 'manage/')
|
||||
]
|
||||
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/'
|
||||
|
@ -465,9 +460,7 @@ class Hobo(ServiceBase):
|
|||
service_default_slug = 'hobo'
|
||||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'hobo', self.get_base_url_path())
|
||||
]
|
||||
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/'
|
||||
|
@ -488,9 +481,7 @@ class BiJoe(ServiceBase):
|
|||
service_default_slug = 'statistics'
|
||||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'bijoe', self.get_base_url_path())
|
||||
]
|
||||
return [Zone(self.title, 'bijoe', self.get_base_url_path())]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -499,5 +490,4 @@ class BiJoe(ServiceBase):
|
|||
return self.get_base_url_path() + 'manage/menu.json'
|
||||
|
||||
|
||||
AVAILABLE_SERVICES = [Authentic, Wcs, Passerelle, Combo, Fargo, Welco,
|
||||
Chrono, BiJoe, Hobo]
|
||||
AVAILABLE_SERVICES = [Authentic, Wcs, Passerelle, Combo, Fargo, Welco, Chrono, BiJoe, Hobo]
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
from django.urls import reverse_lazy
|
||||
from django.template import Library
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from .. import forms
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
@register.filter(name='as_update_form')
|
||||
def as_update_form(object):
|
||||
return getattr(forms, object.__class__.__name__ + 'Form')(instance=object).as_p()
|
||||
|
||||
|
||||
@register.filter(name='save_url')
|
||||
def save_url(object):
|
||||
return reverse_lazy('save-service', kwargs={
|
||||
'service': object.Extra.service_id,
|
||||
'slug': object.slug})
|
||||
return reverse_lazy('save-service', kwargs={'service': object.Extra.service_id, 'slug': object.slug})
|
||||
|
||||
|
||||
@register.filter(name='delete_url')
|
||||
def delete_url(object):
|
||||
return reverse_lazy('delete-service', kwargs={
|
||||
'service': object.Extra.service_id,
|
||||
'slug': object.slug})
|
||||
return reverse_lazy('delete-service', kwargs={'service': object.Extra.service_id, 'slug': object.slug})
|
||||
|
|
|
@ -21,20 +21,30 @@ from . import views
|
|||
urlpatterns = [
|
||||
url(r'^$', views.HomeView.as_view(), name='environment-home'),
|
||||
url(r'^variables$', views.VariablesView.as_view(), name='environment-variables'),
|
||||
url(r'^new-variable$', views.VariableCreateView.as_view(), name='new-variable',),
|
||||
url(r'^update-variable/(?P<pk>\w+)$', views.VariableUpdateView.as_view(),
|
||||
name='update-variable'),
|
||||
url(r'^delete-variable/(?P<pk>\w+)$', views.VariableDeleteView.as_view(),
|
||||
name='delete-variable'),
|
||||
url(r'^check_operational/(?P<service>\w+)/(?P<slug>[\w-]+)$',
|
||||
views.operational_check_view, name='operational-check'),
|
||||
url(
|
||||
r'^new-variable$',
|
||||
views.VariableCreateView.as_view(),
|
||||
name='new-variable',
|
||||
),
|
||||
url(r'^update-variable/(?P<pk>\w+)$', views.VariableUpdateView.as_view(), name='update-variable'),
|
||||
url(r'^delete-variable/(?P<pk>\w+)$', views.VariableDeleteView.as_view(), name='delete-variable'),
|
||||
url(
|
||||
r'^check_operational/(?P<service>\w+)/(?P<slug>[\w-]+)$',
|
||||
views.operational_check_view,
|
||||
name='operational-check',
|
||||
),
|
||||
url(r'^new-(?P<service>\w+)$', views.ServiceCreateView.as_view(), name='create-service'),
|
||||
url(r'^save-(?P<service>\w+)/(?P<slug>[\w-]+)$', views.ServiceUpdateView.as_view(), name='save-service'),
|
||||
url(r'^delete-(?P<service>\w+)/(?P<slug>[\w-]+)$', views.ServiceDeleteView.as_view(), name='delete-service'),
|
||||
|
||||
url(r'^new-variable-(?P<service>\w+)/(?P<slug>[\w-]+)$',
|
||||
views.VariableCreateView.as_view(), name='new-variable-service',),
|
||||
|
||||
url(
|
||||
r'^delete-(?P<service>\w+)/(?P<slug>[\w-]+)$',
|
||||
views.ServiceDeleteView.as_view(),
|
||||
name='delete-service',
|
||||
),
|
||||
url(
|
||||
r'^new-variable-(?P<service>\w+)/(?P<slug>[\w-]+)$',
|
||||
views.VariableCreateView.as_view(),
|
||||
name='new-variable-service',
|
||||
),
|
||||
url(r'^import/$', views.ImportView.as_view(), name='environment-import'),
|
||||
url(r'^export/$', views.ExportView.as_view(), name='environment-export'),
|
||||
url(r'^debug.json$', views.debug_json, name='debug-json'),
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
import hashlib
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.db import connection, transaction
|
||||
from django.utils.six.moves.urllib.parse import urlparse
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.six.moves.urllib.parse import urlparse
|
||||
|
||||
from hobo.middleware.utils import StoreRequestMiddleware
|
||||
from hobo.multitenant.settings_loaders import KnownServices
|
||||
|
@ -29,6 +29,7 @@ from hobo.profile.utils import get_profile_dict
|
|||
|
||||
def get_installed_services():
|
||||
from .models import AVAILABLE_SERVICES
|
||||
|
||||
installed_services = []
|
||||
for available_service in AVAILABLE_SERVICES:
|
||||
installed_services.extend(available_service.objects.all())
|
||||
|
@ -70,13 +71,14 @@ def get_local_hobo_dict():
|
|||
|
||||
def get_installed_services_dict():
|
||||
from .models import Variable
|
||||
|
||||
hobo_service = []
|
||||
hobo_dict = get_local_hobo_dict()
|
||||
if hobo_dict:
|
||||
hobo_service.append(hobo_dict)
|
||||
return {
|
||||
'services': hobo_service + [x.as_dict() for x in get_installed_services()],
|
||||
'variables': {v.name: v.json for v in Variable.objects.filter(service_pk__isnull=True)}
|
||||
'variables': {v.name: v.json for v in Variable.objects.filter(service_pk__isnull=True)},
|
||||
}
|
||||
|
||||
|
||||
|
@ -93,6 +95,7 @@ class Zone:
|
|||
|
||||
def get_variable(name):
|
||||
from .models import Variable
|
||||
|
||||
try:
|
||||
variable = Variable.objects.get(name=name)
|
||||
except Variable.DoesNotExist:
|
||||
|
@ -102,8 +105,8 @@ def get_variable(name):
|
|||
|
||||
def set_variable(name, value):
|
||||
from .models import Variable
|
||||
variable, created = Variable.objects.get_or_create(
|
||||
name=name, defaults={'auto': True})
|
||||
|
||||
variable, created = Variable.objects.get_or_create(name=name, defaults={'auto': True})
|
||||
variable.value = value
|
||||
variable.save()
|
||||
|
||||
|
@ -123,7 +126,7 @@ def create_base_url(hostname, service):
|
|||
|
||||
|
||||
def get_setting_variable(setting_name, label=None, service=None):
|
||||
from .models import Variable, ContentType
|
||||
from .models import ContentType, Variable
|
||||
|
||||
kwargs = {
|
||||
'name': 'SETTING_' + setting_name,
|
||||
|
@ -138,11 +141,7 @@ def get_setting_variable(setting_name, label=None, service=None):
|
|||
try:
|
||||
variable = Variable.objects.get(**kwargs)
|
||||
except Variable.DoesNotExist:
|
||||
variable = Variable(
|
||||
name='SETTING_' + setting_name,
|
||||
label=label or '',
|
||||
service=service,
|
||||
auto=True)
|
||||
variable = Variable(name='SETTING_' + setting_name, label=label or '', service=service, auto=True)
|
||||
|
||||
return variable
|
||||
|
||||
|
@ -152,20 +151,17 @@ def export_parameters():
|
|||
|
||||
variables = []
|
||||
for var in Variable.objects.filter(service_pk__isnull=True):
|
||||
variables.append({
|
||||
'name': var.name,
|
||||
'label': var.label,
|
||||
'value': var.value,
|
||||
'auto': var.auto})
|
||||
variables.append({'name': var.name, 'label': var.label, 'value': var.value, 'auto': var.auto})
|
||||
parameters = {'variables': variables}
|
||||
parameters.update(get_profile_dict())
|
||||
return parameters
|
||||
|
||||
|
||||
def import_parameters(parameters):
|
||||
from .models import Variable
|
||||
from hobo.profile.models import AttributeDefinition
|
||||
|
||||
from .models import Variable
|
||||
|
||||
with transaction.atomic():
|
||||
for variables in parameters.get('variables', []):
|
||||
obj, created = Variable.objects.get_or_create(name=variables['name'])
|
||||
|
|
|
@ -11,11 +11,9 @@ def validate_service_url(url):
|
|||
raise ValidationError(
|
||||
_('Error: %(netloc)s is not resolvable in URL %(url)s'),
|
||||
code='not-resolvable',
|
||||
params={'netloc': urlparse.urlsplit(url).netloc, 'url': url}
|
||||
params={'netloc': urlparse.urlsplit(url).netloc, 'url': url},
|
||||
)
|
||||
if not service.has_valid_certificate():
|
||||
raise ValidationError(
|
||||
_('Error: no valid certificate for %(url)s'),
|
||||
code='invalid-certificate',
|
||||
params={'url': url}
|
||||
_('Error: no valid certificate for %(url)s'), code='invalid-certificate', params={'url': url}
|
||||
)
|
||||
|
|
|
@ -19,17 +19,17 @@ import string
|
|||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse_lazy
|
||||
from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonResponse
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import View
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
|
||||
from .models import Variable, AVAILABLE_SERVICES
|
||||
from . import forms, utils
|
||||
from .models import AVAILABLE_SERVICES, Variable
|
||||
|
||||
|
||||
class AvailableService(object):
|
||||
|
@ -43,9 +43,7 @@ class HomeView(TemplateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(HomeView, self).get_context_data(**kwargs)
|
||||
context['available_services'] = [
|
||||
AvailableService(x) for x in AVAILABLE_SERVICES if x.is_enabled()
|
||||
]
|
||||
context['available_services'] = [AvailableService(x) for x in AVAILABLE_SERVICES if x.is_enabled()]
|
||||
context['installed_services'] = [x for x in utils.get_installed_services() if not x.secondary]
|
||||
return context
|
||||
|
||||
|
@ -78,16 +76,12 @@ class VariableCreateView(CreateView):
|
|||
if form.service:
|
||||
service_kwargs = {
|
||||
'service_pk': form.service.id,
|
||||
'service_type': ContentType.objects.get_for_model(form.service)
|
||||
'service_type': ContentType.objects.get_for_model(form.service),
|
||||
}
|
||||
else:
|
||||
service_kwargs = {
|
||||
'service_pk__isnull': True
|
||||
}
|
||||
service_kwargs = {'service_pk__isnull': True}
|
||||
try:
|
||||
self.object = Variable.objects.get(
|
||||
name=form.instance.name,
|
||||
**service_kwargs)
|
||||
self.object = Variable.objects.get(name=form.instance.name, **service_kwargs)
|
||||
except Variable.DoesNotExist:
|
||||
self.object = form.save()
|
||||
else:
|
||||
|
@ -133,8 +127,8 @@ class ServiceCreateView(CreateView):
|
|||
def get_initial(self):
|
||||
initial = super(ServiceCreateView, self).get_initial()
|
||||
initial['base_url'] = utils.create_base_url(
|
||||
self.request.build_absolute_uri(),
|
||||
self.model.Extra.service_default_slug)
|
||||
self.request.build_absolute_uri(), self.model.Extra.service_default_slug
|
||||
)
|
||||
initial['slug'] = self.model.Extra.service_default_slug
|
||||
return initial
|
||||
|
||||
|
@ -209,8 +203,7 @@ class ImportView(FormView):
|
|||
|
||||
def form_valid(self, form):
|
||||
try:
|
||||
parameters_json = json.loads(
|
||||
force_text(self.request.FILES['parameters_json'].read()))
|
||||
parameters_json = json.loads(force_text(self.request.FILES['parameters_json'].read()))
|
||||
except ValueError:
|
||||
form.add_error('parameters_json', _('File is not in the expected JSON format.'))
|
||||
return self.form_invalid(form)
|
||||
|
@ -220,7 +213,6 @@ class ImportView(FormView):
|
|||
|
||||
|
||||
class ExportView(View):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
response = JsonResponse(utils.export_parameters(), json_dumps_params={'indent': 2})
|
||||
response['Content-Disposition'] = 'attachment; filename="hobo-export.json"'
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from django.forms import ModelForm
|
||||
from django.conf import settings
|
||||
from django.forms import ModelForm
|
||||
|
||||
if 'tenant_schemas' in settings.INSTALLED_APPS:
|
||||
from tenant_schemas.utils import get_tenant_model
|
||||
else:
|
||||
get_tenant_model = lambda: None
|
||||
|
||||
|
||||
class HoboForm(ModelForm):
|
||||
|
||||
required_css_class = 'required'
|
||||
|
@ -26,8 +27,8 @@ class HoboForm(ModelForm):
|
|||
model = get_tenant_model()
|
||||
fields = ['domain_url', 'schema_name']
|
||||
|
||||
class HoboUpdateForm(ModelForm):
|
||||
|
||||
class HoboUpdateForm(ModelForm):
|
||||
class Meta:
|
||||
model = get_tenant_model()
|
||||
fields = ['domain_url']
|
||||
|
|
|
@ -20,19 +20,21 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
class SettingsForm(forms.Form):
|
||||
platform = forms.ChoiceField(
|
||||
label=_('Platform'),
|
||||
choices=[
|
||||
('prod', _('Production')),
|
||||
('test', _('Integration')),
|
||||
])
|
||||
label=_('Platform'),
|
||||
choices=[
|
||||
('prod', _('Production')),
|
||||
('test', _('Integration')),
|
||||
],
|
||||
)
|
||||
client_id = forms.CharField(
|
||||
label=_('Client ID'),
|
||||
help_text=_('See <a href="https://partenaires.franceconnect.gouv.fr/fcp/fournisseur-service">'
|
||||
'FranceConnect partners site</a> for getting client ID and secret.'),
|
||||
widget=forms.TextInput(attrs={'size': 64}))
|
||||
client_secret = forms.CharField(
|
||||
label=_('Client Secret'),
|
||||
widget=forms.TextInput(attrs={'size': 64}))
|
||||
label=_('Client ID'),
|
||||
help_text=_(
|
||||
'See <a href="https://partenaires.franceconnect.gouv.fr/fcp/fournisseur-service">'
|
||||
'FranceConnect partners site</a> for getting client ID and secret.'
|
||||
),
|
||||
widget=forms.TextInput(attrs={'size': 64}),
|
||||
)
|
||||
client_secret = forms.CharField(label=_('Client Secret'), widget=forms.TextInput(attrs={'size': 64}))
|
||||
scopes = forms.MultipleChoiceField(
|
||||
label=_('Scopes'),
|
||||
choices=[
|
||||
|
@ -51,7 +53,8 @@ class SettingsForm(forms.Form):
|
|||
('birth', _('birth profile (birth)')),
|
||||
],
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text=_('These scopes will be requested in addition to openid'))
|
||||
help_text=_('These scopes will be requested in addition to openid'),
|
||||
)
|
||||
|
||||
|
||||
class EnableForm(forms.Form):
|
||||
|
|
|
@ -21,13 +21,13 @@ from django.views.generic import FormView
|
|||
|
||||
from hobo.environment.models import Authentic
|
||||
from hobo.environment.utils import get_setting_variable
|
||||
from .forms import SettingsForm, EnableForm
|
||||
|
||||
from .forms import EnableForm, SettingsForm
|
||||
|
||||
|
||||
def get_variable(setting_name):
|
||||
return get_setting_variable(
|
||||
setting_name,
|
||||
service=Authentic.objects.get(secondary=False))
|
||||
return get_setting_variable(setting_name, service=Authentic.objects.get(secondary=False))
|
||||
|
||||
|
||||
PLATFORMS = {
|
||||
'test': {
|
||||
|
@ -41,7 +41,7 @@ PLATFORMS = {
|
|||
'A2_FC_TOKEN_URL': 'https://app.franceconnect.gouv.fr/api/v1/token',
|
||||
'A2_FC_USERINFO_URL': 'https://app.franceconnect.gouv.fr/api/v1/userinfo',
|
||||
'A2_FC_LOGOUT_URL': 'https://app.franceconnect.gouv.fr/api/v1/logout',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -83,26 +83,28 @@ class HomeView(FormView):
|
|||
variable.save()
|
||||
|
||||
variable = get_variable('A2_FC_USER_INFO_MAPPINGS')
|
||||
variable.value = json.dumps({
|
||||
'last_name': {
|
||||
'ref': 'family_name',
|
||||
'verified': True,
|
||||
},
|
||||
'first_name': {
|
||||
'ref': 'given_name',
|
||||
'verified': True,
|
||||
},
|
||||
'title': {
|
||||
'ref': 'gender',
|
||||
'translation': 'simple',
|
||||
'translation_simple': {
|
||||
'male': 'Monsieur',
|
||||
'female': 'Madame',
|
||||
variable.value = json.dumps(
|
||||
{
|
||||
'last_name': {
|
||||
'ref': 'family_name',
|
||||
'verified': True,
|
||||
},
|
||||
'verified': True,
|
||||
},
|
||||
'email': 'email',
|
||||
})
|
||||
'first_name': {
|
||||
'ref': 'given_name',
|
||||
'verified': True,
|
||||
},
|
||||
'title': {
|
||||
'ref': 'gender',
|
||||
'translation': 'simple',
|
||||
'translation_simple': {
|
||||
'male': 'Monsieur',
|
||||
'female': 'Madame',
|
||||
},
|
||||
'verified': True,
|
||||
},
|
||||
'email': 'email',
|
||||
}
|
||||
)
|
||||
variable.save()
|
||||
|
||||
variable = get_variable('A2_FC_SCOPES')
|
||||
|
@ -116,6 +118,7 @@ class HomeView(FormView):
|
|||
context['enabled'] = bool(get_variable('A2_FC_ENABLE').json)
|
||||
return context
|
||||
|
||||
|
||||
home = HomeView.as_view()
|
||||
|
||||
|
||||
|
@ -130,6 +133,7 @@ class EnableView(FormView):
|
|||
variable.save()
|
||||
return super(EnableView, self).form_valid(form)
|
||||
|
||||
|
||||
enable = EnableView.as_view()
|
||||
|
||||
|
||||
|
@ -144,4 +148,5 @@ class DisableView(FormView):
|
|||
variable.save()
|
||||
return super(DisableView, self).form_valid(form)
|
||||
|
||||
|
||||
disable = DisableView.as_view()
|
||||
|
|
|
@ -18,14 +18,13 @@
|
|||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with python-systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging as _logging
|
||||
import sys as _sys
|
||||
import traceback as _traceback
|
||||
import logging as _logging
|
||||
from syslog import (LOG_ALERT, LOG_CRIT, LOG_ERR,
|
||||
LOG_WARNING, LOG_INFO, LOG_DEBUG)
|
||||
from syslog import LOG_ALERT, LOG_CRIT, LOG_DEBUG, LOG_ERR, LOG_INFO, LOG_WARNING
|
||||
|
||||
from systemd._journal import sendv
|
||||
from django.utils import six
|
||||
from systemd._journal import sendv
|
||||
|
||||
_IDENT_CHARACTER = set('ABCDEFGHIJKLMNOPQRTSUVWXYZ_0123456789')
|
||||
|
||||
|
@ -43,9 +42,7 @@ def _make_line(field, value):
|
|||
return field + '=' + str(value)
|
||||
|
||||
|
||||
def send(MESSAGE, MESSAGE_ID=None,
|
||||
CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None,
|
||||
**kwargs):
|
||||
def send(MESSAGE, MESSAGE_ID=None, CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None, **kwargs):
|
||||
r"""Send a message to the journal.
|
||||
|
||||
>>> from systemd import journal
|
||||
|
@ -181,24 +178,32 @@ class JournalHandler(_logging.Handler):
|
|||
# explicit arguments — highest priority
|
||||
for key, value in record.__dict__.items():
|
||||
new_key = key.upper()
|
||||
if new_key in ['PRIORITY', 'LOGGER', 'THREAD_NAME',
|
||||
'PROCESS_NAME', 'CODE_FILE', 'MESSAGE',
|
||||
'CODE_LINE', 'CODE_FUNC']:
|
||||
if new_key in [
|
||||
'PRIORITY',
|
||||
'LOGGER',
|
||||
'THREAD_NAME',
|
||||
'PROCESS_NAME',
|
||||
'CODE_FILE',
|
||||
'MESSAGE',
|
||||
'CODE_LINE',
|
||||
'CODE_FUNC',
|
||||
]:
|
||||
continue
|
||||
if key in ['threadName', 'processName', 'pathname', 'lineno',
|
||||
'funcName']:
|
||||
if key in ['threadName', 'processName', 'pathname', 'lineno', 'funcName']:
|
||||
continue
|
||||
extras[key.upper()] = value
|
||||
|
||||
self.send(msg,
|
||||
PRIORITY=format(pri),
|
||||
LOGGER=record.name,
|
||||
THREAD_NAME=record.threadName,
|
||||
PROCESS_NAME=record.processName,
|
||||
CODE_FILE=record.pathname,
|
||||
CODE_LINE=record.lineno,
|
||||
CODE_FUNC=record.funcName,
|
||||
**extras)
|
||||
self.send(
|
||||
msg,
|
||||
PRIORITY=format(pri),
|
||||
LOGGER=record.name,
|
||||
THREAD_NAME=record.threadName,
|
||||
PROCESS_NAME=record.processName,
|
||||
CODE_FILE=record.pathname,
|
||||
CODE_LINE=record.lineno,
|
||||
CODE_FUNC=record.funcName,
|
||||
**extras,
|
||||
)
|
||||
except Exception:
|
||||
self.handleError(record)
|
||||
|
||||
|
|
|
@ -19,10 +19,10 @@ import datetime
|
|||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import pytz
|
||||
import time
|
||||
import warnings
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
|
||||
|
@ -31,7 +31,9 @@ from hobo.middleware.utils import StoreRequestMiddleware
|
|||
|
||||
class SettingsLogLevel(str):
|
||||
def __new__(cls, value):
|
||||
warnings.warn('SettingsLogLevel is deprecated, use DEBUG_LOG instead.', DeprecationWarning, stacklevel=2)
|
||||
warnings.warn(
|
||||
'SettingsLogLevel is deprecated, use DEBUG_LOG instead.', DeprecationWarning, stacklevel=2
|
||||
)
|
||||
return super(SettingsLogLevel, cls).__new__(value)
|
||||
|
||||
|
||||
|
@ -48,10 +50,10 @@ class RequestContextFilter(logging.Filter):
|
|||
DEFAULT_APPLICATION = 'django'
|
||||
|
||||
def filter(self, record):
|
||||
'''Add username, ip and request ID to the log record.
|
||||
"""Add username, ip and request ID to the log record.
|
||||
|
||||
Inspired by django-log-request-id
|
||||
'''
|
||||
Inspired by django-log-request-id
|
||||
"""
|
||||
# prevent multiple execution on the same record
|
||||
if getattr(record, 'request_context', False):
|
||||
return True
|
||||
|
@ -63,9 +65,11 @@ class RequestContextFilter(logging.Filter):
|
|||
|
||||
# lookup user from record then from request
|
||||
if not hasattr(record, 'user'):
|
||||
if (hasattr(request, 'user')
|
||||
and hasattr(request.user, 'is_authenticated')
|
||||
and request.user.is_authenticated):
|
||||
if (
|
||||
hasattr(request, 'user')
|
||||
and hasattr(request.user, 'is_authenticated')
|
||||
and request.user.is_authenticated
|
||||
):
|
||||
record.user = request.user
|
||||
else:
|
||||
record.user = None
|
||||
|
@ -117,6 +121,7 @@ class ForceDebugFilter(logging.Filter):
|
|||
|
||||
class LogRecord(logging.LogRecord):
|
||||
'''Subclass LogRecord to make multiline log parseable'''
|
||||
|
||||
def getMessage(self):
|
||||
return super(LogRecord, self).getMessage().replace('\n', '\n ')
|
||||
|
||||
|
@ -146,8 +151,10 @@ class DebugLogFilter(object):
|
|||
elif hasattr(debug_log, 'encode'):
|
||||
# debug_log is a string
|
||||
domains = [domain.strip() for domain in debug_log.split(',')]
|
||||
return any(record.name == domain or (record.name.startswith(domain) and record.name[len(domain)] == '.')
|
||||
for domain in domains)
|
||||
return any(
|
||||
record.name == domain or (record.name.startswith(domain) and record.name[len(domain)] == '.')
|
||||
for domain in domains
|
||||
)
|
||||
else:
|
||||
return bool(debug_log)
|
||||
|
||||
|
@ -201,11 +208,13 @@ class DebugLog(object):
|
|||
d['user'] = ast.literal_eval(d['user'])
|
||||
except SyntaxError:
|
||||
pass
|
||||
d.update({
|
||||
'cursor': cursor,
|
||||
'timestamp': timestamp,
|
||||
'message': message,
|
||||
})
|
||||
d.update(
|
||||
{
|
||||
'cursor': cursor,
|
||||
'timestamp': timestamp,
|
||||
'message': message,
|
||||
}
|
||||
)
|
||||
yield d
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -26,17 +26,18 @@ class SettingsForm(forms.Form):
|
|||
'cnil_compliant_visits_tracking_js' else into 'visits_tracking_js'.
|
||||
The goal is to display a banner advertising users about intrusive JS.
|
||||
"""
|
||||
tracking_js = forms.CharField(
|
||||
label=_('Tracking Javascript'),
|
||||
required=False,
|
||||
widget=forms.Textarea())
|
||||
|
||||
tracking_js = forms.CharField(label=_('Tracking Javascript'), required=False, widget=forms.Textarea())
|
||||
|
||||
def clean_tracking_js(self):
|
||||
value = self.cleaned_data['tracking_js']
|
||||
if '<script' in value:
|
||||
raise forms.ValidationError(
|
||||
_('This field should only contain the Javascript code. '
|
||||
'You should remove the surrounding <script> markup.'))
|
||||
_(
|
||||
'This field should only contain the Javascript code. '
|
||||
'You should remove the surrounding <script> markup.'
|
||||
)
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
|
|
|
@ -16,19 +16,18 @@
|
|||
|
||||
import hashlib
|
||||
import re
|
||||
import requests
|
||||
import string
|
||||
|
||||
from lxml import etree
|
||||
from random import choice, randint
|
||||
|
||||
from django.db import connection
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core import exceptions
|
||||
from django.db import connection
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.six.moves.urllib import parse as urlparse
|
||||
from lxml import etree
|
||||
|
||||
from hobo.environment.models import Variable, Wcs, Combo, Fargo
|
||||
from hobo.environment.models import Combo, Fargo, Variable, Wcs
|
||||
|
||||
CNIL_JS = """
|
||||
// disallow cookie's time extension
|
||||
|
@ -47,15 +46,15 @@ CNIL_JS = """
|
|||
}]);
|
||||
"""
|
||||
|
||||
|
||||
def get_variable(name, default=''):
|
||||
"""get hobo variables from DB
|
||||
set it to '' into DB if not already created
|
||||
"""
|
||||
variable, created = Variable.objects.get_or_create(
|
||||
name=name,
|
||||
defaults={'auto': True, 'value': default})
|
||||
variable, created = Variable.objects.get_or_create(name=name, defaults={'auto': True, 'value': default})
|
||||
return variable
|
||||
|
||||
|
||||
def get_variable_value(name, default=''):
|
||||
"""get hobo variables's value from DB"""
|
||||
try:
|
||||
|
@ -64,12 +63,14 @@ def get_variable_value(name, default=''):
|
|||
value = default
|
||||
return value
|
||||
|
||||
|
||||
def get_tracking_js():
|
||||
"""merge JS code from the 2 above variables"""
|
||||
tracking_js = get_variable_value('cnil_compliant_visits_tracking_js')
|
||||
tracking_js += get_variable_value('visits_tracking_js')
|
||||
return tracking_js
|
||||
|
||||
|
||||
def put_tracking_js(tracking_js):
|
||||
"""store JS code into only one of the 2 above variables"""
|
||||
variable1 = get_variable('cnil_compliant_visits_tracking_js')
|
||||
|
@ -87,11 +88,11 @@ def put_tracking_js(tracking_js):
|
|||
variable1.delete()
|
||||
variable2.delete()
|
||||
|
||||
|
||||
def get_tenant_name_and_public_urls():
|
||||
"""get an alias for our matomo's id and urls to monitor"""
|
||||
tenant_name = None
|
||||
services = [x for x in Combo.objects.all()
|
||||
if 'portal-user' in x.template_name and not x.secondary]
|
||||
services = [x for x in Combo.objects.all() if 'portal-user' in x.template_name and not x.secondary]
|
||||
if services != [] and services[0] != '':
|
||||
tenant_name = urlparse.urlparse(services[0].base_url).netloc
|
||||
services += [x for x in Wcs.objects.all() if not x.secondary]
|
||||
|
@ -99,12 +100,15 @@ def get_tenant_name_and_public_urls():
|
|||
site_urls = [x.base_url for x in services if x.base_url != '']
|
||||
return tenant_name, site_urls
|
||||
|
||||
|
||||
class MatomoException(Exception):
|
||||
"""unexpected Matomo internal error"""
|
||||
|
||||
|
||||
class MatomoError(MatomoException):
|
||||
"""expected Matomo error responses"""
|
||||
|
||||
|
||||
class MatomoWS(object):
|
||||
"""api for matomo webservices"""
|
||||
|
||||
|
@ -166,8 +170,7 @@ class MatomoWS(object):
|
|||
return tree
|
||||
|
||||
def get_site_id_from_site_url(self, url):
|
||||
data = {'method': 'SitesManager.getSitesIdFromSiteUrl',
|
||||
'url': url}
|
||||
data = {'method': 'SitesManager.getSitesIdFromSiteUrl', 'url': url}
|
||||
tree = self.call(data)
|
||||
try:
|
||||
if tree.xpath('/result[not(*)]')[0].text is None:
|
||||
|
@ -181,8 +184,7 @@ class MatomoWS(object):
|
|||
return tag.text
|
||||
|
||||
def add_site(self, site_name):
|
||||
data = {'method': 'SitesManager.addSite',
|
||||
'siteName': site_name}
|
||||
data = {'method': 'SitesManager.addSite', 'siteName': site_name}
|
||||
tree = self.call(data)
|
||||
try:
|
||||
tag = tree.xpath('/result')[0]
|
||||
|
@ -191,8 +193,7 @@ class MatomoWS(object):
|
|||
return tag.text
|
||||
|
||||
def add_site_alias_urls(self, id_site, site_urls):
|
||||
data = {'method': 'SitesManager.addSiteAliasUrls',
|
||||
'idSite': id_site}
|
||||
data = {'method': 'SitesManager.addSiteAliasUrls', 'idSite': id_site}
|
||||
cpt = 0
|
||||
for url in site_urls:
|
||||
key = 'urls[%i]' % cpt
|
||||
|
@ -206,23 +207,23 @@ class MatomoWS(object):
|
|||
return tag.text
|
||||
|
||||
def add_user(self, user_login, password, initial_id_site):
|
||||
data = {'method': 'UsersManager.addUser',
|
||||
'userLogin': user_login,
|
||||
'password': password,
|
||||
'email': self.email_template % user_login,
|
||||
'initialIdSite': initial_id_site}
|
||||
data = {
|
||||
'method': 'UsersManager.addUser',
|
||||
'userLogin': user_login,
|
||||
'password': password,
|
||||
'email': self.email_template % user_login,
|
||||
'initialIdSite': initial_id_site,
|
||||
}
|
||||
tree = self.call(data)
|
||||
return self.assert_success(tree, 'add_user')
|
||||
|
||||
def del_user(self, user_login):
|
||||
data = {'method': 'UsersManager.deleteUser',
|
||||
'userLogin': user_login}
|
||||
data = {'method': 'UsersManager.deleteUser', 'userLogin': user_login}
|
||||
tree = self.call(data)
|
||||
return self.assert_success(tree, 'del_user')
|
||||
|
||||
def get_javascript_tag(self, id_site):
|
||||
data = {'method': 'SitesManager.getJavascriptTag',
|
||||
'idSite': id_site}
|
||||
data = {'method': 'SitesManager.getJavascriptTag', 'idSite': id_site}
|
||||
tree = self.call(data)
|
||||
try:
|
||||
tag = tree.xpath('/result')[0]
|
||||
|
@ -240,14 +241,11 @@ class MatomoWS(object):
|
|||
try:
|
||||
tree = resp.json()
|
||||
except ValueError:
|
||||
raise MatomoException(
|
||||
'internal error on ping (JSON expected): %s' % resp.content)
|
||||
raise MatomoException('internal error on ping (JSON expected): %s' % resp.content)
|
||||
if not isinstance(tree, dict):
|
||||
raise MatomoException(
|
||||
'internal error on ping (dict expected): %s' % resp.content)
|
||||
raise MatomoException('internal error on ping (dict expected): %s' % resp.content)
|
||||
if 'status' not in tree:
|
||||
raise MatomoException(
|
||||
'internal error on ping (status expected): %s' % resp.content)
|
||||
raise MatomoException('internal error on ping (status expected): %s' % resp.content)
|
||||
if tree['status'] != 'success':
|
||||
raise MatomoError('ping fails: %s' % resp.content)
|
||||
return True
|
||||
|
@ -265,6 +263,7 @@ def upgrade_site(matomo, tenant_name, site_urls):
|
|||
matomo.add_site_alias_urls(id_site, site_urls)
|
||||
return id_site
|
||||
|
||||
|
||||
def upgrade_user(matomo, user_login, id_site):
|
||||
# API is not obvious to change password (need the previous one)
|
||||
try:
|
||||
|
@ -280,9 +279,13 @@ def upgrade_user(matomo, user_login, id_site):
|
|||
# build the user's login url
|
||||
password_md5 = hashlib.md5(force_bytes(password)).hexdigest()
|
||||
logme_url = '%s/index.php?module=Login&action=logme&login=%s&password=%s' % (
|
||||
matomo.url_ws_base, user_login, password_md5)
|
||||
matomo.url_ws_base,
|
||||
user_login,
|
||||
password_md5,
|
||||
)
|
||||
return logme_url
|
||||
|
||||
|
||||
def upgrade_javascript_tag(matomo, id_site):
|
||||
"""addapt JS return by Matomo and merge it whith previous JS code we have"""
|
||||
matomo_tag = matomo.get_javascript_tag(id_site)
|
||||
|
@ -306,7 +309,7 @@ def upgrade_javascript_tag(matomo, id_site):
|
|||
regex = re.compile(r'\s*var _paq = window._paq \|\| \[\];')
|
||||
for count, line in enumerate(lines):
|
||||
if regex.match(line):
|
||||
lines.insert(count+1, CNIL_JS)
|
||||
lines.insert(count + 1, CNIL_JS)
|
||||
break
|
||||
|
||||
enhanced_tag = '\n'.join(lines)
|
||||
|
|
|
@ -14,16 +14,23 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.views.generic import RedirectView, FormView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import FormView, RedirectView
|
||||
|
||||
from .forms import SettingsForm, EnableForm
|
||||
from .utils import get_variable, get_variable_value, \
|
||||
get_tracking_js, put_tracking_js, \
|
||||
MatomoException, MatomoError, MatomoWS, \
|
||||
compute_cnil_acknowledgment_level, auto_configure_matomo
|
||||
from .forms import EnableForm, SettingsForm
|
||||
from .utils import (
|
||||
MatomoError,
|
||||
MatomoException,
|
||||
MatomoWS,
|
||||
auto_configure_matomo,
|
||||
compute_cnil_acknowledgment_level,
|
||||
get_tracking_js,
|
||||
get_variable,
|
||||
get_variable_value,
|
||||
put_tracking_js,
|
||||
)
|
||||
|
||||
|
||||
class HomeView(FormView):
|
||||
|
@ -63,6 +70,7 @@ class HomeView(FormView):
|
|||
context['mode'] = 'manual'
|
||||
return context
|
||||
|
||||
|
||||
home = HomeView.as_view()
|
||||
|
||||
|
||||
|
@ -83,6 +91,7 @@ class EnableManualView(FormView):
|
|||
logme_url.delete()
|
||||
return super(EnableManualView, self).form_valid(form)
|
||||
|
||||
|
||||
enable_manual = EnableManualView.as_view()
|
||||
|
||||
|
||||
|
@ -96,14 +105,15 @@ class EnableAutoView(FormView):
|
|||
try:
|
||||
id_site = auto_configure_matomo(matomo)
|
||||
except MatomoException as exc:
|
||||
messages.error(self.request, 'matomo: '+str(exc))
|
||||
messages.error(self.request, 'matomo: ' + str(exc))
|
||||
else:
|
||||
try:
|
||||
matomo.create_fake_first_tracking_visit(id_site)
|
||||
except MatomoException as exc:
|
||||
messages.warning(self.request, 'ping: '+str(exc))
|
||||
messages.warning(self.request, 'ping: ' + str(exc))
|
||||
return super(EnableAutoView, self).form_valid(form)
|
||||
|
||||
|
||||
enable_auto = EnableAutoView.as_view()
|
||||
|
||||
|
||||
|
@ -118,4 +128,5 @@ class DisableView(FormView):
|
|||
variable.delete()
|
||||
return super(DisableView, self).form_valid(form)
|
||||
|
||||
|
||||
disable = DisableView.as_view()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from .version import VersionMiddleware
|
||||
from .cookies_samesite import CookiesSameSiteFixMiddleware
|
||||
from .cors import CORSMiddleware
|
||||
from .seo import RobotsTxtMiddleware
|
||||
from .stats import PrometheusStatsMiddleware
|
||||
from .version import VersionMiddleware
|
||||
|
|
|
@ -35,4 +35,5 @@ class CookiesSameSiteFixMiddleware(MiddlewareMixin):
|
|||
|
||||
# required for Python <3.8
|
||||
import http.cookies
|
||||
|
||||
http.cookies.Morsel._reserved.setdefault('samesite', 'SameSite')
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.conf import settings
|
|||
from django.http import HttpResponse
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
class CORSMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request):
|
||||
"""
|
||||
|
@ -13,8 +14,7 @@ class CORSMiddleware(MiddlewareMixin):
|
|||
view/exception middleware along with the requested view;
|
||||
it will call any response middlewares
|
||||
"""
|
||||
if request.method == 'OPTIONS' and \
|
||||
"HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META:
|
||||
if request.method == 'OPTIONS' and "HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META:
|
||||
response = HttpResponse()
|
||||
return response
|
||||
return None
|
||||
|
|
|
@ -22,7 +22,5 @@ from django.utils.deprecation import MiddlewareMixin
|
|||
class RobotsTxtMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request):
|
||||
if request.method == 'GET' and request.path == '/robots.txt' and hasattr(settings, 'TEMPLATE_VARS'):
|
||||
return HttpResponse(
|
||||
settings.TEMPLATE_VARS.get('robots_txt', ''),
|
||||
content_type='text/plain')
|
||||
return HttpResponse(settings.TEMPLATE_VARS.get('robots_txt', ''), content_type='text/plain')
|
||||
return None
|
||||
|
|
|
@ -18,47 +18,50 @@ import inspect
|
|||
import time
|
||||
|
||||
import django
|
||||
import prometheus_client
|
||||
from django.db import connection
|
||||
from django.http import HttpResponse
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
import prometheus_client
|
||||
|
||||
|
||||
requests_total_by_host_view_status_method = prometheus_client.Counter(
|
||||
'django_http_requests_total_by_host_view_status_method',
|
||||
'Count of requests by host, view, status, method.',
|
||||
['host', 'view', 'status', 'method'])
|
||||
['host', 'view', 'status', 'method'],
|
||||
)
|
||||
|
||||
requests_bytes_by_host_view_status_method = prometheus_client.Summary(
|
||||
'django_http_requests_bytes_by_host_view_status_method',
|
||||
'Bytes of requests by host, view, status, method.',
|
||||
['host', 'view', 'status', 'method'])
|
||||
['host', 'view', 'status', 'method'],
|
||||
)
|
||||
|
||||
requests_times_by_host_view_status_method = prometheus_client.Summary(
|
||||
'django_http_requests_times_by_host_view_status_method',
|
||||
'Duration of requests by host, view, status, method.',
|
||||
['host', 'view', 'status', 'method'])
|
||||
['host', 'view', 'status', 'method'],
|
||||
)
|
||||
|
||||
requests_queries_count_by_host_view_status_method = prometheus_client.Summary(
|
||||
'django_http_requests_queries_count_by_host_view_status_method',
|
||||
'Query count by host, view, status, method.',
|
||||
['host', 'view', 'status', 'method'])
|
||||
['host', 'view', 'status', 'method'],
|
||||
)
|
||||
|
||||
requests_queries_time_by_host_view_status_method = prometheus_client.Summary(
|
||||
'django_http_requests_queries_time_by_host_view_status_method',
|
||||
'Query time by host, view, status, method.',
|
||||
['host', 'view', 'status', 'method'])
|
||||
|
||||
['host', 'view', 'status', 'method'],
|
||||
)
|
||||
|
||||
|
||||
class PrometheusStatsMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request):
|
||||
if request.method == 'GET' and request.path == '/__metrics__/':
|
||||
return HttpResponse(prometheus_client.generate_latest(),
|
||||
content_type=prometheus_client.CONTENT_TYPE_LATEST)
|
||||
return HttpResponse(
|
||||
prometheus_client.generate_latest(), content_type=prometheus_client.CONTENT_TYPE_LATEST
|
||||
)
|
||||
|
||||
connection.force_debug_cursor = True # to count queries
|
||||
connection.force_debug_cursor = True # to count queries
|
||||
request._stats_t0 = time.time()
|
||||
return None
|
||||
|
||||
|
@ -80,23 +83,26 @@ class PrometheusStatsMiddleware(MiddlewareMixin):
|
|||
|
||||
status_code = response.status_code
|
||||
view_name = getattr(request, '_view_name', '<unnamed view>')
|
||||
requests_total_by_host_view_status_method.labels(
|
||||
host_name, view_name, status_code, http_method).inc()
|
||||
requests_total_by_host_view_status_method.labels(host_name, view_name, status_code, http_method).inc()
|
||||
|
||||
if hasattr(response, 'content'):
|
||||
response_size = len(response.content)
|
||||
requests_bytes_by_host_view_status_method.labels(
|
||||
host_name, view_name, status_code, http_method).observe(response_size)
|
||||
host_name, view_name, status_code, http_method
|
||||
).observe(response_size)
|
||||
|
||||
requests_times_by_host_view_status_method.labels(
|
||||
host_name, view_name, status_code, http_method).observe(total_time)
|
||||
host_name, view_name, status_code, http_method
|
||||
).observe(total_time)
|
||||
|
||||
if connection.queries_logged:
|
||||
sql_queries_count = len(connection.queries)
|
||||
sql_queries_time = sum([float(x['time']) for x in connection.queries])
|
||||
requests_queries_count_by_host_view_status_method.labels(
|
||||
host_name, view_name, status_code, http_method).observe(sql_queries_count)
|
||||
host_name, view_name, status_code, http_method
|
||||
).observe(sql_queries_count)
|
||||
requests_queries_time_by_host_view_status_method.labels(
|
||||
host_name, view_name, status_code, http_method).observe(sql_queries_time)
|
||||
host_name, view_name, status_code, http_method
|
||||
).observe(sql_queries_time)
|
||||
|
||||
return response
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import threading
|
||||
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
class StoreRequestMiddleware(MiddlewareMixin):
|
||||
collection = {}
|
||||
|
||||
|
|
|
@ -5,11 +5,10 @@ from django.utils.deprecation import MiddlewareMixin
|
|||
|
||||
from hobo.scrutiny.wsgi import middleware
|
||||
|
||||
|
||||
class VersionMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request):
|
||||
if request.method == 'GET' and (request.path == '/__version__' or
|
||||
request.path == '/__version__/'):
|
||||
if request.method == 'GET' and (request.path == '/__version__' or request.path == '/__version__/'):
|
||||
packages_version = middleware.VersionMiddleware.get_packages_version()
|
||||
return HttpResponse(json.dumps(packages_version),
|
||||
content_type='application/json')
|
||||
return HttpResponse(json.dumps(packages_version), content_type='application/json')
|
||||
return None
|
||||
|
|
|
@ -3,15 +3,15 @@ from django.utils.deprecation import MiddlewareMixin
|
|||
|
||||
|
||||
class XForwardedForMiddleware(MiddlewareMixin):
|
||||
'''Copy the first address from X-Forwarded-For header to the REMOTE_ADDR meta.
|
||||
"""Copy the first address from X-Forwarded-For header to the REMOTE_ADDR meta.
|
||||
|
||||
This middleware should only be used if you are sure the header cannot be
|
||||
forged (behind a reverse proxy for example)."""
|
||||
|
||||
This middleware should only be used if you are sure the header cannot be
|
||||
forged (behind a reverse proxy for example).'''
|
||||
def process_request(self, request):
|
||||
if getattr(settings, 'USE_X_FORWARDED_FOR', False):
|
||||
if 'HTTP_X_FORWARDED_FOR' in request.META:
|
||||
ip = request.META.get('HTTP_X_FORWARDED_FOR', '').split(",")[0].strip()
|
||||
ip = request.META.get('HTTP_X_FORWARDED_FOR', '').split(",")[0].strip()
|
||||
if ip:
|
||||
request.META['REMOTE_ADDR'] = ip
|
||||
return None
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ default_app_config = 'hobo.multitenant.apps.MultitenantAppConfig'
|
|||
# import the context_processors module so it gets to compute the versions hash
|
||||
# early on.
|
||||
from hobo import context_processors
|
||||
|
||||
# install tenant aware thread classes to prevent other lib to use classic ones
|
||||
from . import threads
|
||||
|
||||
threads.install_tenant_aware_threads()
|
||||
|
|
|
@ -8,15 +8,13 @@ class MultitenantAppConfig(AppConfig):
|
|||
verbose_name = 'Multitenant'
|
||||
|
||||
def ready(self):
|
||||
from django.db.migrations import operations
|
||||
from django.db import migrations
|
||||
from django import conf
|
||||
from django.db import migrations
|
||||
from django.db.migrations import operations
|
||||
|
||||
# Install tenant aware settings
|
||||
if not isinstance(conf.settings._wrapped,
|
||||
settings.TenantSettingsWrapper):
|
||||
conf.settings._wrapped = settings.TenantSettingsWrapper(
|
||||
conf.settings._wrapped)
|
||||
if not isinstance(conf.settings._wrapped, settings.TenantSettingsWrapper):
|
||||
conf.settings._wrapped = settings.TenantSettingsWrapper(conf.settings._wrapped)
|
||||
# reset settings getattr method to a cache-less version, to cancel
|
||||
# https://code.djangoproject.com/ticket/27625.
|
||||
conf.LazySettings.__getattr__ = lambda self, name: getattr(self._wrapped, name)
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
from django.core.cache.backends.base import InvalidCacheBackendError
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import connection
|
||||
from django.utils.module_loading import import_string
|
||||
from django.core.cache.backends.base import InvalidCacheBackendError
|
||||
|
||||
|
||||
class TenantBaseCache(object):
|
||||
'''Prepend the tenant schema name to the cache prefix'''
|
||||
|
||||
def set_key_prefix(self, prefix):
|
||||
self.__key_prefix = prefix
|
||||
|
||||
def get_key_prefix(self):
|
||||
if connection.tenant:
|
||||
return '%s_%s'% (connection.tenant.schema_name, self.__key_prefix)
|
||||
return '%s_%s' % (connection.tenant.schema_name, self.__key_prefix)
|
||||
return self.__key_prefix
|
||||
|
||||
key_prefix = property(get_key_prefix, set_key_prefix)
|
||||
|
@ -18,6 +20,7 @@ class TenantBaseCache(object):
|
|||
|
||||
__DERIVED_CLASSES = {}
|
||||
|
||||
|
||||
def TenantCache(host, params, **kwargs):
|
||||
try:
|
||||
backend = params['REAL_BACKEND']
|
||||
|
@ -31,6 +34,5 @@ def TenantCache(host, params, **kwargs):
|
|||
if derived_cls_name not in __DERIVED_CLASSES:
|
||||
# dynamically create a new class with TenantBaseCache
|
||||
# and the original class as parents
|
||||
__DERIVED_CLASSES[derived_cls_name] = type(derived_cls_name,
|
||||
(TenantBaseCache, backend_cls), {})
|
||||
__DERIVED_CLASSES[derived_cls_name] = type(derived_cls_name, (TenantBaseCache, backend_cls), {})
|
||||
return __DERIVED_CLASSES[derived_cls_name](host, params, **kwargs)
|
||||
|
|
|
@ -20,18 +20,17 @@ import os
|
|||
import shutil
|
||||
import time
|
||||
|
||||
import haystack.backends.whoosh_backend
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
|
||||
from tenant_schemas.postgresql_backend.base import FakeTenant
|
||||
|
||||
import haystack.backends.whoosh_backend
|
||||
|
||||
class WhooshSearchBackend(haystack.backends.whoosh_backend.WhooshSearchBackend):
|
||||
@property
|
||||
def use_file_storage(self):
|
||||
tenant = connection.get_tenant()
|
||||
return not(isinstance(connection.tenant, FakeTenant))
|
||||
return not (isinstance(connection.tenant, FakeTenant))
|
||||
|
||||
@use_file_storage.setter
|
||||
def use_file_storage(self, value):
|
||||
|
|
|
@ -14,13 +14,14 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.db import connection
|
||||
import django.utils.log
|
||||
from django.db import connection
|
||||
|
||||
|
||||
class AdminEmailHandler(django.utils.log.AdminEmailHandler):
|
||||
def format_subject(self, subject):
|
||||
from .models import Tenant
|
||||
|
||||
subject = super(AdminEmailHandler, self).format_subject(subject)
|
||||
try:
|
||||
subject = '[%s] %s' % (connection.get_tenant().domain_url, subject)
|
||||
|
|
|
@ -4,16 +4,19 @@
|
|||
# License: MIT license
|
||||
# Home-page: http://github.com/bcarneiro/django-tenant-schemas
|
||||
from optparse import make_option
|
||||
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command, get_commands, load_command_class
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import connection
|
||||
|
||||
try:
|
||||
from django.utils.six.moves import input
|
||||
except ImportError:
|
||||
input = raw_input
|
||||
from tenant_schemas.utils import get_public_schema_name
|
||||
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
|
||||
|
||||
|
@ -61,9 +64,11 @@ class BaseTenantCommand(BaseCommand):
|
|||
|
||||
if verbosity >= 1:
|
||||
print()
|
||||
print(self.style.NOTICE("=== Switching to schema '")
|
||||
+ self.style.SQL_TABLE(tenant.schema_name)
|
||||
+ self.style.NOTICE("' then calling %s:" % command_name))
|
||||
print(
|
||||
self.style.NOTICE("=== Switching to schema '")
|
||||
+ self.style.SQL_TABLE(tenant.schema_name)
|
||||
+ self.style.NOTICE("' then calling %s:" % command_name)
|
||||
)
|
||||
|
||||
connection.set_tenant(tenant)
|
||||
|
||||
|
@ -77,7 +82,12 @@ class BaseTenantCommand(BaseCommand):
|
|||
if options['domain']:
|
||||
# only run on a particular schema
|
||||
connection.set_schema_to_public()
|
||||
self.execute_command(TenantMiddleware.get_tenant_by_hostname(options['domain']), self.COMMAND_NAME, *args, **options)
|
||||
self.execute_command(
|
||||
TenantMiddleware.get_tenant_by_hostname(options['domain']),
|
||||
self.COMMAND_NAME,
|
||||
*args,
|
||||
**options,
|
||||
)
|
||||
else:
|
||||
for tenant in TenantMiddleware.get_tenants():
|
||||
self.execute_command(tenant, self.COMMAND_NAME, *args, **options)
|
||||
|
@ -91,9 +101,11 @@ class InteractiveTenantOption(object):
|
|||
all_tenants = list(TenantMiddleware.get_tenants())
|
||||
|
||||
if not all_tenants:
|
||||
raise CommandError("""There are no tenants in the system.
|
||||
raise CommandError(
|
||||
"""There are no tenants in the system.
|
||||
To learn how create a tenant, see:
|
||||
https://django-tenant-schemas.readthedocs.org/en/latest/use.html#creating-a-tenant""")
|
||||
https://django-tenant-schemas.readthedocs.org/en/latest/use.html#creating-a-tenant"""
|
||||
)
|
||||
|
||||
if options.get('domain'):
|
||||
domain = options['domain']
|
||||
|
@ -106,7 +118,7 @@ https://django-tenant-schemas.readthedocs.org/en/latest/use.html#creating-a-tena
|
|||
if domain in [t.domain_url for t in all_tenants]:
|
||||
break
|
||||
try:
|
||||
domain = displayed_tenants[int(domain)-1].domain_url
|
||||
domain = displayed_tenants[int(domain) - 1].domain_url
|
||||
break
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
@ -115,8 +127,7 @@ https://django-tenant-schemas.readthedocs.org/en/latest/use.html#creating-a-tena
|
|||
else:
|
||||
displayed_tenants = [x for x in all_tenants if domain in x.domain_url]
|
||||
for i, tenant in enumerate(displayed_tenants):
|
||||
print("[%2d] %s (schema %s)" % (
|
||||
i+1, tenant.domain_url, tenant.schema_name))
|
||||
print("[%2d] %s (schema %s)" % (i + 1, tenant.domain_url, tenant.schema_name))
|
||||
|
||||
return TenantMiddleware.get_tenant_by_hostname(domain)
|
||||
|
||||
|
@ -145,13 +156,24 @@ class TenantWrappedCommand(InteractiveTenantOption, BaseCommand):
|
|||
|
||||
|
||||
class SyncCommon(BaseCommand):
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--app_label', action='store', dest='app_label', nargs='?',
|
||||
help='App label of an application to synchronize the state.')
|
||||
parser.add_argument('--migration_name', action='store', dest='migration_name', nargs='?',
|
||||
help=('Database state will be brought to the state after that '
|
||||
'migration. Use the name "zero" to unapply all migrations.'))
|
||||
parser.add_argument(
|
||||
'--app_label',
|
||||
action='store',
|
||||
dest='app_label',
|
||||
nargs='?',
|
||||
help='App label of an application to synchronize the state.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--migration_name',
|
||||
action='store',
|
||||
dest='migration_name',
|
||||
nargs='?',
|
||||
help=(
|
||||
'Database state will be brought to the state after that '
|
||||
'migration. Use the name "zero" to unapply all migrations.'
|
||||
),
|
||||
)
|
||||
parser.add_argument("-d", "--domain", dest="domain")
|
||||
parser.add_argument("-s", "--schema", dest="schema_name")
|
||||
|
||||
|
@ -177,8 +199,8 @@ class SyncCommon(BaseCommand):
|
|||
|
||||
|
||||
def disable_global_logging():
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
if 'TERM' not in os.environ:
|
||||
|
@ -187,6 +209,7 @@ def disable_global_logging():
|
|||
# try to disable sentry
|
||||
if 'sentry_sdk' in sys.modules:
|
||||
import sentry_sdk
|
||||
|
||||
sentry_sdk.init()
|
||||
|
||||
# then try to disable journald, syslog logging and mail admins
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
from hobo.agent.common.management.commands.hobo_deploy import Command as HoboDeployCommand
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
|
||||
from .create_tenant import Command as CreateTenantCommand
|
||||
from hobo.agent.common.management.commands.hobo_deploy import Command as HoboDeployCommand
|
||||
|
||||
|
||||
class Command(CreateTenantCommand):
|
||||
|
@ -17,7 +17,7 @@ class Command(CreateTenantCommand):
|
|||
if not hostnames:
|
||||
raise CommandError("you must give at least one tenant hostname")
|
||||
|
||||
if '-' in hostnames: # get additional list of hostnames from stdin
|
||||
if '-' in hostnames: # get additional list of hostnames from stdin
|
||||
hostnames = list(hostnames)
|
||||
hostnames.remove('-')
|
||||
hostnames.extend([x.strip() for x in sys.stdin.readlines()])
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
from django.db import connection
|
||||
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Create schemas for all declared tenants"
|
||||
|
||||
|
@ -15,7 +17,6 @@ class Command(BaseCommand):
|
|||
for tenant in all_tenants:
|
||||
if verbosity >= 1:
|
||||
print()
|
||||
print(self.style.NOTICE("=== Creating schema ")
|
||||
+ self.style.SQL_TABLE(tenant.schema_name))
|
||||
print(self.style.NOTICE("=== Creating schema ") + self.style.SQL_TABLE(tenant.schema_name))
|
||||
|
||||
tenant.create_schema(check_if_exists=True)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import connection, transaction
|
||||
from django.core.management.base import CommandError, BaseCommand
|
||||
|
||||
from hobo.multitenant.middleware import TenantMiddleware, get_tenant_model
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Create tenant(s) by hostname(s)"
|
||||
|
||||
|
@ -17,7 +18,7 @@ class Command(BaseCommand):
|
|||
if not hostnames:
|
||||
raise CommandError("you must give at least one tenant hostname")
|
||||
|
||||
if '-' in hostnames: # get additional list of hostnames from stdin
|
||||
if '-' in hostnames: # get additional list of hostnames from stdin
|
||||
hostnames = list(hostnames)
|
||||
hostnames.remove('-')
|
||||
hostnames.extend([x.strip() for x in sys.stdin.readlines()])
|
||||
|
@ -32,8 +33,7 @@ class Command(BaseCommand):
|
|||
tenant_dir = os.path.join(tenant_base, hostname)
|
||||
if os.path.exists(tenant_dir):
|
||||
raise CommandError('tenant already exists')
|
||||
tenant_dir_tmp = os.path.join(
|
||||
tenant_base, hostname + '__CREATION_IN_PROGRESS.invalid')
|
||||
tenant_dir_tmp = os.path.join(tenant_base, hostname + '__CREATION_IN_PROGRESS.invalid')
|
||||
try:
|
||||
os.mkdir(tenant_dir_tmp, 0o755)
|
||||
except OSError as e:
|
||||
|
@ -45,8 +45,9 @@ class Command(BaseCommand):
|
|||
tenant = get_tenant_model()(schema_name=schema, domain_url=hostname)
|
||||
if verbosity >= 1:
|
||||
print()
|
||||
print(self.style.NOTICE("=== Creating schema ")
|
||||
+ self.style.SQL_TABLE(tenant.schema_name))
|
||||
print(
|
||||
self.style.NOTICE("=== Creating schema ") + self.style.SQL_TABLE(tenant.schema_name)
|
||||
)
|
||||
with transaction.atomic():
|
||||
tenant.create_schema(check_if_exists=True)
|
||||
except Exception as e:
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
# Email: carneiro.be@gmail.com
|
||||
# License: MIT license
|
||||
# Home-page: http://github.com/bcarneiro/django-tenant-schemas
|
||||
from hobo.multitenant.management.commands import TenantWrappedCommand
|
||||
from django.contrib.auth.management.commands import createsuperuser
|
||||
|
||||
from hobo.multitenant.management.commands import TenantWrappedCommand
|
||||
|
||||
|
||||
class Command(TenantWrappedCommand):
|
||||
COMMAND = createsuperuser.Command
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import sys
|
||||
from optparse import make_option
|
||||
|
||||
from django.core.management.base import CommandError, BaseCommand
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
|
||||
|
||||
|
@ -9,15 +10,19 @@ class Command(BaseCommand):
|
|||
help = "Delete tenant(s) by hostname(s)"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--force-drop', action='store_true', default=False,
|
||||
help='If you want the schema to be deleted from database')
|
||||
parser.add_argument(
|
||||
'--force-drop',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='If you want the schema to be deleted from database',
|
||||
)
|
||||
parser.add_argument('hostnames', metavar='HOSTNAME', nargs='+')
|
||||
|
||||
def handle(self, hostnames, **options):
|
||||
if not hostnames:
|
||||
raise CommandError("you must give at least one tenant hostname")
|
||||
|
||||
if '-' in hostnames: # get additional list of hostnames from stdin
|
||||
if '-' in hostnames: # get additional list of hostnames from stdin
|
||||
hostnames = list(hostnames)
|
||||
hostnames.remove('-')
|
||||
hostnames.extend([x.strip() for x in sys.stdin.readlines()])
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
requires_model_validation = True
|
||||
can_import_settings = True
|
||||
|
@ -10,4 +12,3 @@ class Command(BaseCommand):
|
|||
|
||||
for tenant in all_tenants:
|
||||
print("{0} {1}".format(tenant.schema_name, tenant.domain_url))
|
||||
|
||||
|
|
|
@ -5,20 +5,26 @@
|
|||
# Home-page: http://github.com/bcarneiro/django-tenant-schemas
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.core.management.base import CommandError, BaseCommand
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from tenant_schemas.utils import django_is_in_test_mode
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
database = options.get('database', 'default')
|
||||
if (settings.DATABASES[database]['ENGINE'] == 'tenant_schemas.postgresql_backend' or
|
||||
MigrateCommand is BaseCommand):
|
||||
raise CommandError("migrate has been disabled, for database '{}'. Use migrate_schemas "
|
||||
"instead. Please read the documentation if you don't know why you "
|
||||
"shouldn't call migrate directly!".format(database))
|
||||
if (
|
||||
settings.DATABASES[database]['ENGINE'] == 'tenant_schemas.postgresql_backend'
|
||||
or MigrateCommand is BaseCommand
|
||||
):
|
||||
raise CommandError(
|
||||
"migrate has been disabled, for database '{}'. Use migrate_schemas "
|
||||
"instead. Please read the documentation if you don't know why you "
|
||||
"shouldn't call migrate directly!".format(database)
|
||||
)
|
||||
super(Command, self).handle(*args, **options)
|
||||
|
||||
|
||||
if django_is_in_test_mode():
|
||||
from .migrate_schemas import MigrateSchemasCommand
|
||||
|
||||
Command = MigrateSchemasCommand
|
||||
|
|
|
@ -15,18 +15,17 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import django
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.management.commands.migrate import Command as MigrateCommand
|
||||
from django.db import connection
|
||||
from django.db.migrations.loader import MigrationLoader
|
||||
from django.db.migrations.recorder import MigrationRecorder
|
||||
from django.db import connection
|
||||
from django.conf import settings
|
||||
|
||||
from tenant_schemas.utils import get_public_schema_name, schema_exists
|
||||
from tenant_schemas.postgresql_backend.base import FakeTenant
|
||||
from hobo.multitenant.middleware import TenantMiddleware, TenantNotFound
|
||||
from tenant_schemas.utils import get_public_schema_name, schema_exists
|
||||
|
||||
from hobo.multitenant.management.commands import SyncCommon
|
||||
from hobo.multitenant.middleware import TenantMiddleware, TenantNotFound
|
||||
|
||||
|
||||
class MigrateSchemasCommand(SyncCommon):
|
||||
|
@ -56,7 +55,9 @@ class MigrateSchemasCommand(SyncCommon):
|
|||
app_labels.append(app.label)
|
||||
loader = MigrationLoader(None)
|
||||
loader.load_disk()
|
||||
all_migrations = set([(app, migration) for app, migration in loader.disk_migrations if app in app_labels])
|
||||
all_migrations = set(
|
||||
[(app, migration) for app, migration in loader.disk_migrations if app in app_labels]
|
||||
)
|
||||
for tenant in TenantMiddleware.get_tenants():
|
||||
connection.set_tenant(tenant, include_public=False)
|
||||
applied_migrations = self.get_applied_migrations(app_labels)
|
||||
|
@ -102,4 +103,5 @@ class MigrateSchemasCommand(SyncCommon):
|
|||
def _notice(self, output):
|
||||
self.stdout.write(self.style.NOTICE(output))
|
||||
|
||||
|
||||
Command = MigrateSchemasCommand
|
||||
|
|
|
@ -2,13 +2,12 @@ import os
|
|||
from optparse import make_option
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.commands.runserver import Command as RunserverCommand
|
||||
from django.utils.six.moves.urllib.request import url2pathname
|
||||
from django.views import static
|
||||
|
||||
from django.contrib.staticfiles.handlers import StaticFilesHandler
|
||||
from django.contrib.staticfiles.management.commands.runserver import Command as StaticRunserverCommand
|
||||
from django.contrib.staticfiles.views import serve
|
||||
from django.core.management.commands.runserver import Command as RunserverCommand
|
||||
from django.utils.six.moves.urllib.request import url2pathname
|
||||
from django.views import static
|
||||
|
||||
from hobo.multitenant.middleware import TenantMiddleware, TenantNotFound
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from hobo.multitenant.management.commands import TenantWrappedCommand, disable_global_logging
|
||||
from django.core.management.commands import shell
|
||||
|
||||
from hobo.multitenant.management.commands import TenantWrappedCommand, disable_global_logging
|
||||
|
||||
|
||||
class Command(TenantWrappedCommand):
|
||||
COMMAND = shell.Command
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.commands.showmigrations import Command as ShowMigrationsCommand
|
||||
from django.db import connection
|
||||
from django.conf import settings
|
||||
|
||||
from hobo.multitenant.middleware import TenantMiddleware, TenantNotFound
|
||||
from hobo.multitenant.management.commands import SyncCommon
|
||||
from hobo.multitenant.middleware import TenantMiddleware, TenantNotFound
|
||||
|
||||
|
||||
class ShowMigrationsSchemasCommand(SyncCommon):
|
||||
|
@ -36,8 +36,7 @@ class ShowMigrationsSchemasCommand(SyncCommon):
|
|||
try:
|
||||
tenant = TenantMiddleware.get_tenant_by_hostname(self.domain)
|
||||
except TenantNotFound:
|
||||
raise RuntimeError('Schema "{}" does not exist'.format(
|
||||
self.schema_name))
|
||||
raise RuntimeError('Schema "{}" does not exist'.format(self.schema_name))
|
||||
self.run_showmigrations(tenant, settings.TENANT_APPS)
|
||||
else:
|
||||
for tenant in TenantMiddleware.get_tenants():
|
||||
|
@ -50,4 +49,5 @@ class ShowMigrationsSchemasCommand(SyncCommon):
|
|||
command.execute(*self.args, **self.options)
|
||||
connection.set_schema_to_public()
|
||||
|
||||
|
||||
Command = ShowMigrationsSchemasCommand
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
# Email: carneiro.be@gmail.com
|
||||
# License: MIT license
|
||||
# Home-page: http://github.com/bcarneiro/django-tenant-schemas
|
||||
from django.core.management.base import CommandError
|
||||
from django.conf import settings
|
||||
from django.core.management.base import CommandError
|
||||
from tenant_schemas.utils import django_is_in_test_mode
|
||||
|
||||
if 'south' in settings.INSTALLED_APPS:
|
||||
|
@ -14,13 +14,16 @@ else:
|
|||
|
||||
|
||||
class Command(syncdb.Command):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
database = options.get('database', 'default')
|
||||
if (settings.DATABASES[database]['ENGINE'] == 'tenant_schemas.postgresql_backend' and not
|
||||
django_is_in_test_mode()):
|
||||
raise CommandError("syncdb has been disabled, for database '{0}'. "
|
||||
"Use sync_schemas instead. Please read the "
|
||||
"documentation if you don't know why "
|
||||
"you shouldn't call syncdb directly!".format(database))
|
||||
if (
|
||||
settings.DATABASES[database]['ENGINE'] == 'tenant_schemas.postgresql_backend'
|
||||
and not django_is_in_test_mode()
|
||||
):
|
||||
raise CommandError(
|
||||
"syncdb has been disabled, for database '{0}'. "
|
||||
"Use sync_schemas instead. Please read the "
|
||||
"documentation if you don't know why "
|
||||
"you shouldn't call syncdb directly!".format(database)
|
||||
)
|
||||
super(Command, self).handle(*args, **options)
|
||||
|
|
|
@ -9,15 +9,13 @@ import argparse
|
|||
import sys
|
||||
|
||||
import django
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.base import (BaseCommand, CommandError,
|
||||
SystemCheckError,
|
||||
handle_default_options)
|
||||
from django.core.management import call_command, get_commands, load_command_class
|
||||
from django.core.management.base import BaseCommand, CommandError, SystemCheckError, handle_default_options
|
||||
from django.db import connection, connections
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from hobo.multitenant.management.commands import InteractiveTenantOption
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
|
@ -62,8 +60,9 @@ def run_command_from_argv(command, argv):
|
|||
if isinstance(e, SystemCheckError):
|
||||
command.stderr.write(str(e), lambda x: x)
|
||||
else:
|
||||
command.stderr.write('%s: %s: %s' % (
|
||||
connection.get_tenant(), e.__class__.__name__, exception_to_text(e)))
|
||||
command.stderr.write(
|
||||
'%s: %s: %s' % (connection.get_tenant(), e.__class__.__name__, exception_to_text(e))
|
||||
)
|
||||
return e
|
||||
finally:
|
||||
try:
|
||||
|
@ -103,26 +102,31 @@ class Command(InteractiveTenantOption, BaseCommand):
|
|||
# and forward the rest of the arguments to the actual command being wrapped.
|
||||
del argv[1]
|
||||
args_parser = argparse.ArgumentParser()
|
||||
args_parser.add_argument("--all-tenants", help="apply command to all tenants",
|
||||
action='store_true')
|
||||
args_parser.add_argument("--all-tenants", help="apply command to all tenants", action='store_true')
|
||||
args_parser.add_argument("-d", "--domain", dest="domain_name", help="specify tenant domain name")
|
||||
args_parser.add_argument(
|
||||
'--force-job', dest='force_job', action='store_true',
|
||||
help='Run command even if DISABLE_CRON_JOBS is set')
|
||||
'--force-job',
|
||||
dest='force_job',
|
||||
action='store_true',
|
||||
help='Run command even if DISABLE_CRON_JOBS is set',
|
||||
)
|
||||
args_namespace, args = args_parser.parse_known_args(argv)
|
||||
|
||||
# Continue weirdness: parse verbosity option and also leave it in args
|
||||
# for subcommand consumption
|
||||
verbosity_parser = argparse.ArgumentParser()
|
||||
verbosity_parser.add_argument('-v', '--verbosity', action='store',
|
||||
dest='verbosity', default=1, type=int)
|
||||
verbosity_parser.add_argument(
|
||||
'-v', '--verbosity', action='store', dest='verbosity', default=1, type=int
|
||||
)
|
||||
args_verbosity, _ = verbosity_parser.parse_known_args(args)
|
||||
|
||||
if (args_namespace.all_tenants and not args_namespace.force_job and
|
||||
getattr(settings, 'DISABLE_CRON_JOBS', False)):
|
||||
if (
|
||||
args_namespace.all_tenants
|
||||
and not args_namespace.force_job
|
||||
and getattr(settings, 'DISABLE_CRON_JOBS', False)
|
||||
):
|
||||
if args_verbosity.verbosity > 1:
|
||||
print('Command %s is ignored because DISABLE_CRON_JOBS is set'
|
||||
% app_name)
|
||||
print('Command %s is ignored because DISABLE_CRON_JOBS is set' % app_name)
|
||||
return
|
||||
|
||||
if args_namespace.all_tenants:
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
from __future__ import absolute_import
|
||||
import logging
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from django.db import connection
|
||||
|
||||
from mellon.adapters import DefaultAdapter
|
||||
|
||||
from . import utils
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import os
|
||||
import glob
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.utils.encoding import smart_bytes
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import connection
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from tenant_schemas.utils import get_tenant_model, get_public_schema_name
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.utils.encoding import smart_bytes
|
||||
from tenant_schemas.utils import get_public_schema_name, get_tenant_model
|
||||
|
||||
SENTINEL = object()
|
||||
|
||||
|
@ -23,6 +23,7 @@ class TenantMiddleware(MiddlewareMixin):
|
|||
Selects the proper database schema using the request host. Can fail in
|
||||
various ways which is better than corrupting or revealing data...
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def base(cls):
|
||||
return settings.TENANT_BASE
|
||||
|
@ -55,9 +56,7 @@ class TenantMiddleware(MiddlewareMixin):
|
|||
continue
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
yield get_tenant_model()(
|
||||
schema_name=self.hostname2schema(hostname),
|
||||
domain_url=hostname)
|
||||
yield get_tenant_model()(schema_name=self.hostname2schema(hostname), domain_url=hostname)
|
||||
|
||||
def process_request(self, request):
|
||||
# connection needs first to be at the public schema, as this is where the
|
||||
|
@ -88,5 +87,8 @@ class TenantMiddleware(MiddlewareMixin):
|
|||
ContentType.objects.clear_cache()
|
||||
|
||||
# do we have a public-specific token?
|
||||
if hasattr(settings, 'PUBLIC_SCHEMA_URLCONF') and request.tenant.schema_name == get_public_schema_name():
|
||||
if (
|
||||
hasattr(settings, 'PUBLIC_SCHEMA_URLCONF')
|
||||
and request.tenant.schema_name == get_public_schema_name()
|
||||
):
|
||||
request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import os
|
||||
import json
|
||||
import os
|
||||
from shutil import rmtree
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
from django.utils import timezone
|
||||
from django.utils.six.moves.urllib.parse import urljoin
|
||||
|
||||
from tenant_schemas.utils import get_public_schema_name
|
||||
from tenant_schemas.models import TenantMixin
|
||||
from tenant_schemas.utils import django_is_in_test_mode, schema_exists
|
||||
from tenant_schemas.utils import django_is_in_test_mode, get_public_schema_name, schema_exists
|
||||
|
||||
|
||||
class Tenant(TenantMixin):
|
||||
# default true, schema will be automatically created and synced when it is saved
|
||||
|
@ -34,9 +33,7 @@ class Tenant(TenantMixin):
|
|||
|
||||
def get_hobo_json(self):
|
||||
if not self.__hobo_json:
|
||||
self.__hobo_json = json.load(open(
|
||||
os.path.join(self.get_directory(),
|
||||
'hobo.json')))
|
||||
self.__hobo_json = json.load(open(os.path.join(self.get_directory(), 'hobo.json')))
|
||||
return self.__hobo_json
|
||||
|
||||
def get_service(self):
|
||||
|
@ -66,9 +63,10 @@ class Tenant(TenantMixin):
|
|||
auto_drop_schema set to True.
|
||||
"""
|
||||
if connection.schema_name not in (self.schema_name, get_public_schema_name()):
|
||||
raise Exception("Can't delete tenant outside it's own schema or "
|
||||
"the public schema. Current schema is %s."
|
||||
% connection.schema_name)
|
||||
raise Exception(
|
||||
"Can't delete tenant outside it's own schema or "
|
||||
"the public schema. Current schema is %s." % connection.schema_name
|
||||
)
|
||||
if force_drop:
|
||||
rmtree(self.get_directory())
|
||||
else:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue