746 lines
26 KiB
Python
746 lines
26 KiB
Python
import collections
|
|
import copy
|
|
import datetime
|
|
import inspect
|
|
import logging
|
|
import os
|
|
import re
|
|
import sys
|
|
import traceback
|
|
import base64
|
|
|
|
import django
|
|
from django.apps import apps
|
|
from django.conf import settings
|
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist, PermissionDenied
|
|
from django.core.urlresolvers import reverse
|
|
from django.db import models, transaction
|
|
from django.db.models import Q
|
|
from django.test import override_settings
|
|
from django.utils.text import slugify
|
|
from django.utils import timezone
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.core.files.base import ContentFile
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.contrib.contenttypes import fields
|
|
|
|
from jsonfield import JSONField
|
|
|
|
from model_utils.managers import InheritanceManager as ModelUtilsInheritanceManager
|
|
|
|
import jsonfield
|
|
|
|
import passerelle
|
|
import requests
|
|
|
|
KEYTYPE_CHOICES = (
|
|
('API', _('API Key')),
|
|
('SIGN', _('HMAC Signature')),
|
|
)
|
|
|
|
class ApiUser(models.Model):
|
|
username = models.CharField(max_length=128,
|
|
verbose_name=_('Username'),
|
|
unique=True)
|
|
fullname = models.CharField(max_length=50,
|
|
verbose_name=_('Full Name'))
|
|
description = models.TextField(blank=True,
|
|
verbose_name=_('Description'))
|
|
|
|
keytype = models.CharField(max_length=4, choices=KEYTYPE_CHOICES,
|
|
blank=True, verbose_name=_('Key Type'))
|
|
key = models.CharField(max_length=256, blank=True, verbose_name=_('Key'))
|
|
|
|
ipsource = models.GenericIPAddressField(blank=True, null=True, unpack_ipv4=True,
|
|
verbose_name=_('IP Address'))
|
|
|
|
def __unicode__(self):
|
|
return u'%s <%s>' % (self.fullname, self.username)
|
|
|
|
def clean(self):
|
|
if self.keytype and not self.key:
|
|
raise ValidationError(_('Key can not be empty for type %s.') % self.keytype)
|
|
|
|
def export_json(self):
|
|
return {
|
|
'@type': 'passerelle-user',
|
|
'username': self.username,
|
|
'fullname': self.fullname,
|
|
'description': self.description,
|
|
'keytype': self.keytype,
|
|
'key': self.key,
|
|
'ipsource': self.ipsource,
|
|
}
|
|
|
|
@classmethod
|
|
def import_json(self, d, overwrite=False):
|
|
if d.get('@type') != 'passerelle-user':
|
|
raise ValueError('not a passerelle user export')
|
|
d = d.copy()
|
|
d.pop('@type')
|
|
api_user, created = self.objects.get_or_create(username=d['username'], defaults=d)
|
|
if overwrite and not created:
|
|
for key in d:
|
|
setattr(api_user, key, d[key])
|
|
api_user.save()
|
|
|
|
|
|
class TemplateVar(models.Model):
|
|
name = models.CharField(max_length=64)
|
|
value = models.CharField(max_length=128)
|
|
|
|
def __unicode__(self):
|
|
return u'%s - %s' % (self.name, self.value)
|
|
|
|
|
|
class InheritanceManager(ModelUtilsInheritanceManager):
|
|
|
|
def get_slug(self, slug, request=None):
|
|
'''
|
|
Returns a resource by its slug
|
|
Request based access control, if request is present
|
|
'''
|
|
resource = self.get_subclass(slug=slug)
|
|
if request and not resource.is_accessible_by(request):
|
|
raise PermissionDenied
|
|
return resource
|
|
|
|
def filter_apiuser(self, apiuser):
|
|
'''
|
|
Returns all resources accessible by apiuser
|
|
'''
|
|
return self.filter(Q(users=None) | Q(users=apiuser))
|
|
|
|
|
|
class BaseResource(models.Model):
|
|
title = models.CharField(max_length=50, verbose_name=_('Title'))
|
|
description = models.TextField(verbose_name=_('Description'))
|
|
slug = models.SlugField(verbose_name=_('Identifier'), unique=True)
|
|
users = models.ManyToManyField(ApiUser, blank=True)
|
|
objects = InheritanceManager()
|
|
|
|
parameters = None
|
|
manager_view_template_name = None
|
|
|
|
# permission descriptions
|
|
_can_access_description = _('Access is limited to the following API users:')
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(BaseResource, self).__init__(*args, **kwargs)
|
|
self.logger = ProxyLogger(connector=self)
|
|
|
|
def __unicode__(self):
|
|
return self.title
|
|
|
|
def get_css_class_name(self):
|
|
category = self.category if not hasattr(self.category, '_proxy____args') else self.category._proxy____args[0]
|
|
return "%s %s" % (slugify(category), self._meta.model_name)
|
|
|
|
def is_accessible_by(self, request):
|
|
if request.user.is_superuser:
|
|
return True
|
|
restricted = self.users.all()
|
|
return not restricted or request.apiuser in restricted
|
|
|
|
@classmethod
|
|
def is_enabled(cls):
|
|
return getattr(settings, 'PASSERELLE_APP_%s_ENABLED' % cls._meta.app_label.upper(), True)
|
|
|
|
@property
|
|
def requests(self):
|
|
if getattr(self, '_requests', None) is None:
|
|
self._requests = passerelle.utils.Request(resource=self, logger=self.logger)
|
|
return self._requests
|
|
|
|
@property
|
|
def logging_parameters(self):
|
|
resource_type = ContentType.objects.get_for_model(self)
|
|
try:
|
|
return LoggingParameters.objects.get(
|
|
resource_type=resource_type,
|
|
resource_pk=self.id)
|
|
except LoggingParameters.DoesNotExist:
|
|
return LoggingParameters(
|
|
resource_type=resource_type,
|
|
resource_pk=self.id)
|
|
|
|
@property
|
|
def log_level(self):
|
|
return self.logging_parameters.log_level
|
|
|
|
def set_log_level(self, value):
|
|
parameters = self.logging_parameters
|
|
parameters.log_level = value
|
|
parameters.save()
|
|
|
|
@property
|
|
def availability_parameters(self):
|
|
resource_type = ContentType.objects.get_for_model(self)
|
|
try:
|
|
return AvailabilityParameters.objects.get(
|
|
resource_type=resource_type,
|
|
resource_pk=self.id)
|
|
except AvailabilityParameters.DoesNotExist:
|
|
return AvailabilityParameters(
|
|
resource_type=resource_type,
|
|
resource_pk=self.id)
|
|
|
|
def soap_client(self, **kwargs):
|
|
return passerelle.utils.SOAPClient(resource=self, **kwargs)
|
|
|
|
@classmethod
|
|
def get_verbose_name(cls):
|
|
return cls._meta.verbose_name
|
|
|
|
@classmethod
|
|
def get_connector_slug(cls):
|
|
return cls._meta.app_label.replace('_', '-')
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('view-connector',
|
|
kwargs={'connector': self.get_connector_slug(), 'slug': self.slug})
|
|
|
|
@classmethod
|
|
def get_add_url(cls):
|
|
return reverse('create-connector', kwargs={'connector': cls.get_connector_slug()})
|
|
|
|
def get_edit_url(self):
|
|
return reverse('edit-connector',
|
|
kwargs={'connector': self.get_connector_slug(), 'slug': self.slug})
|
|
|
|
def get_delete_url(self):
|
|
return reverse('delete-connector',
|
|
kwargs={'connector': self.get_connector_slug(), 'slug': self.slug})
|
|
|
|
def get_description_fields(self):
|
|
fields = []
|
|
for field in self._meta.fields:
|
|
if (field.name.endswith(('key', 'password', 'secret', 'keystore')) or
|
|
field.name in ('id', 'title', 'slug', 'description',
|
|
'log_level', 'users', 'client_certificate')):
|
|
continue
|
|
if hasattr(self, 'get_%s_display' % field.name):
|
|
value = getattr(self, 'get_%s_display' % field.name)()
|
|
else:
|
|
value = getattr(self, field.name, None)
|
|
if isinstance(field, models.URLField) and value:
|
|
# hide http authentication part
|
|
value = re.sub(r'://([^/]*:[^/]*?)@', '://***:***@', value)
|
|
fields.append((field, value))
|
|
return fields
|
|
|
|
def get_endpoints_infos(self):
|
|
endpoints = []
|
|
for name, method in inspect.getmembers(self, predicate=inspect.ismethod):
|
|
if hasattr(method, 'endpoint_info'):
|
|
method.endpoint_info.object = self
|
|
for http_method in method.endpoint_info.methods:
|
|
# duplicate information to give each method its own entry
|
|
endpoint_info = copy.copy(method.endpoint_info)
|
|
endpoint_info.http_method = http_method
|
|
endpoints.append(endpoint_info)
|
|
endpoints.sort(key=lambda x: (x.name, x.pattern))
|
|
return endpoints
|
|
|
|
def get_connector_permissions(self):
|
|
perms = {}
|
|
for endpoint_info in self.get_endpoints_infos():
|
|
permission = endpoint_info.perm
|
|
if permission:
|
|
perms[permission] = getattr(self,
|
|
'_%s_description' % permission,
|
|
_('Access (%s) is limited to the following API users:') % permission)
|
|
return [{'key': x[0], 'label': x[1]} for x in perms.items()]
|
|
|
|
def get_availability_status(self):
|
|
resource_type = ContentType.objects.get_for_model(self)
|
|
current_status = ResourceStatus.objects.filter(
|
|
resource_type=resource_type,
|
|
resource_pk=self.pk).first()
|
|
return current_status
|
|
|
|
def down(self):
|
|
status = self.get_availability_status()
|
|
return (status and status.down())
|
|
|
|
def export_json(self):
|
|
d = {
|
|
'@type': 'passerelle-resource',
|
|
'resource_type': '%s.%s' % (self.__class__._meta.app_label,
|
|
self.__class__._meta.model_name),
|
|
'title': self.title,
|
|
'slug': self.slug,
|
|
'description': self.description,
|
|
'log_level': self.log_level,
|
|
'access_rights': []
|
|
}
|
|
resource_type = ContentType.objects.get_for_model(self)
|
|
for ar in AccessRight.objects.filter(resource_type=resource_type,
|
|
resource_pk=self.pk).select_related():
|
|
d['access_rights'].append({
|
|
'codename': ar.codename,
|
|
'apiuser': ar.apiuser.username,
|
|
})
|
|
concrete_fields = [
|
|
f for f in self.__class__._meta.get_fields()
|
|
if f.concrete and (
|
|
not f.is_relation
|
|
or f.one_to_one
|
|
or (f.many_to_one and f.related_model)
|
|
)
|
|
]
|
|
for field in concrete_fields:
|
|
if field.name == 'id':
|
|
continue
|
|
value = getattr(self, field.attname)
|
|
if isinstance(field, (models.TextField, models.CharField, models.SlugField,
|
|
models.URLField, models.BooleanField, models.IntegerField,
|
|
models.CommaSeparatedIntegerField, models.EmailField,
|
|
models.IntegerField, models.PositiveIntegerField, JSONField)):
|
|
d[field.name] = value
|
|
elif isinstance(field, models.FileField):
|
|
if value:
|
|
d[field.name] = {
|
|
'name': os.path.basename(value.name),
|
|
'content': base64.b64encode(value.read()),
|
|
}
|
|
else:
|
|
d[field.name] = None
|
|
else:
|
|
raise Exception('export_json: field %s of ressource class %s is unsupported' % (
|
|
field, self.__class__))
|
|
return d
|
|
|
|
@staticmethod
|
|
def import_json(d, import_users=False, overwrite=False):
|
|
if d.get('@type') != 'passerelle-resource':
|
|
raise ValueError('not a passerelle resource export')
|
|
|
|
d = d.copy()
|
|
d.pop('@type')
|
|
app_label, model_name = d['resource_type'].split('.')
|
|
model = apps.get_model(app_label, model_name)
|
|
try:
|
|
instance = model.objects.get(slug=d['slug'])
|
|
if not overwrite:
|
|
return
|
|
except model.DoesNotExist:
|
|
instance = None
|
|
with transaction.atomic():
|
|
# prevent semi-creation of ressources
|
|
instance = model.import_json_real(overwrite, instance, d)
|
|
resource_type = ContentType.objects.get_for_model(instance)
|
|
# We can only connect AccessRight objects to the new Resource after its creation
|
|
if import_users:
|
|
for ar in d['access_rights']:
|
|
apiuser = ApiUser.objects.get(username=ar['apiuser'])
|
|
AccessRight.objects.get_or_create(
|
|
codename=ar['codename'],
|
|
resource_type=resource_type,
|
|
resource_pk=instance.pk,
|
|
apiuser=apiuser)
|
|
return instance
|
|
|
|
@classmethod
|
|
def import_json_real(cls, overwrite, instance, d, **kwargs):
|
|
init_kwargs = {
|
|
'title': d['title'],
|
|
'slug': d['slug'],
|
|
'description': d['description'],
|
|
}
|
|
init_kwargs.update(kwargs)
|
|
if instance:
|
|
for key in init_kwargs:
|
|
setattr(instance, key, init_kwargs[key])
|
|
else:
|
|
instance = cls(**init_kwargs)
|
|
concrete_fields = [
|
|
f for f in cls._meta.get_fields()
|
|
if f.concrete and (
|
|
not f.is_relation
|
|
or f.one_to_one
|
|
or (f.many_to_one and f.related_model)
|
|
)
|
|
]
|
|
for field in concrete_fields:
|
|
if field.name == 'id':
|
|
continue
|
|
value = d[field.name]
|
|
if isinstance(field, (models.TextField, models.CharField, models.SlugField,
|
|
models.URLField, models.BooleanField, models.IntegerField,
|
|
models.CommaSeparatedIntegerField, models.EmailField,
|
|
models.IntegerField, models.PositiveIntegerField, JSONField)):
|
|
setattr(instance, field.attname, value)
|
|
elif isinstance(field, models.FileField):
|
|
if value:
|
|
getattr(instance, field.attname).save(
|
|
value['name'],
|
|
ContentFile(base64.b64decode(value['content'])),
|
|
save=False)
|
|
else:
|
|
raise Exception('import_json_real: field %s of ressource class '
|
|
'%s is unsupported' % (field, cls))
|
|
instance.save()
|
|
if 'log_level' in d:
|
|
instance.set_log_level(d['log_level'])
|
|
return instance
|
|
|
|
def clean_logs(self):
|
|
# clean logs
|
|
timestamp = timezone.now() - datetime.timedelta(days=settings.LOG_RETENTION_DAYS)
|
|
ResourceLog.objects.filter(
|
|
appname=self.get_connector_slug(),
|
|
slug=self.slug,
|
|
timestamp__lt=timestamp).delete()
|
|
|
|
def check_status(self):
|
|
# should raise an exception if status is not ok
|
|
raise NotImplementedError
|
|
check_status.not_implemented = True
|
|
|
|
def availability(self):
|
|
# "availability" cron job to update service statuses
|
|
|
|
# eventually skip it
|
|
if not self.availability_parameters.run_check:
|
|
return
|
|
|
|
currently_down = self.down()
|
|
try:
|
|
self.check_status()
|
|
status = 'up'
|
|
message = ''
|
|
except NotImplementedError:
|
|
return
|
|
except Exception as e:
|
|
status = 'down'
|
|
message = repr(e)[:500]
|
|
|
|
resource_type = ContentType.objects.get_for_model(self)
|
|
current_status = ResourceStatus.objects.filter(
|
|
resource_type=resource_type,
|
|
resource_pk=self.pk).first()
|
|
if not current_status or status != current_status.status:
|
|
if status == 'down' and not currently_down:
|
|
self.logger.error(u'connector "%s" (%s) is now down', self, self.__class__.__name__)
|
|
ResourceStatus(
|
|
resource_type=resource_type,
|
|
resource_pk=self.pk,
|
|
status=status,
|
|
message=message).save()
|
|
if status == 'up' and currently_down:
|
|
self.logger.info(u'connector "%s" (%s) is back up', self, self.__class__.__name__)
|
|
elif status == 'down':
|
|
current_status.message = message
|
|
current_status.save()
|
|
|
|
def hourly(self):
|
|
pass
|
|
|
|
def daily(self):
|
|
self.clean_logs()
|
|
|
|
def weekly(self):
|
|
pass
|
|
|
|
def monthly(self):
|
|
pass
|
|
|
|
def jobs(self):
|
|
# "jobs" cron job to run asynchronous tasks
|
|
resource_type = ContentType.objects.get_for_model(self)
|
|
skip_locked = {'skip_locked': True}
|
|
if django.VERSION < (1, 11, 0):
|
|
skip_locked = {}
|
|
skipped_jobs = []
|
|
while True:
|
|
with transaction.atomic():
|
|
# lock a job
|
|
job = Job.objects.exclude(
|
|
pk__in=skipped_jobs
|
|
).filter(
|
|
resource_type=resource_type,
|
|
resource_pk=self.pk,
|
|
status='registered'
|
|
).select_for_update(**skip_locked).first()
|
|
if not job:
|
|
break
|
|
job.status = 'running'
|
|
job.save()
|
|
# release lock
|
|
try:
|
|
getattr(self, job.method_name)(**job.parameters)
|
|
except SkipJob:
|
|
job.status = 'registered'
|
|
skipped_jobs.append(job.id)
|
|
except Exception as e:
|
|
(exc_type, exc_value, tb) = sys.exc_info()
|
|
job.status = 'failed'
|
|
job.done_timestamp = timezone.now()
|
|
job.status_details = {
|
|
'error_summary': '\n'.join(traceback.format_exception_only(exc_type, exc_value)).strip(),
|
|
}
|
|
else:
|
|
job.status = 'completed'
|
|
job.done_timestamp = timezone.now()
|
|
job.save()
|
|
|
|
def add_job(self, method_name, natural_id=None, **kwargs):
|
|
resource_type = ContentType.objects.get_for_model(self)
|
|
job = Job(resource_type=resource_type,
|
|
resource_pk=self.pk,
|
|
method_name=method_name,
|
|
natural_id=natural_id,
|
|
parameters=kwargs)
|
|
job.save()
|
|
return job
|
|
|
|
|
|
class AccessRight(models.Model):
|
|
codename = models.CharField(max_length=100, verbose_name='codename')
|
|
resource_type = models.ForeignKey(ContentType)
|
|
resource_pk = models.PositiveIntegerField()
|
|
resource = fields.GenericForeignKey('resource_type', 'resource_pk')
|
|
apiuser = models.ForeignKey(ApiUser, verbose_name=_('API User'))
|
|
|
|
class Meta:
|
|
permissions = (
|
|
('view_accessright', 'Can view access right'),
|
|
)
|
|
unique_together = (
|
|
('codename', 'resource_type', 'resource_pk', 'apiuser'),
|
|
)
|
|
|
|
def __unicode__(self):
|
|
return '%s (on %s <%s>) (for %s)' % (self.codename, self.resource_type, self.resource_pk, self.apiuser)
|
|
|
|
|
|
class LoggingParameters(models.Model):
|
|
resource_type = models.ForeignKey(ContentType)
|
|
resource_pk = models.PositiveIntegerField()
|
|
resource = fields.GenericForeignKey('resource_type', 'resource_pk')
|
|
log_level = models.CharField(
|
|
verbose_name=_('Log Level'),
|
|
max_length=10,
|
|
choices = (
|
|
('DEBUG', 'DEBUG'),
|
|
('INFO', 'INFO'),
|
|
('WARNING', 'WARNING'),
|
|
('ERROR', 'ERROR'),
|
|
('CRITICAL', 'CRITICAL'),
|
|
),
|
|
default='INFO'
|
|
)
|
|
trace_emails = models.TextField(
|
|
verbose_name=_('Emails to receive error and critical traces'),
|
|
help_text=_('One address per line (empty for site administrators)'),
|
|
blank=True
|
|
)
|
|
|
|
class Meta:
|
|
unique_together = (('resource_type', 'resource_pk'))
|
|
|
|
|
|
class AvailabilityParameters(models.Model):
|
|
resource_type = models.ForeignKey(ContentType)
|
|
resource_pk = models.PositiveIntegerField()
|
|
resource = fields.GenericForeignKey('resource_type', 'resource_pk')
|
|
run_check = models.BooleanField(
|
|
default=True, verbose_name=_('Run regular availability checks'),
|
|
help_text=_('Run an availability check every 5 minutes'))
|
|
|
|
class Meta:
|
|
unique_together = (('resource_type', 'resource_pk'))
|
|
|
|
|
|
class SkipJob(Exception):
|
|
pass
|
|
|
|
|
|
class Job(models.Model):
|
|
resource_type = models.ForeignKey(ContentType)
|
|
resource_pk = models.PositiveIntegerField()
|
|
resource = fields.GenericForeignKey('resource_type', 'resource_pk')
|
|
method_name = models.CharField(max_length=50)
|
|
natural_id = models.CharField(max_length=256, blank=True, null=True)
|
|
parameters = jsonfield.JSONField(default={})
|
|
creation_timestamp = models.DateTimeField(auto_now_add=True)
|
|
update_timestamp = models.DateTimeField(auto_now=True)
|
|
done_timestamp = models.DateTimeField(null=True)
|
|
status = models.CharField(
|
|
max_length=20,
|
|
default='registered',
|
|
choices=(('registered', _('Registered')),
|
|
('running', _('Running')),
|
|
('failed', _('Failed')),
|
|
('completed', _('Completed'))
|
|
),
|
|
)
|
|
status_details = jsonfield.JSONField(default={})
|
|
|
|
|
|
class ResourceLog(models.Model):
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
appname = models.CharField(max_length=128, verbose_name='appname', null=True)
|
|
slug = models.CharField(max_length=128, verbose_name='slug', null=True)
|
|
levelno = models.IntegerField(verbose_name='log level')
|
|
sourceip = models.GenericIPAddressField(blank=True, null=True, verbose_name=_('Source IP'))
|
|
message = models.TextField(max_length=2048, verbose_name='message')
|
|
extra = jsonfield.JSONField(verbose_name='extras', default={})
|
|
|
|
class Meta:
|
|
permissions = (
|
|
('view_resourcelog', 'Can view resource logs'),
|
|
)
|
|
|
|
@property
|
|
def level(self):
|
|
return slugify(logging.getLevelName(self.levelno))
|
|
|
|
def __unicode__(self):
|
|
return '%s %s %s %s' % (self.timestamp, self.levelno, self.appname, self.slug)
|
|
|
|
|
|
STATUS_CHOICES = (
|
|
('unknown', _('Unknown')),
|
|
('up', _('Up')),
|
|
('down', _('Down')),
|
|
)
|
|
|
|
class ResourceStatus(models.Model):
|
|
resource_type = models.ForeignKey(ContentType)
|
|
resource_pk = models.PositiveIntegerField()
|
|
start_timestamp = models.DateTimeField(auto_now_add=True)
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='unknown')
|
|
message = models.CharField(max_length=500, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['-start_timestamp']
|
|
|
|
def up(self):
|
|
return self.status == 'up'
|
|
|
|
def down(self):
|
|
return self.status == 'down'
|
|
|
|
|
|
class ProxyLogger(object):
|
|
|
|
def __init__(self, connector):
|
|
self.connector = connector
|
|
self.appname = connector.get_connector_slug()
|
|
self.slug = connector.slug
|
|
logger_name = 'passerelle.resource.%s.%s' % (self.appname, self.slug)
|
|
self._logger = logging.getLogger(logger_name)
|
|
self._logger.setLevel(connector.log_level)
|
|
|
|
@property
|
|
def level(self):
|
|
return self._logger.getEffectiveLevel()
|
|
|
|
def _log(self, levelname, message, *args, **kwargs):
|
|
if self.connector.down():
|
|
# don't log if the connector is known to be down
|
|
return
|
|
levelno = getattr(logging, levelname)
|
|
if self._logger.level <= levelno:
|
|
|
|
attr = {}
|
|
attr['levelno'] = levelno
|
|
|
|
rl_message = message
|
|
rl_args = args
|
|
# Borrowed from python stdlib logging/__init__.py
|
|
if (rl_args and len(rl_args) == 1 and isinstance(rl_args[0], collections.Mapping)
|
|
and rl_args[0]):
|
|
rl_args = rl_args[0]
|
|
# End Borrow
|
|
if rl_args:
|
|
rl_message = rl_message % rl_args
|
|
attr['message'] = rl_message[:ResourceLog._meta.get_field('message').max_length]
|
|
|
|
attr['appname'] = self.appname
|
|
attr['slug'] = self.slug
|
|
attr['extra'] = kwargs.get('extra', {})
|
|
request = kwargs.pop('request', None)
|
|
|
|
if getattr(request, 'META', None):
|
|
if 'HTTP_X_FORWARDED_FOR' in request.META:
|
|
sourceip = request.META.get('HTTP_X_FORWARDED_FOR', '').split(",")[0].strip()
|
|
else:
|
|
sourceip = request.META.get('REMOTE_ADDR')
|
|
else:
|
|
sourceip = None
|
|
attr['sourceip'] = sourceip
|
|
|
|
if kwargs.get('exc_info'):
|
|
(exc_type, exc_value, tb) = sys.exc_info()
|
|
attr['extra']['error_summary'] = traceback.format_exception_only(exc_type, exc_value)
|
|
|
|
ResourceLog.objects.create(**attr)
|
|
|
|
admins = settings.ADMINS
|
|
logging_parameters = self.connector.logging_parameters
|
|
if logging_parameters.trace_emails:
|
|
admins = [('', x) for x in logging_parameters.trace_emails.splitlines()]
|
|
with override_settings(ADMINS=admins):
|
|
getattr(self._logger, levelname.lower())(message, *args, **kwargs)
|
|
|
|
def exception(self, message, *args, **kwargs):
|
|
kwargs['exc_info'] = 1
|
|
self._log('ERROR', message, *args, **kwargs)
|
|
|
|
def debug(self, message, *args, **kwargs):
|
|
self._log('DEBUG', message, *args, **kwargs)
|
|
|
|
def info(self, message, *args, **kwargs):
|
|
self._log('INFO', message, *args, **kwargs)
|
|
|
|
def warning(self, message, *args, **kwargs):
|
|
self._log('WARNING', message, *args, **kwargs)
|
|
|
|
def critical(self, message, *args, **kwargs):
|
|
self._log('CRITICAL', message, *args, **kwargs)
|
|
|
|
def error(self, message, *args, **kwargs):
|
|
self._log('ERROR', message, *args, **kwargs)
|
|
|
|
def fatal(self, message, *args, **kwargs):
|
|
self._log('FATAL', message, *args, **kwargs)
|
|
|
|
|
|
class HTTPResource(models.Model):
|
|
'''Mixin to add basic TLS/Basic HTTP authentication fields to any
|
|
resource.'''
|
|
basic_auth_username = models.CharField(
|
|
max_length=128,
|
|
verbose_name=_('Basic authentication username'),
|
|
blank=True)
|
|
basic_auth_password = models.CharField(
|
|
max_length=128,
|
|
verbose_name=_('Basic authentication password'),
|
|
blank=True)
|
|
client_certificate = models.FileField(
|
|
verbose_name=_('TLS client certificate'),
|
|
null=True,
|
|
blank=True)
|
|
trusted_certificate_authorities = models.FileField(
|
|
verbose_name=_('TLS trusted CAs'),
|
|
null=True,
|
|
blank=True)
|
|
verify_cert = models.BooleanField(
|
|
verbose_name=_('TLS verify certificates'),
|
|
default=True,
|
|
blank=True)
|
|
http_proxy = models.CharField(
|
|
max_length=128,
|
|
verbose_name=_('HTTP and HTTPS proxy'),
|
|
blank=True)
|
|
|
|
class Meta:
|
|
abstract = True
|