303 lines
10 KiB
Python
303 lines
10 KiB
Python
# passerelle - uniform access to multiple data sources and services
|
|
# Copyright (C) 2019 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# 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 collections
|
|
import json
|
|
import re
|
|
|
|
from django import template
|
|
from django.contrib.auth import get_permission_codename
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
|
from django.template.defaultfilters import stringfilter
|
|
from django.utils.html import format_html, mark_safe
|
|
from django.utils.translation import gettext as _
|
|
|
|
from passerelle.utils import get_trusted_services
|
|
|
|
from ..models import AccessRight, ResourceLog
|
|
|
|
register = template.Library()
|
|
|
|
|
|
@register.inclusion_tag('passerelle/includes/access-rights-table.html', takes_context=True)
|
|
def access_rights_table(context, resource, permission):
|
|
resource_type = ContentType.objects.get_for_model(resource)
|
|
rights = AccessRight.objects.filter(
|
|
resource_type=resource_type, resource_pk=resource.id, codename=permission
|
|
)
|
|
context['permission'] = permission
|
|
context['access_rights_list'] = rights
|
|
context['resource_type'] = resource_type.id
|
|
context['resource_pk'] = resource.id
|
|
context['trusted_services'] = get_trusted_services()
|
|
return context
|
|
|
|
|
|
@register.filter(name='resource_type')
|
|
def as_resource_type(resource):
|
|
return ContentType.objects.get_for_model(resource).id
|
|
|
|
|
|
@register.inclusion_tag('passerelle/includes/resource-logs-table.html', takes_context=True)
|
|
def resource_logs_table(context, resource):
|
|
request = context.get('request')
|
|
page = request.GET.get('page', 1)
|
|
|
|
connector = resource.get_connector_slug()
|
|
context['connector'] = connector
|
|
context['slug'] = resource.slug
|
|
qs = ResourceLog.objects.filter(appname=connector, slug=resource.slug).order_by('-timestamp')
|
|
|
|
paginator = Paginator(qs, 10)
|
|
try:
|
|
logrecords = paginator.page(page)
|
|
except PageNotAnInteger:
|
|
logrecords = paginator.page(1)
|
|
except (EmptyPage,):
|
|
logrecords = paginator.page(paginator.num_pages)
|
|
|
|
context['logrecords'] = logrecords
|
|
return context
|
|
|
|
|
|
@register.inclusion_tag('passerelle/includes/resource-jobs-table.html', takes_context=True)
|
|
def resource_jobs_table(context, resource):
|
|
request = context.get('request')
|
|
page = request.GET.get('page', 1)
|
|
|
|
qs = resource.jobs_set().order_by('-creation_timestamp')
|
|
|
|
paginator = Paginator(qs, 10)
|
|
try:
|
|
jobs = paginator.page(page)
|
|
except PageNotAnInteger:
|
|
jobs = paginator.page(1)
|
|
except (EmptyPage,):
|
|
jobs = paginator.page(paginator.num_pages)
|
|
|
|
context['jobs'] = jobs
|
|
return context
|
|
|
|
|
|
@register.filter
|
|
def can_edit(obj, user):
|
|
return user.has_perm(get_permission_codename('change', obj._meta), obj=obj)
|
|
|
|
|
|
@register.filter
|
|
def can_delete(obj, user):
|
|
return user.has_perm(get_permission_codename('delete', obj._meta), obj=obj)
|
|
|
|
|
|
@register.filter
|
|
@stringfilter
|
|
def censor(string):
|
|
return re.sub(r'://([^/]*):([^/]*?)@', r'://\1:***@', string)
|
|
|
|
|
|
def render_json_schema(schema):
|
|
if not isinstance(schema, dict):
|
|
if schema is True:
|
|
return mark_safe('<em>%s</em>') % _('always valid')
|
|
if schema is False:
|
|
return mark_safe('<em>%s</em>') % _('always invalid')
|
|
return format_html('<tt>{!r}</tt>', schema)
|
|
|
|
def many_of(name, schemas):
|
|
s = format_html('<b>{}</b>', name)
|
|
parts = [render_json_schema(schema) for schema in schemas]
|
|
if any('\n' in part for part in parts):
|
|
s += '<ul>'
|
|
for part in parts:
|
|
s += format_html('<li>{0}</li>\n', part)
|
|
s += '</ul>'
|
|
else:
|
|
s += ' [ ' + ' | '.join(parts) + ' ]'
|
|
return mark_safe(s)
|
|
|
|
def html_type(s):
|
|
return '<span class="type">%s</span>' % s
|
|
|
|
if 'anyOf' in schema:
|
|
return many_of('anyOf', schema['anyOf'])
|
|
|
|
if 'oneOf' in schema and not schema.get('type') == 'object':
|
|
return many_of('oneOf', schema['oneOf'])
|
|
|
|
if 'allOf' in schema:
|
|
return many_of('allOf', schema['allOf'])
|
|
|
|
original_schema = schema
|
|
schema = schema.copy()
|
|
schema.pop('$schema', None)
|
|
schema.pop('$id', None)
|
|
title = schema.pop('title', None)
|
|
description = schema.pop('description', None)
|
|
typ = schema.pop('type', None)
|
|
if typ == 'null':
|
|
return mark_safe(html_type('null'))
|
|
if typ == 'string':
|
|
enum = schema.pop('enum', [])
|
|
min_length = schema.pop('minLength', '')
|
|
max_length = schema.pop('maxLength', '')
|
|
pattern = schema.pop('pattern', '')
|
|
pattern_description = schema.pop('pattern_description', '')
|
|
if enum:
|
|
enum = mark_safe(' | '.join([format_html('<tt>{}</tt>', json.dumps(el)) for el in enum]))
|
|
s = 'string'
|
|
if max_length or min_length:
|
|
s += format_html('[{0}:{1}]', min_length, max_length)
|
|
s = html_type(s)
|
|
if enum:
|
|
s += ' %s' % enum
|
|
if pattern_description:
|
|
s += format_html(' <em>{}</em>', pattern_description)
|
|
elif pattern:
|
|
s += format_html(' /<tt>{}</tt>/', pattern)
|
|
if schema:
|
|
s += format_html('\n{!r}', schema)
|
|
return mark_safe(s)
|
|
if typ == 'integer':
|
|
if not schema:
|
|
return mark_safe(html_type('integer'))
|
|
if typ == 'number':
|
|
if not schema:
|
|
return mark_safe(html_type('number'))
|
|
if typ == 'array':
|
|
s = html_type('array') + ' '
|
|
if 'items' in schema:
|
|
s += render_json_schema(schema['items'])
|
|
return mark_safe(s)
|
|
if typ == 'object':
|
|
s = html_type('object')
|
|
unflatten = schema.pop('unflatten', False)
|
|
merge_extra = schema.pop('merge_extra', False)
|
|
properties = schema.pop('properties', {})
|
|
pattern_properties = schema.pop('patternProperties', {})
|
|
required_keys = schema.pop('required', [])
|
|
additional_properties = schema.pop('additionalProperties', True)
|
|
one_of = schema.pop('oneOf', [])
|
|
if unflatten:
|
|
s += format_html(', <em class="unflatten">{}</em>', _('unflatten'))
|
|
if merge_extra:
|
|
s += format_html(', <em class="merge-extra">{}</em>', _('merge extra'))
|
|
if not additional_properties:
|
|
s += format_html(
|
|
', <em class="additional-properties-false">{}</em>', _('no additional properties')
|
|
)
|
|
if title:
|
|
s += format_html(', <em class="title">{}</em>', title)
|
|
if schema:
|
|
s += format_html('<tt class="raw">{!r}</tt>', schema)
|
|
if description:
|
|
s += format_html('\n<p class="description">{}</p>', description)
|
|
s += ' '
|
|
|
|
def render_property_schema(key, html, sub):
|
|
nonlocal s
|
|
|
|
required = key in required_keys
|
|
sub_description = sub.pop('description', '')
|
|
sub_title = sub.pop('title', '')
|
|
s += format_html('<li>{0}', html)
|
|
if required:
|
|
s += format_html('<span title="{}" class="required">*</span>', _('required'))
|
|
if description or sub:
|
|
s += ' :'
|
|
if sub_title:
|
|
s += format_html(' <em>{0}</em>', sub_title)
|
|
elif sub_description and '\n' not in sub_description:
|
|
s += format_html(' <em>{0}</em>', sub_description)
|
|
if sub_title or '\n' in sub_description:
|
|
s += format_html('\n<p class="description">{}</p>', sub_description)
|
|
if sub:
|
|
s += format_html('\n{0}', render_json_schema(sub))
|
|
s += '</li>'
|
|
|
|
if properties or pattern_properties:
|
|
s += '\n<ul>'
|
|
|
|
if properties:
|
|
keys = properties
|
|
if not isinstance(properties, collections.OrderedDict):
|
|
keys = sorted(properties, key=lambda key: key.lower())
|
|
for key in keys:
|
|
sub = properties.get(key, {}).copy()
|
|
render_property_schema(key, format_html('<tt>{0}</tt>', key), sub)
|
|
if one_of and one_of[0].get('required'):
|
|
s += many_of('oneOf', one_of)
|
|
|
|
if pattern_properties:
|
|
s += format_html('<li><span>{0}</span>', _('Pattern properties'))
|
|
s += '\n<ul>'
|
|
for key, sub in pattern_properties.items():
|
|
if key:
|
|
pattern_key = format_html('/<tt>{0}</tt>/', key)
|
|
else:
|
|
pattern_key = format_html('<em>any</em>')
|
|
render_property_schema(key, pattern_key, sub)
|
|
s += '</ul>'
|
|
|
|
if properties or pattern_properties:
|
|
s += '</ul>'
|
|
|
|
return mark_safe(s)
|
|
if typ == 'boolean':
|
|
if not schema:
|
|
return mark_safe(html_type('boolean'))
|
|
enum = schema.pop('enum', [])
|
|
if enum and not schema:
|
|
return mark_safe(' | '.join([format_html('<tt>{}</tt>', json.dumps(el)) for el in enum]))
|
|
required_keys = schema.pop('required', [])
|
|
if required_keys and not schema:
|
|
return format_html('<em>{0} {1!r}</em>', _('required'), ', '.join(required_keys))
|
|
return format_html('<em>{0} {1!r}</em>', _('unknown validation'), original_schema)
|
|
|
|
|
|
@register.simple_tag(takes_context=False)
|
|
def render_body_schemas(body_schemas):
|
|
if not body_schemas:
|
|
return ''
|
|
|
|
s = mark_safe('<ul>')
|
|
for key in body_schemas:
|
|
if key == 'application/json':
|
|
s += mark_safe('<li><tt>application/json</tt> : <span class="json-schema">')
|
|
s += render_json_schema(body_schemas['application/json'])
|
|
s += mark_safe('</span></li>')
|
|
else:
|
|
s += format_html('<li><tt>{0}</tt></li>', key)
|
|
s += mark_safe('<ul>')
|
|
return mark_safe(s)
|
|
|
|
|
|
@register.filter(name='get')
|
|
def get(obj, key):
|
|
try:
|
|
return obj.get(key)
|
|
except AttributeError:
|
|
try:
|
|
return obj[key]
|
|
except (IndexError, KeyError, TypeError):
|
|
return None
|
|
|
|
|
|
@register.filter
|
|
def json_dumps(d):
|
|
return json.dumps(d, indent=2)
|