PK!su̎spkcspider/__init__.py__all__ = ("celery_app", "settings", "urls", "wsgi") try: from .celery import app as celery_app except ImportError: celery_app = None PK!^0S>DD"spkcspider/apps/spider/__init__.pydefault_app_config = 'spkcspider.apps.spider.apps.SpiderBaseConfig' PK!v6"6"spkcspider/apps/spider/admin.pyfrom django.contrib import admin from django.conf import settings from .models import ( Protection, AssignedProtection, UserComponent, AssignedContent, ContentVariant, UserInfo ) @admin.register(AssignedContent) class AssignedContentAdmin(admin.ModelAdmin): fields = ['info', 'created', 'modified', 'deletion_requested', 'token'] readonly_fields = [ 'info', 'created', 'modified' ] def has_module_permission(self, request): return True def has_delete_permission(self, request, obj=None): n = request.user._meta.app_label m = request.user._meta.model_name if not request.user.is_active: return False # not obj allows deletion of users if not obj or request.user.is_superuser: return True # obj is UserComponent if obj.usercomponent.name == "index": return request.user.has_perm("{}.delete_{}".format(n, m)) return request.user.has_perm("spider_base.delete_assignedcontent") def has_view_permission(self, request, obj=None): if request.user.is_superuser: return True # obj is UserComponent if not obj or obj.usercomponent.name == "index": return False return request.user.has_perm("spider_base.view_assignedcontent") def has_change_permission(self, request, obj=None): if not request.user.is_active: return False # obj is UserComponent if not obj or obj.usercomponent.name == "index": return False if request.user.is_superuser: return True return request.user.has_perm("spider_base.change_assignedcontent") def has_add_permission(self, request, obj=None): return False class AssignedProtectionInline(admin.TabularInline): model = AssignedProtection # protection field is not possible (and shouldn't, can corrupt data) # data is too dangerous, admin should not have access to e.g. pws fields = [ 'created', 'modified', 'active', 'instant_fail' ] readonly_fields = ['created', 'modified'] fk_name = 'usercomponent' extra = 0 def has_add_permission(self, request, obj=None): # adding protection doesn't make sense without data return False class ContentInline(admin.TabularInline): model = AssignedContent fields = [ 'info', 'created', 'modified', 'deletion_requested', 'token' ] readonly_fields = [ 'info', 'created', 'modified' ] # until fixed: readonly_fields += ['deletion_requested', 'token'] show_change_link = True extra = 0 # content is not visible def has_delete_permission(self, request, obj=None): n = request.user._meta.app_label m = request.user._meta.model_name if not request.user.is_active: return False # not obj allows deletion of users if not obj or request.user.is_superuser: return True # obj is UserComponent if obj.name == "index": return request.user.has_perm("{}.delete_{}".format(n, m)) return request.user.has_perm("spider_base.delete_usercontent") def has_view_permission(self, request, obj=None): if request.user.is_superuser: return True # obj is UserComponent if not obj or obj.name == "index": return False return request.user.has_perm("spider_base.view_usercontent") def has_change_permission(self, request, obj=None): return False def has_add_permission(self, request, obj=None): return False @admin.register(UserComponent) class UserComponentAdmin(admin.ModelAdmin): inlines = [ ContentInline, AssignedProtectionInline, ] actions = ["feature", "unfeature"] fields = [ 'user', 'name', 'created', 'modified', 'description', 'features', 'can_auth', 'allow_domain_mode', 'featured', 'public', 'strength', 'token', 'required_passes', 'deletion_requested', 'token_duration' ] readonly_fields = [ 'created', 'modified', 'featured', 'strength', 'can_auth', 'allow_domain_mode' ] list_display = ('name', 'username', 'strength', 'featured', 'modified') view_on_site = True def has_module_permission(self, request): return True def feature(self, request, queryset): queryset.exclude( name__in=("index", "fake_index") ).exclude(public=False).update(featured=True) feature.allowed_permissions = ('can_feature',) def unfeature(self, request, queryset): queryset.update(featured=False) unfeature.allowed_permissions = ('can_feature',) def has_can_feature_permission(self, request, obj=None): if not request.user.is_active: return False if request.user.is_superuser: return True return request.user.has_perm("spider_base.can_feature") def has_add_permission(self, request, obj=None): if not request.user.is_active: return False if request.user.is_superuser: return True return request.user.has_perm("spider_base.add_usercomponent") def has_delete_permission(self, request, obj=None): n = request.user._meta.app_label m = request.user._meta.model_name if not request.user.is_active: return False if request.user.is_superuser: return True # you must be able to delete user to delete index component if obj and obj.name == "index": if not request.user.has_perm("{}.delete_{}".format(n, m)): return False return request.user.has_perm("spider_base.delete_usercomponent") def has_view_permission(self, request, obj=None): if not request.user.is_active: return False if request.user.is_superuser: return True if obj and obj.name == "index" and obj.user != request.user: return False return request.user.has_perm("spider_base.view_usercomponent") def has_change_permission(self, request, obj=None): if not request.user.is_active: return False if request.user.is_superuser: return True if obj and obj.name == "index" and obj.user != request.user: return False return request.user.has_perm("spider_base.change_usercomponent") @admin.register(Protection) class ProtectionAdmin(admin.ModelAdmin): view_on_site = False fields = ['code'] def has_module_permission(self, request): return True def has_view_permission(self, request, obj=None): return True def has_change_permission(self, request, obj=None): if not request.user.is_active: return False return request.user.is_superuser or request.user.is_staff def has_add_permission(self, request, obj=None): if not request.user.is_active: return False return request.user.is_superuser or request.user.is_staff def has_delete_permission(self, request, obj=None): if not request.user.is_active: return False return request.user.is_superuser or request.user.is_staff @admin.register(ContentVariant) class ContentVariantAdmin(ProtectionAdmin): fields = ['name'] @admin.register(UserInfo) class UserInfoAdmin(admin.ModelAdmin): view_on_site = False fields = [] def get_actions(self, request): actions = super().get_actions(request) if 'delete_selected' in actions: del actions['delete_selected'] return actions def has_view_permission(self, request, obj=None): if not request.user.is_active: return False return request.user.is_superuser or request.user.is_staff def has_change_permission(self, request, obj=None): if not request.user.is_active: return False # not obj allows deletion of users if not obj: return True return False def has_add_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): n = request.user._meta.app_label m = request.user._meta.model_name if not request.user.is_active: return False # not obj allows deletion of users if not obj: return True return request.user.has_perm("{}.delete_{}".format(n, m)) if 'django.contrib.flatpages' in settings.INSTALLED_APPS: from django.contrib.flatpages.admin import FlatPageAdmin from .forms.flatpages import FlatpageItemForm FlatPageAdmin.form = FlatpageItemForm PK!i z spkcspider/apps/spider/apps.py__all__ = ["SpiderBaseConfig"] from django.apps import AppConfig from django.db.models.signals import ( post_migrate, post_save, pre_save, post_delete ) from django.contrib.auth.signals import user_logged_out from django.contrib.auth import get_user_model from django.conf import settings from .helpers import extract_app_dicts from .signals import ( UpdateSpiderCb, InitUserCb, UpdateAnchorContent, UpdateAnchorComponent, update_dynamic, TriggerUpdate, RemoveTokensLogout, CleanupCb, MovePersistentCb, move_persistent, DeleteContentCb, UpdateAnchorTargets ) class SpiderBaseConfig(AppConfig): name = 'spkcspider.apps.spider' label = 'spider_base' spider_url_path = 'spider/' verbose_name = 'spkcspider base' def ready(self): from .models import ( AssignedContent, UserComponent, AuthToken, LinkContent ) from django.apps import apps from .protections import installed_protections for app in apps.get_app_configs(): installed_protections.update( extract_app_dicts(app, "spider_protections", "name") ) user_logged_out.connect( RemoveTokensLogout, dispatch_uid="delete_token_logout" ) pre_save.connect( UpdateAnchorComponent, sender=UserComponent, dispatch_uid="spider_update_anchors_component" ) pre_save.connect( UpdateAnchorTargets, sender=UserComponent, dispatch_uid="spider_update_anchors_targets" ) post_save.connect( UpdateAnchorContent, sender=AssignedContent, dispatch_uid="spider_update_anchors_content" ) move_persistent.connect( MovePersistentCb, sender=AuthToken, dispatch_uid="spider_move_persistent" ) # order important for next two post_delete.connect( CleanupCb, sender=UserComponent, dispatch_uid="spider_delete_cleanup_usercomponent" ) post_delete.connect( CleanupCb, sender=AssignedContent, dispatch_uid="spider_delete_cleanup_content" ) post_delete.connect( DeleteContentCb, sender=LinkContent, dispatch_uid="spider_delete_cleanup_linkcontent" ) ##################### post_save.connect( InitUserCb, sender=get_user_model(), dispatch_uid="setup_spider_user" ) update_dynamic.connect( UpdateSpiderCb, dispatch_uid="update_spider_dynamic" ) if getattr(settings, "UPDATE_DYNAMIC_AFTER_MIGRATION", True): post_migrate.connect( TriggerUpdate, sender=self, dispatch_uid="update_spider_base_trigger" ) PK!,]]spkcspider/apps/spider/auth.pyfrom django.contrib.auth.backends import ModelBackend from django.http import Http404 from django.utils import timezone from .models import UserComponent, Protection, TravelProtection, AuthToken from .constants import ProtectionType, TravelLoginType class SpiderTokenAuthBackend(ModelBackend): def authenticate(self, request, username=None, protection_codes=None, **kwargs): """ Use protections for authentication""" tokenstring = request.GET.get("token", None) if not tokenstring: return travel = TravelProtection.objects.get_active().filter( associated_rel__usercomponent__user__username=username ).exclude(login_protection=TravelLoginType.none.value) is_fake = False if travel.exists(): uc = UserComponent.objects.filter( user__username=username, name="fake_index" ).first(), is_fake = True else: uc = UserComponent.objects.filter( user__username=username, name="index" ).first() expire = timezone.now()-uc.token_duration # delete old token, so no confusion happen AuthToken.objects.filter( usercomponent=uc, created__lt=expire ).delete() if AuthToken.objects.filter( usercomponent=uc, token=tokenstring ).exists(): request.session["is_fake"] = is_fake return uc.user class SpiderAuthBackend(ModelBackend): def authenticate(self, request, username=None, protection_codes=None, nospider=False, **kwargs): """ Use protections for authentication""" # disable SpiderAuthBackend backend (against recursion) if nospider: return travel = TravelProtection.objects.get_active().filter( associated_rel__usercomponent__user__username=username ).exclude(login_protection=TravelLoginType.none.value) uc = UserComponent.objects.filter( user__username=username, name="index" ).first() uc_fake = None if travel.exists(): uc_fake = UserComponent.objects.filter( user__username=username, name="fake_index" ).first() try: if not uc: request.protections = Protection.authall( request, scope="auth", ptype=ProtectionType.authentication.value, protection_codes=protection_codes ) if type(request.protections) is int: # should never happen return None else: if uc_fake: request.protections = uc_fake.auth( request, scope="auth", ptype=ProtectionType.authentication.value, protection_codes=protection_codes ) if type(request.protections) is int: request.session["is_fake"] = True return uc_fake.user # don't overwrite request.protections yet to serve fake version # in case the real login doesn't work either protections = uc.auth( request, scope="auth", ptype=ProtectionType.authentication.value, protection_codes=protection_codes ) if type(protections) is int: request.protections = protections request.session["is_fake"] = False return uc.user # there was no fake so set protections if not uc_fake: request.protections = protections except Http404: # for Http404 auth abort by protections (e.g. Random Fail) pass PK!z spkcspider/apps/spider/conf.py__all__ = [ "media_extensions", "image_extensions", "get_anchor_domain", "INITIAL_STATIC_TOKEN_SIZE", "STATIC_TOKEN_CHOICES", "default_uctoken_duration", "force_captcha", "VALID_INTENTIONS", "VALID_SUB_INTENTIONS" ] import datetime from django.conf import settings from django.utils.translation import gettext_lazy as _ # webbrowser supported image formats image_extensions = set(getattr( settings, "SPIDER_IMAGE_EXTENSIONS", { "jpg", "jpeg", "bmp", "png", "ico", "svg", "gif", "webp" } )) media_extensions = set(getattr( settings, "SPIDER_MEDIA_EXTENSIONS", { "mp4", "ogg", "flac", "mp3", "webm", "wav", "avi" } )) # if None, set to default Site ID if models are ready _anchor_domain = getattr( settings, "SPIDER_ANCHOR_DOMAIN", None ) def get_anchor_domain(): global _anchor_domain if _anchor_domain: return _anchor_domain from django.contrib.sites.models import Site _anchor_domain = \ Site.objects.get(id=settings.SITE_ID).domain return _anchor_domain INITIAL_STATIC_TOKEN_SIZE = str( getattr(settings, "SPIDER_INITIAL_STATIC_TOKEN_SIZE", 12) ) STATIC_TOKEN_CHOICES = [ ("", ""), (INITIAL_STATIC_TOKEN_SIZE, _("default ({} Bytes)")), ("3", _("low ({} Bytes)")), ("12", _("medium ({} Bytes)")), ("30", _("high ({} Bytes)")), ] VALID_INTENTIONS = set(getattr( settings, "SPIDER_VALID_INTENTIONS", {"auth", "domain", "live", "login", "payment", "persist", "sl"} )) VALID_SUB_INTENTIONS = {"sl", "live"} force_captcha = getattr(settings, "REQUIRE_LOGIN_CAPTCHA", False) # require USE_CAPTCHAS force_captcha = (force_captcha and getattr(settings, "USE_CAPTCHAS", False)) default_uctoken_duration = getattr( settings, "DEFAULT_UCTOKEN_DURATION", datetime.timedelta(days=7) ) PK!. #spkcspider/apps/spider/constants.py __all__ = ( "ProtectionType", "VariantType", "ProtectionResult", "TravelLoginType", "MAX_TOKEN_SIZE", "MAX_TOKEN_B64_SIZE", "hex_size_of_bigid", "TokenCreationError", "index_names", "protected_names", "spkcgraph", "dangerous_login_choices", "ActionUrl" ) import enum from collections import namedtuple from rdflib.namespace import Namespace spkcgraph = Namespace("https://spkcspider.net/static/schemes/spkcgraph#") # Literal allows arbitary datatypes, use this and don't bind hex_size_of_bigid = 16 MAX_TOKEN_SIZE = 90 if MAX_TOKEN_SIZE % 3 != 0: raise Exception("MAX_TOKEN_SIZE must be multiple of 3") MAX_TOKEN_B64_SIZE = MAX_TOKEN_SIZE*4//3 class TokenCreationError(Exception): pass ProtectionResult = namedtuple("ProtectionResult", ["result", "protection"]) ActionUrl = namedtuple("ActionUrl", ["url", "name"]) index_names = ["index", "fake_index"] protected_names = ["index", "fake_index"] class ProtectionType(str, enum.Enum): # receives: request, scope access_control = "a" # receives: request, scope, password authentication = "b" # c: former reliable, no meaning # protections which does not contribute to required_passes (404 only) no_count = "d" # protections which have side effects side_effects = "e" # forget about recovery, every recovery method is authentication # and will be misused this way # The only real recovery is by staff and only if there is a secret class VariantType(str, enum.Enum): # a not assigned # use persistent token # can be used for some kind of federation # warning: if token is deleted persisted content is deleted persist = "b" # update content is without form/for form updates it is not rendered # required for still beeing able to update elemental parameters raw_update = "c" # raw_add not required, archieved by returning response # don't list as contentvariant for user (for computer only stuff) # works like unlisted in info field, just for ContentVariants # don't appear in allowed_content in contrast to feature # => should either depend on feature or normal content unlisted = "d" # activates domain mode domain_mode = "e" # allow outside applications push content to spiders # adds by default unlisted attribute # appears in features of userComponent # counts as foreign content # don't list as content variant for user component_feature = "f" # same, but assigns to contents content_feature = "g" # is content unique for usercomponent # together with strength level 10: unique for user unique = "h" # can be used as anchor, hash is automatically embedded anchor = "i" class TravelLoginType(str, enum.Enum): # no protection, login works as usual none = "a" # experimental, creates a fake subset view fake_login = "b" # wipe travel protected data and index as soon as login occurs # Note: noticable if shared contents are removed wipe = "c" # wipe user, self destruct user on login, Note: maybe noticable wipe_user = "d" dangerous_login_choices = ( TravelLoginType.fake_login.value, TravelLoginType.wipe.value, TravelLoginType.wipe_user.value ) PK!9KDjDj"spkcspider/apps/spider/contents.py __all__ = ( "add_content", "installed_contents", "BaseContent" ) import logging from urllib.parse import urljoin from functools import lru_cache from django.utils.html import escape from django.apps import apps as django_apps from django.db import models, transaction from django.utils.translation import gettext from django.template.loader import render_to_string from django.core.files.base import File from django.core.exceptions import NON_FIELD_ERRORS, ValidationError from django.http import Http404 from django.db.utils import IntegrityError from django.middleware.csrf import CsrfViewMiddleware from django.contrib.contenttypes.fields import GenericRelation from django.http.response import HttpResponseBase, HttpResponse from django.conf import settings from django.utils.translation import pgettext from django.contrib import messages from django.views.decorators.csrf import csrf_exempt from rdflib import Literal, Graph, BNode, URIRef, XSD from .constants import VariantType, spkcgraph, ActionUrl from .conf import get_anchor_domain from .serializing import paginate_stream, serialize_stream from .helpers import ( get_settings_func, add_property, create_b64_id_token, merge_get_url ) from .templatetags.spider_rdf import literalize installed_contents = {} # don't spam set objects _empty_set = frozenset() default_abilities = frozenset( ("add", "view", "update", "export", "list", "raw", "raw_update") ) # never use these names forbidden_names = ["Content", "UserComponent"] # updated attributes of ContentVariant _attribute_list = ["name", "ctype", "strength"] def add_content(klass): code = klass._meta.model_name if code in installed_contents: raise Exception("Duplicate content") if "{}.{}".format(klass.__module__, klass.__qualname__) not in getattr( settings, "SPIDER_BLACKLISTED_MODULES", _empty_set ): installed_contents[code] = klass return klass def initialize_content_models(apps=None): if not apps: apps = django_apps ContentVariant = apps.get_model("spider_base", "ContentVariant") all_content = models.Q() for code, val in installed_contents.items(): appearances = val.appearances if callable(appearances): appearances = appearances() # update name if only one name exists update = False if len(appearances) == 1: update = True for dic in appearances: require_save = False assert dic["name"] not in forbidden_names, \ "Forbidden content name: %" % dic["name"] try: with transaction.atomic(): if update: variant = ContentVariant.objects.get_or_create( defaults=dic, code=code )[0] else: variant = ContentVariant.objects.get_or_create( defaults=dic, code=code, name=dic["name"] )[0] except IntegrityError: # renamed model = code changed variant = ContentVariant.objects.get(name=dic["name"]) variant.code = code require_save = True for key in _attribute_list: val = dic.get( key, variant._meta.get_field(key).get_default() ) if getattr(variant, key) != val: setattr(variant, key, val) require_save = True if require_save: variant.save() all_content |= models.Q(name=dic["name"], code=code) invalid_models = ContentVariant.objects.exclude(all_content) if invalid_models.exists(): print("Invalid content, please update or remove them:", ["\"{}\":{}".format(t.code, t.name) for t in invalid_models]) class BaseContent(models.Model): # consider not writing admin wrapper for (sensitive) inherited content # this way content could be protected to be only visible to admin, user # and legitimated users (if not index) # iterable or callable returning iterable containing dicts # required keys: name # optional: strength (default=0), ctype (default="") # max name length is 50 chars # max ctype length is 10 chars (10 attributes) # use case: addons for usercontent, e.g. dependencies on external libraries # use case: model with different abilities appearances = None id = models.BigAutoField(primary_key=True, editable=False) # every content can specify its own deletion period deletion_period = getattr( settings, "SPIDER_CONTENTS_DEFAULT_DELETION_PERIOD", None ) # if created associated is None (will be set later) # use usercomponent in form instead associated_rel = GenericRelation("spider_base.AssignedContent") _associated_tmp = None # user can set name # if set to "force" name will be enforced expose_name = True # user can set description expose_description = False associated_errors = None _content_is_cleaned = False @property def associated(self): if self._associated_tmp: return self._associated_tmp return self.associated_rel.first() class Meta: abstract = True default_permissions = [] @classmethod def static_create( cls, *, associated=None, associated_kwargs=None, content_kwargs=None, token_size=None ): if content_kwargs: ob = cls(**content_kwargs) else: ob = cls() if associated: ob._associated_tmp = associated else: from .models import AssignedContent ob = cls() if not associated_kwargs: associated_kwargs = {} ob._associated_tmp = AssignedContent(**associated_kwargs) ob._associated_tmp.content = ob if token_size is not None: ob._associated_tmp.token_generate_new_size = token_size return ob @classmethod def localize_name(cls, name): return pgettext("content name", name) def __str__(self): if not self.id: return self.localize_name(self.associated.ctype.name) else: return self.associated.name def __repr__(self): return "".format( self.associated.usercomponent.username, self.__str__() ) def get_size(self): # 255 = length name no matter what encoding s = 255 if self.expose_description: s += len(self.associated.description) return s def get_priority(self): return 0 @classmethod def feature_urls(cls, name): """ For implementing component features """ return [] @classmethod @lru_cache(typed=True) def cached_feature_urls(cls, name): return list(map( lambda x: ActionUrl(*x), cls.feature_urls(name) )) def get_content_name(self): return "{}_{}".format( self.localize_name(self.associated.ctype.name), self.associated.id ) def get_content_description(self): return " " def localized_description(self): """ localize and perform other transforms before rendering to user """ if not self.expose_description: return self.associated.description return gettext(self.associated.description) def get_strength(self): """ get required strength """ return self.associated.ctype.strength def get_strength_link(self): """ get required strength for links """ return self.get_strength() def get_abilities(self, context): """ Override for declaring content extra abilities """ return set() def get_instance_form(self, context): return self def get_form_kwargs( self, request, instance=None, disable_data=False, **kwargs ): """Return the keyword arguments for instantiating the form.""" fkwargs = {} if instance is None: fkwargs["instance"] = self.get_instance_form(kwargs) elif instance: fkwargs["instance"] = instance if not disable_data and request.method in ('POST', 'PUT'): fkwargs.update({ 'data': request.POST, 'files': request.FILES, }) return fkwargs # default scopes: # public accessible: # raw: view with raw parameter # list: raw but embedded in content list # view: view of conten without raw parameter # only accessible by owner and staff: # add: create view of content # update: update view of content # raw_update: special update mode for machines or really fancy updates # export: like raw just optimized for export: e.g. no dereferencing def get_form(self, scope): raise NotImplementedError def get_template_name(self, scope): if scope in ["add", "update"]: return 'spider_base/partials/base_form.html' return 'spider_base/view_form.html' def update_used_space(self, size_diff): if size_diff == 0: return f = "local" if VariantType.component_feature.value in self.associated.ctype.ctype: f = "remote" with transaction.atomic(): self.associated.usercomponent.user_info.update_with_quota( size_diff, f ) self.associated.usercomponent.user_info.save( update_fields=[ "used_space_local", "used_space_remote" ] ) def render_form(self, scope, **kwargs): _ = gettext if scope == "add": kwargs["form_empty_message"] = _("No User Input required") old_size = 0 else: old_size = self.get_size() parent_form = kwargs.get("form", None) kwargs["form"] = self.get_form(scope)( **self.get_form_kwargs( scope=scope, **kwargs ) ) if kwargs["form"].is_valid(): instance = kwargs["form"].save(False) try: self.update_used_space( instance.get_size() - old_size ) except ValidationError as exc: kwargs["form"].add_error(None, exc) messages.error( kwargs["request"], _('Space exhausted') ) if kwargs["form"].is_valid(): instance.save() kwargs["form"].save_m2m() messages.success( kwargs["request"], _('Content updated') ) kwargs["form"] = self.get_form(scope)( **self.get_form_kwargs( scope=scope, instance=instance, **kwargs ) ) elif parent_form: if len(kwargs["form"].errors.get(NON_FIELD_ERRORS, [])) > 0: parent_form.add_error( None, kwargs["form"].errors.pop(NON_FIELD_ERRORS) ) if self.associated_errors: parent_form.add_error( None, self.associated_errors ) return ( render_to_string( self.get_template_name(scope), request=kwargs["request"], context=kwargs ), kwargs["form"].media ) def get_absolute_url(self, scope="view"): return self.associated.get_absolute_url(scope) def get_primary_anchor(self, graph, context): p = self.associated.usercomponent.primary_anchor if p: return urljoin( get_anchor_domain(), p.get_absolute_url() ) if not p and self.associated.usercomponent.public: return urljoin( get_anchor_domain(), self.associated.usercomponent.get_absolute_url() ) return urljoin( get_anchor_domain(), context["request"].path ) def get_references(self): return [] def map_data(self, name, field, data, graph, context): if isinstance(data, File): return get_settings_func( "SPIDER_FILE_EMBED_FUNC", "spkcspider.apps.spider.functions.embed_file_default" )(name, data, self, context) return literalize(data, field) def serialize(self, graph, ref_content, context): form = self.get_form(context["scope"])( **self.get_form_kwargs( disable_data=True, **context ) ) graph.add(( ref_content, spkcgraph["type"], Literal(self.associated.getlist("type", 1)[0]) )) if "abilities" not in context: context["abilities"] = set(self.get_abilities(context)) for ability in context["abilities"]: assert(ability not in default_abilities) graph.add(( ref_content, spkcgraph["ability:name"], Literal(ability, datatype=XSD.string) )) for name, field in form.fields.items(): raw_value = form.initial.get(name, None) try: value = field.to_python(raw_value) except Exception as exc: # user can corrupt tags, so just debug level = logging.WARNING if getattr(form, "layout_generating_form", False): level = logging.DEBUG logging.log( level, "Corrupted field: %s, form: %s, error: %s", name, form, exc ) continue value_node = BNode() hashable = getattr(field, "hashable", False) graph.add(( ref_content, spkcgraph["properties"], value_node )) graph.add(( value_node, spkcgraph["hashable"], Literal(hashable) )) graph.add(( value_node, spkcgraph["name"], Literal(name, datatype=XSD.string) )) graph.add(( value_node, spkcgraph["fieldname"], Literal(form.add_prefix(name), datatype=XSD.string) )) if not isinstance(value, (list, tuple, models.QuerySet)): value = [value] for i in value: graph.add(( value_node, spkcgraph["value"], self.map_data(name, field, i, graph, context) )) def render_serialize(self, **kwargs): from .models import AssignedContent # ** creates copy of dict, so it is safe to overwrite kwargs here session_dict = { "request": kwargs["request"], "context": kwargs, "scope": kwargs["scope"], "hostpart": kwargs["hostpart"], "ac_namespace": spkcgraph["contents"], "sourceref": URIRef(urljoin( kwargs["hostpart"], kwargs["request"].path )) } g = Graph() g.namespace_manager.bind("spkc", spkcgraph, replace=True) p = paginate_stream( AssignedContent.objects.filter(id=self.associated.id), getattr( settings, "SPIDER_SERIALIZED_PER_PAGE", settings.SPIDER_OBJECTS_PER_PAGE ), settings.SPIDER_MAX_EMBED_DEPTH ) page = 1 try: page = int(self.request.GET.get("page", "1")) except Exception: pass serialize_stream( g, p, session_dict, page=page, embed=True ) if hasattr(kwargs["request"], "token_expires"): session_dict["expires"] = kwargs["request"].token_expires.strftime( "%a, %d %b %Y %H:%M:%S %z" ) if page <= 1: add_property( g, "token_expires", ob=session_dict["request"], ref=session_dict["sourceref"] ) if page <= 1: g.add(( session_dict["sourceref"], spkcgraph["scope"], Literal(kwargs["scope"], datatype=XSD.string) )) uc = kwargs.get("source", self.associated.usercomponent) g.add(( session_dict["sourceref"], spkcgraph["strength"], Literal(uc.strength) )) token = getattr(session_dict["request"], "auth_token", None) if token: token = token.token url2 = merge_get_url(str(session_dict["sourceref"]), token=token) g.add( ( session_dict["sourceref"], spkcgraph["action:view"], URIRef(url2) ) ) if kwargs["referrer"]: g.add(( session_dict["sourceref"], spkcgraph["referrer"], Literal(kwargs["referrer"], datatype=XSD.anyURI) )) if kwargs["token_strength"]: add_property( g, "token_strength", ref=session_dict["sourceref"], literal=kwargs["token_strength"] ) add_property( g, "intentions", ref=session_dict["sourceref"], literal=kwargs["intentions"], datatype=XSD.string, iterate=True ) ret = HttpResponse( g.serialize(format="turtle"), content_type="text/turtle;charset=utf-8" ) if session_dict.get("expires", None): ret['X-Token-Expires'] = session_dict["expires"] # allow cors requests for raw ret["Access-Control-Allow-Origin"] = "*" return ret def access_view(self, **kwargs): _ = gettext kwargs["form"] = self.get_form("view")( **self.get_form_kwargs(disable_data=True, **kwargs) ) kwargs.setdefault("legend", escape(_("View"))) # not required: done by access template # kwargs.setdefault( # "add_spkc_types", # [ # self.associated.getlist("type", 1)[0], # "Content" # ] # ) return ( render_to_string( self.get_template_name(kwargs["scope"]), request=kwargs["request"], context=kwargs ), kwargs["form"].media ) def access_add(self, **kwargs): _ = gettext kwargs.setdefault( "legend", escape(_("Add \"%s\"") % self.__str__()) ) # not visible by default kwargs.setdefault("confirm", _("Create")) # prevent second button kwargs.setdefault("inner_form", True) return self.render_form(**kwargs) def access_update(self, **kwargs): _ = gettext kwargs.setdefault( "legend", escape(_("Update \"%s\"") % self.__str__()) ) # not visible by default kwargs.setdefault("confirm", _("Update")) # prevent second button kwargs.setdefault("inner_form", True) return self.render_form(**kwargs) def access_export(self, **kwargs): kwargs["scope"] = "export" return self.render_serialize(**kwargs) def access_raw(self, **kwargs): kwargs["scope"] = "raw" return self.render_serialize(**kwargs) def access_anchor(self, **kwargs): """ Called for anchors WARNING: this means the context is completely different "access", "get_abilities" method of anchors (and links on them) must be robust WARNING: it is expected that get_abilities returns "anchor" WARNING: it is expected that access_anchor returns a HttpResponse """ raise NotImplementedError() @csrf_exempt def access_default(self, **kwargs): raise Http404() access_raw_update = access_default def access(self, context): # context is updated and used outside!! # so make sure that func gets only a copy (**) context["abilities"] = set(self.get_abilities(context)) context.setdefault("extra_outer_forms", []) func = self.access_default if context["scope"] == "view" and "raw" in context["request"].GET: func = self.access_raw elif context["scope"] in default_abilities: func = getattr(self, "access_{}".format(context["scope"])) elif context["scope"] in context["abilities"]: func = getattr(self, "access_{}".format(context["scope"])) # check csrf tokens manually if not getattr(func, "csrf_exempt", False): csrferror = CsrfViewMiddleware().process_view( context["request"], None, (), {} ) if csrferror is not None: # csrferror is HttpResponse return csrferror ret = func(**context) if context["scope"] == "update": # update function should never return HttpResponse # (except csrf error) assert(not isinstance(ret, HttpResponseBase)) return ret def get_info(self, unique=None, unlisted=None): # unique=None, feature=None shortcuts for get_info overwrites # passing down these parameters not neccessary if unique is None: unique = ( VariantType.unique.value in self.associated.ctype.ctype ) if unlisted is None: unlisted = ( VariantType.component_feature.value in self.associated.ctype.ctype or VariantType.content_feature.value in self.associated.ctype.ctype ) anchortag = "" if VariantType.anchor.value in self.associated.ctype.ctype: anchortag = "anchor\x1e" idtag = "primary\x1e" # simulates beeing not unique, by adding id if not unique: idtag = "id=\x1e" # placeholder if getattr(self.associated, "id", None): idtag = "id={}\x1e".format(self.associated.id) unlistedtag = "" if unlisted: unlistedtag = "unlisted\x1e" return "\x1etype={}\x1e{}{}{}".format( self.associated.ctype.name, idtag, anchortag, unlistedtag ) def full_clean(self, **kwargs): # checked with clean kwargs.setdefault("exclude", []).append("associated_rel") return super().full_clean(**kwargs) def clean(self): _ = gettext if self._associated_tmp: self._associated_tmp.content = self assignedcontent = self.associated assignedcontent.info = self.get_info() if getattr(self, "id", None): if not self.expose_name or not assignedcontent.name: assignedcontent.name = self.get_content_name() if not self.expose_description or not assignedcontent.description: assignedcontent.description = self.get_content_description() assignedcontent.priority = self.get_priority() assignedcontent.strength = self.get_strength() assignedcontent.strength_link = self.get_strength_link() try: assignedcontent.full_clean(exclude=["content"]) except ValidationError as exc: self.associated_errors = exc # persist AssignedContent for saving self._associated_tmp = assignedcontent if self.associated_errors: raise ValidationError( _('AssignedContent validation failed'), code="assigned_content" ) self._content_is_cleaned = True def save(self, *args, **kwargs): if settings.DEBUG: assert self._content_is_cleaned, "try to save uncleaned content" super().save(*args, **kwargs) assignedcontent = self.associated if not assignedcontent.content: # add requires this assignedcontent.content = self assignedcontent.info = self.get_info() assignedcontent.strength = self.get_strength() assignedcontent.strength_link = self.get_strength_link() created = False if not getattr(assignedcontent, "id", None): created = True # update info and set content assignedcontent.save() if created: to_save = set() # add id to info if "\x1eprimary\x1e" not in assignedcontent.info: assignedcontent.info = assignedcontent.info.replace( "\x1eid=\x1e", "\x1eid={}\x1e".format( assignedcontent.id ), 1 ) to_save.add("info") if ( assignedcontent.token_generate_new_size is not None and not assignedcontent.token ): assignedcontent.token_generate_new_size = \ getattr(settings, "TOKEN_SIZE", 30) # set token if assignedcontent.token_generate_new_size is not None: if assignedcontent.token: print( "Old nonce for Content id:", assignedcontent.id, "is", assignedcontent.token ) assignedcontent.token = create_b64_id_token( assignedcontent.id, "/", assignedcontent.token_generate_new_size ) assignedcontent.token_generate_new_size = None to_save.add("token") if not self.expose_name or not assignedcontent.name: assignedcontent.name = self.get_content_name() to_save.add("name") if not self.expose_description or not assignedcontent.description: assignedcontent.description = self.get_content_description() to_save.add("description") # second save required if to_save: assignedcontent.save(update_fields=to_save) # needs id first s = set(assignedcontent.attached_contents.all()) s.update(self.get_references()) assignedcontent.references.set(s) # delete saved errors self.associated_errors = None # require cleaning again self._content_is_cleaned = False PK!g ,spkcspider/apps/spider/context_processors.py__all__ = ("settings",) from django.conf import settings as _settings def settings(request): return { 'SETTINGS': _settings, } PK!R Tokens protect against bruteforce and attackers
If you have problems with attackers (because they know the token), you can invalidate it with this option and/or add protections
Warning:this removes also access for all services you gave the link
Warning:public user components disclose static token, regenerate after removing public attribute from component for restoring protection
""") # noqa: E501 _help_text_features_components = _(""" Note: persistent Features (=features which use a persistent token) require and enable "Persistence" """) # noqa: E501 _help_text_features_contents = _(""" Note: persistent Features (=features which use a persistent token) require active "Persistence" Feature on usercomponent They are elsewise excluded """) # noqa: E501 class UserComponentForm(forms.ModelForm): protections = None new_static_token = forms.ChoiceField( label=_("New static token"), help_text=_help_text_static_token, required=False, initial="", choices=STATIC_TOKEN_CHOICES ) class Meta: model = UserComponent fields = [ 'name', 'description', 'public', 'featured', 'features', 'default_content_features', 'primary_anchor', 'required_passes', 'token_duration' ] error_messages = { NON_FIELD_ERRORS: { 'unique_together': _('Name of User Component already exists') } } help_texts = { 'features': _help_text_features_components, } widgets = { 'features': forms.CheckboxSelectMultiple(), 'default_content_features': forms.CheckboxSelectMultiple(), 'description': forms.Textarea( attrs={ "maxlength": settings.SPIDER_MAX_DESCRIPTION_LENGTH+1 } ), 'primary_anchor': Select2Widget( allow_multiple_selected=False, attrs={ "style": "min-width: 150px; width:100%" } ), } def __init__(self, request, data=None, files=None, auto_id='id_%s', prefix=None, *args, **kwargs): super().__init__( *args, data=data, files=files, auto_id=auto_id, prefix=prefix, **kwargs ) # self.fields["allow_domain_mode"].disabled = True self.fields["new_static_token"].choices = map( lambda c: (c[0], c[1].format(c[0])), self.fields["new_static_token"].choices ) if not ( request.user.is_superuser or request.user.has_perm('spider_base.can_feature') ) or request.session.get("is_fake", False): self.fields['featured'].disabled = True self.fields["features"].queryset = ( self.fields["features"].queryset & request.user.spider_info.allowed_content.all() ).order_by("name") self.fields["default_content_features"].queryset = ( self.fields["default_content_features"].queryset & request.user.spider_info.allowed_content.all() ).order_by("name") if self.instance and self.instance.id: assigned = self.instance.protections if self.instance.name in protected_names: self.fields["name"].disabled = True if not self.instance.is_public_allowed: self.fields["public"].disabled = True self.fields.pop("featured", None) self.fields["primary_anchor"].queryset = \ self.fields["primary_anchor"].queryset.filter( usercomponent=self.instance ) if self.instance.is_index: self.fields.pop("features", None) self.fields.pop("featured", None) self.fields["required_passes"].help_text = _( "How many protections must be passed to login?
" "Minimum is 1, no matter what selected" ) ptype = ProtectionType.authentication.value else: ptype = ProtectionType.access_control.value self.protections = Protection.get_forms(data=data, files=files, prefix=prefix, assigned=assigned, ptype=ptype, request=request) self.protections = list(self.protections) else: self.fields.pop("primary_anchor", None) self.fields["new_static_token"].initial = INITIAL_STATIC_TOKEN_SIZE self.fields["new_static_token"].choices = \ self.fields["new_static_token"].choices[1:] self.protections = [] self.fields["required_passes"].initial = 1 @property def media(self): """Return all media required to render the widgets on this form.""" media = super().media for protection in self.protections: media = media + protection.media return media def is_valid(self): isvalid = super().is_valid() for protection in self.protections: if not protection.is_valid(): isvalid = False return isvalid def clean_description(self): value = self.cleaned_data.get("description", "").strip() if len(value) > settings.SPIDER_MAX_DESCRIPTION_LENGTH: return "{}…".format( value[:settings.SPIDER_MAX_DESCRIPTION_LENGTH] ) return value def clean(self): ret = super().clean() assert(self.instance.user) self.cleaned_data["can_auth"] = False self.cleaned_data["allow_domain_mode"] = False if 'name' not in self.cleaned_data: # ValidationError so return, calculations won't work here return if not getattr(self.instance, "id", None): quota = self.instance.get_component_quota() if UserComponent.objects.filter( user=self.instance.user ).count() >= quota: raise forms.ValidationError( _("Maximal amount of usercomponents reached %(amount)s."), code='quota_exceeded', params={'amount': quota}, ) # TODO: cleanup into protected_names/forbidden_names if self.cleaned_data['name'] in protected_names: raise forms.ValidationError( _('Forbidden Name'), code="forbidden_name" ) for protection in self.protections: protection.full_clean() self.cleaned_data["strength"] = 0 max_prot_strength = 0 if self.cleaned_data["name"] in index_names: self.cleaned_data["strength"] = 10 return ret if not self.cleaned_data["public"]: self.cleaned_data["strength"] += 5 if getattr(self.instance, "id", None): # instant fail strength fail_strength = 0 # regular protections strength amount_regular = 0 strengths = [] for protection in self.protections: if not protection.cleaned_data.get("active", False): continue if len(str(protection.cleaned_data)) > 1000000: raise forms.ValidationError( _("Protection >1 mb: %(name)s"), params={"name": protection} ) if protection.cleaned_data.get("instant_fail", False): s = protection.get_strength() if s[1] > max_prot_strength: max_prot_strength = s[1] fail_strength = max( fail_strength, s[0] ) else: s = protection.get_strength() if s[1] > max_prot_strength: max_prot_strength = s[1] strengths.append(s[0]) amount_regular += 1 strengths.sort() if self.cleaned_data["required_passes"] > 0: if amount_regular == 0: # login or token only # not 10 because 10 is also used for uniqueness strengths = 4 else: strengths = round(mean(strengths[:ret["required_passes"]])) else: strengths = 0 self.cleaned_data["strength"] += max(strengths, fail_strength) else: # check on creation if self.cleaned_data["required_passes"] > 0: self.cleaned_data["strength"] += 4 self.cleaned_data["can_auth"] = max_prot_strength >= 4 if any(map( lambda x: VariantType.persist.value in x.ctype, self.cleaned_data["features"] )): # fixes strange union bug self.cleaned_data["features"] = ContentVariant.objects.filter( models.Q(name="Persistence") | models.Q( id__in=self.cleaned_data["features"].values_list( "id", flat=True ) ) ) self.cleaned_data["allow_domain_mode"] = any(map( lambda x: VariantType.domain_mode.value in x.ctype, self.cleaned_data["features"] )) min_strength = self.cleaned_data["features"].filter( strength__gt=self.cleaned_data["strength"] ).aggregate(m=models.Max("strength"))["m"] # min_strength is None if no features require a higher strength # than provided if min_strength: self.add_error("features", forms.ValidationError( _( "selected features require " "higher protection strength: %s" ) % min_strength, code="insufficient_strength" )) return self.cleaned_data def _save_protections(self): for protection in self.protections: cleaned_data = protection.cleaned_data t = AssignedProtection.objects.filter( usercomponent=self.instance, protection=protection.protection ).first() if not cleaned_data["active"] and not t: continue if not t: t = AssignedProtection( usercomponent=self.instance, protection=protection.protection ) t.active = cleaned_data.pop("active") t.instant_fail = cleaned_data.pop("instant_fail") t.data = cleaned_data t.save() def _save_m2m(self): super()._save_m2m() self._save_protections() if self.cleaned_data["new_static_token"] != "": if self.instance.token: print( "Old nonce for Component id:", self.instance.id, "is", self.instance.token ) self.instance.token = create_b64_id_token( self.instance.id, "/", int(self.cleaned_data["new_static_token"]) ) self.instance.save(update_fields=["token"]) def save(self, commit=True): self.instance.strength = self.cleaned_data["strength"] self.instance.can_auth = self.cleaned_data["can_auth"] # field maybe not available self.instance.allow_domain_mode = \ self.cleaned_data["allow_domain_mode"] return super().save(commit=commit) save.alters_data = True class UserContentForm(forms.ModelForm): prefix = "content_control" new_static_token = forms.ChoiceField( label=_("New static token"), help_text=_help_text_static_token, required=False, initial="", choices=STATIC_TOKEN_CHOICES ) migrate_primary_anchor = forms.BooleanField( label=_("Migrate primary anchor"), help_text=_( "Set as primary anchor on target component. " "Move tokens and all persistent dependencies. " "Elsewise anchor persistent tokens on the component and leave the " "dependencies untouched." ), required=False, initial=False ) class Meta: model = AssignedContent fields = ['usercomponent', 'features', 'name', 'description'] error_messages = { NON_FIELD_ERRORS: { 'unique_together': _( "Unique Content already exists" ) } } help_texts = { 'features': _help_text_features_contents, } widgets = { 'usercomponent': Select2Widget( allow_multiple_selected=False, attrs={ "style": "min-width: 150px; width:100%" } ), 'features': forms.CheckboxSelectMultiple(), 'description': forms.Textarea( attrs={ "maxlength": settings.SPIDER_MAX_DESCRIPTION_LENGTH+1 } ) } def __init__(self, request, *args, **kwargs): super().__init__(*args, **kwargs) if not self.instance.content.expose_name: self.fields["name"].widget = forms.HiddenInput() elif self.instance.content.expose_name == "force": self.fields["name"].required = True if not self.instance.content.expose_description: self.fields["description"].widget = forms.HiddenInput() self.fields["new_static_token"].choices = map( lambda c: (c[0], c[1].format(c[0])), self.fields["new_static_token"].choices ) user = self.instance.usercomponent.user if request.session.get("is_fake", False): q = ~models.Q(name="index") else: q = ~models.Q(name="fake_index") query = UserComponent.objects.filter( q, user=user, strength__gte=self.instance.ctype.strength ) self.fields["usercomponent"].queryset = query show_primary_anchor_mig = False if self.instance.id: if ( request.user == user and self.instance.primary_anchor_for.exists() ): # can only migrate if a) created, b) real user show_primary_anchor_mig = True else: self.fields["new_static_token"].initial = INITIAL_STATIC_TOKEN_SIZE self.fields["new_static_token"].choices = \ self.fields["new_static_token"].choices[1:] if self.instance.usercomponent.is_index: # contents in index has no features as they could allow # remote access self.fields.pop("features", None) else: if not self.instance.usercomponent.features.filter( name="Persistence" ): self.fields["features"].queryset = \ self.fields["features"].queryset.exclude( ctype__contains=VariantType.persist.value ) user = request.user if not user.is_authenticated: user = self.instance.usercomponent.user self.fields["features"].queryset = ( self.fields["features"].queryset & user.spider_info.allowed_content.all() ).order_by("name") self.fields["features"].initial = \ self.instance.usercomponent.default_content_features.all() if request.user != user and not request.is_staff: del self.fields["usercomponent"] if not show_primary_anchor_mig: del self.fields["migrate_primary_anchor"] def clean_description(self): value = self.cleaned_data.get("description", "") if not self.instance.content.expose_description: return value value = value.strip() if len(value) > settings.SPIDER_MAX_DESCRIPTION_LENGTH: return "{}…".format( value[:settings.SPIDER_MAX_DESCRIPTION_LENGTH] ) return value def clean(self): super().clean() self.cleaned_data["allow_domain_mode"] = \ VariantType.domain_mode.value in self.instance.ctype.ctype if "features" in self.cleaned_data: min_strength = self.cleaned_data["features"].filter( strength__gt=self.instance.usercomponent.strength ).aggregate(m=models.Max("strength"))["m"] # min_strength is None if no features require a higher strength # than provided if min_strength: self.add_error("features", forms.ValidationError( _( "selected features require " "higher protection strength: %s" ) % min_strength, code="insufficient_strength" )) if not self.cleaned_data["allow_domain_mode"]: self.cleaned_data["allow_domain_mode"] = any(map( lambda x: VariantType.domain_mode.value in x.ctype, self.cleaned_data["features"] )) return self.cleaned_data def update_anchor(self): if not self.instance.primary_anchor_for.exists(): return if self.instance.primary_anchor_for == self.instance.usercomponent: return if self.cleaned_data.get("migrate_primary_anchor", False): # no signals self.instance.primary_anchor_for.set( self.instance.usercomponent, bulk=True ) tokens = AuthToken.objects.filter( persist=self.instance.id ) tokens.update( usercomponent=self.instance.usercomponent, ) # this is how to move persistent content results = move_persistent.send_robust( AuthToken, tokens=tokens, to=self.instance.usercomponent ) for (receiver, result) in results: if isinstance(result, Exception): logging.error( "%s failed", receiver, exc_info=result ) else: self.instance.primary_anchor_for.clear(bulk=False) # token migrate to component via signal def _save_m2m(self): super()._save_m2m() self.update_anchor() update_fields = set() if not self.instance.name: update_fields.add("name") if self.instance.token_generate_new_size is not None: if self.instance.token: print( "Old nonce for Content id:", self.instance.id, "is", self.instance.token ) self.instance.token = create_b64_id_token( self.instance.id, "/", self.instance.token_generate_new_size ) self.instance.token_generate_new_size = None update_fields.add("token") if update_fields: self.instance.save(update_fields=update_fields) def save(self, commit=True): # field maybe not available self.instance.allow_domain_mode = \ self.cleaned_data["allow_domain_mode"] if not self.instance.token: self.instance.token_generate_new_size = \ getattr(settings, "TOKEN_SIZE", 30) if self.cleaned_data.get("new_static_token", ""): self.instance.token_generate_new_size = \ int(self.cleaned_data["new_static_token"]) return super().save(commit=commit) save.alters_data = True PK!f;;)spkcspider/apps/spider/forms/_contents.py__all__ = [ "LinkForm", "TravelProtectionForm" ] from django import forms from django.conf import settings from django.db import models from django.contrib.auth.hashers import ( make_password, ) from django.utils.translation import gettext_lazy as _ from ..constants import dangerous_login_choices from ..models import LinkContent, TravelProtection from ..helpers import create_b64_token from ..widgets import HTMLWidget _extra = '' if settings.DEBUG else '.min' PROTECTION_CHOICES = [ ("none", _("No self-protection (not recommended)")), ("pw", _("Password")), ("token", _("Token")) ] KEEP_CHOICES = [("keep", _("Keep protection"))] + PROTECTION_CHOICES _self_protection = _("""Disallows user to disable travel protection if active. Can be used in connection with "secret" to allow unlocking via secret""") class LinkForm(forms.ModelForm): class Meta: model = LinkContent fields = ['content', 'push'] def __init__(self, uc, request, **kwargs): super().__init__(**kwargs) # if self.instance.associated: # if "\x1eanchor\x1e" in self.instance.associated: # self.fields["content"].disabled = True q = self.fields["content"].queryset travel = TravelProtection.objects.get_active() self.fields["content"].queryset = q.filter( strength__lte=uc.strength ).exclude(usercomponent__travel_protected__in=travel) # component auth should limit links to visible content # read access outside from component elsewise possible if request.user != uc.user and not request.is_staff: q = self.fields["content"].queryset self.fields["content"].queryset = q.filter( models.Q(usercomponent=uc) | models.Q(referenced_by__usercomponent=uc) ) class TravelProtectionForm(forms.ModelForm): uc = None is_fake = None _password = None password = forms.CharField( label=_("Old Password"), strip=False, widget=forms.PasswordInput, ) self_protection = forms.ChoiceField( label=_("Self-protection"), help_text=_(_self_protection), initial="None", choices=PROTECTION_CHOICES ) token_arg = forms.CharField( label=_("Token"), help_text=_("please export/copy for disabling travel mode"), widget=HTMLWidget ) new_pw = forms.CharField( initial="", required=False, label=_("New Password"), widget=forms.PasswordInput(), ) new_pw2 = forms.CharField( initial="", required=False, label=_("New Password (Retype)"), widget=forms.PasswordInput(), ) class Meta: model = TravelProtection fields = [ "active", "start", "stop", "login_protection", "disallow" ] class Media: js = [ 'spider_base/travelprotection.js' ] def __init__(self, request, uc, *args, **kwargs): super().__init__(*args, **kwargs) self.uc = uc self.request = request q = models.Q( user=self.uc.user, strength__lt=10 # don't disclose other index ) & ~models.Q( travel_protected__in=TravelProtection.objects.get_active() ) & ~models.Q( public=True # this would easily expose the travel mode ) if not getattr(settings, "DANGEROUS_TRAVEL_PROTECTIONS", False): self.fields["login_protection"].choices = \ filter( lambda x: x[0] not in dangerous_login_choices, self.fields["login_protection"].choices ) self.fields["disallow"].queryset = \ self.fields["disallow"].queryset.filter(q) # elif self.travel_protection.is_active: # for f in self.fields: # f.disabled = True self.fields["token_arg"].initial = create_b64_token(30) if not self.instance.active or not self.instance.hashed_secret: del self.fields["password"] if self.instance.hashed_secret: self.fields["self_protection"].choices = KEEP_CHOICES # doesn't matter if it is same user, lazy travel = TravelProtection.objects.get_active(no_stop=True) self.fields["disallow"].queryset = self.fields["disallow"].queryset.\ filter( ~models.Q(travel_protected__in=travel), user=self.uc.user, strength__lt=10 ) def is_valid(self): isvalid = super().is_valid() if not getattr(self, "cleaned_data", None): return False if self.instance.active and "password" in self.fields: isvalid = self.instance.check_password( self.cleaned_data["password"] ) return isvalid def clean(self): ret = super().clean() if ret["self_protection"] == "none": self._password = False if ret["self_protection"] == "pw": if self.cleaned_data["new_pw"] != self.cleaned_data["new_pw2"]: self.add_error("new_pw", forms.ValidationError( _("The two password fields didn't match.") )) self.add_error("new_pw2", forms.ValidationError( _("The two password fields didn't match.") )) else: self._password = self.cleaned_data["new_pw"] elif ret["self_protection"] == "token": self._password = self.fields["token_arg"].initial return ret def save(self, commit=True): self.instance.is_fake = self.request.session.get("is_fake", False) if self._password: self.instance.hashed_secret = make_password(self._password) if self._password is False: self.instance.hashed_secret = None return super().save(commit=commit) PK!-L{\FF)spkcspider/apps/spider/forms/flatpages.py__all__ = ["FlatpageItemForm"] import copy from django.contrib.flatpages.forms import FlatpageForm from django.utils.translation import gettext_lazy as _ from ..widgets import TrumbowygWidget _help_text = _( """Example: '/about/contact/'. Make sure to have leading and trailing slashes.
Special Urls (ordered by url):
/home/heading/*/ : flatpages used for heading on frontpage
/home/main/*/ : flatpages used for general information on frontpage
/gfooter/*/ : flatpage links with general informations (links will be rendered on every page) """ # noqa: E501 ) class FlatpageItemForm(FlatpageForm): url = copy.copy(FlatpageForm.base_fields["url"]) url.help_text = _help_text class Meta(FlatpageForm.Meta): widgets = { "content": TrumbowygWidget() } PK!k ;#spkcspider/apps/spider/functions.py__all__ = [ "rate_limit_default", "allow_all_filter", "embed_file_default", "has_admin_permission", "LimitedTemporaryFileUploadHandler", "validate_url_default", "get_quota", "validate_payment_default", "clean_verifier", "clean_verifier_url" ] import time import base64 import logging from decimal import Decimal from urllib.parse import urlsplit import requests import certifi from django.core.files.uploadhandler import ( TemporaryFileUploadHandler, StopUpload, StopFutureHandlers ) from django.http import Http404 from django.shortcuts import redirect from django.views.decorators.cache import never_cache from django.urls import reverse from django.conf import settings from rdflib import Literal, XSD from .signals import failed_guess from .constants import spkcgraph def rate_limit_default(view, request): results = failed_guess.send_robust(sender=view, request=request) for (receiver, result) in results: if isinstance(result, Exception): logging.error( "%s failed", receiver, exc_info=result ) time.sleep(1) raise Http404() def allow_all_filter(*args, **kwargs): return True def get_quota(user, quota_type): return getattr(user, "quota_{}".format(quota_type), None) def clean_verifier_url(url): if "://" not in url: return False return True def clean_verifier(view, request): if not request.auth_token or not request.auth_token.referrer: return False url = request.auth_token.referrer.url if request.method == "POST": url = request.POST.get("url", "") if not url.startswith(request.auth_token.referrer.url): return False if "://" not in url: return False if request.method == "GET": # don't spam verifier return True try: resp = requests.get( url, stream=True, verify=certifi.where(), timeout=settings.SPIDER_REQUESTS_TIMEOUT ) resp.close() resp.raise_for_status() except Exception: return False return True def validate_payment_default(amountstr, cur): if amountstr in ("", "0"): return Decimal(0), "" if len(cur) != 3: return None, None return Decimal(amountstr), cur.upper() class LimitedTemporaryFileUploadHandler(TemporaryFileUploadHandler): activated = False def handle_raw_input( self, input_data, META, content_length, boundary, encoding=None ): """ Use the content_length to signal whether or not this handler should be used. """ # disable upload if too big self.activated = self.check_allowed_size(content_length) def new_file(self, *args, **kwargs): if not self.activated: raise StopFutureHandlers() return super().new_file(*args, **kwargs) def receive_data_chunk(self, raw_data, start): if not self.activated: raise StopUpload(True) return super().receive_data_chunk(raw_data, start) def file_complete(self, file_size): """Return a file object if this handler is activated.""" if not self.activated: return return super().file_complete(file_size) def check_allowed_size(self, content_length): if self.request.user.is_staff: max_length = getattr( settings, "MAX_FILE_SIZE_STAFF", None ) else: max_length = getattr( settings, "MAX_FILE_SIZE", None ) if not max_length: return True # superuser can upload as much as he wants if self.request.user.is_superuser: return True return content_length <= max_length def validate_url_default(url, view=None): url = urlsplit(url) if url.scheme == "https": return True elif url.scheme == "http": # MAKE SURE that you control your dns if url.netloc.endswith(".onion"): return True elif settings.DEBUG: return True return False def embed_file_default(name, value, content, context): override = ( ( context["request"].user.is_superuser or context["request"].user.is_staff ) and context["request"].GET.get("embed_big", "") == "true" ) if ( value.size < getattr(settings, "MAX_EMBED_SIZE", 4000000) or override ): return Literal( base64.b64encode(value.read()), datatype=XSD.base64Binary, normalize=False ) elif ( context["scope"] == "export" or getattr(settings, "FILE_DIRECT_DOWNLOAD", False) ): # link always direct to files in exports url = value.url if "://" not in getattr(settings, "MEDIA_URL", ""): url = "{}{}".format(context["hostpart"], url) return Literal( url, datatype=spkcgraph["hashableURI"], ) else: # only file filet has files yet url = content.associated.get_absolute_url("download") url = "{}{}?{}".format( context["hostpart"], url, context["context"]["spider_GET"].urlencode() ) return Literal( url, datatype=spkcgraph["hashableURI"], ) def has_admin_permission(self, request): # allow only non travel protected user with superuser and staff permissions if not request.user.is_active or not request.user.is_staff: return False from .models import TravelProtection as TravelProtectionContent if TravelProtectionContent.objects.get_active().filter( associated_rel__usercomponent__user=request.user ).exists(): return False return True @never_cache def admin_login(self, request, extra_context=None): """ Display the login form for the given HttpRequest. """ if request.method == 'GET' and self.has_permission(request): # Already logged-in, redirect to admin index index_path = reverse('admin:index', current_app=self.name) return redirect(index_path) else: loginpath = getattr( settings, "LOGIN_URL", reverse("auth:login") ) return redirect(loginpath) PK!A8ss!spkcspider/apps/spider/helpers.py__all__ = ( "create_b64_token", "create_b64_id_token", "get_settings_func", "extract_app_dicts", "add_by_field", "prepare_description", "merge_get_url", "add_property", "is_decimal", "validator_token", "extract_host", "get_hashob" ) import os import re import base64 import logging import inspect from urllib.parse import urlsplit, urlunsplit, parse_qs, urlencode from functools import lru_cache from importlib import import_module from rdflib import Literal, BNode, XSD, RDF from django.conf import settings from django.core import validators from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from .constants import MAX_TOKEN_SIZE, spkcgraph # for not spamming sets _empty_set = frozenset() # for not spamming dicts _empty_dict = dict() validator_token = validators.RegexValidator( r'^[-a-zA-Z0-9_/]+\Z', _("Enter a valid token."), 'invalid' ) def get_hashob(): return hashes.Hash( settings.SPIDER_HASH_ALGORITHM, backend=default_backend() ) def is_decimal(inp, precision=None, allow_sign=False): prec_start = None if len(inp) == 0: return False if inp[0] == "-" and allow_sign: inp = inp[1:] for count, i in enumerate(inp): if i == "." and prec_start is None: prec_start = count elif not i.isdigit(): return False return ( None in (precision, prec_start) or len(inp)-prec_start-1 <= precision ) def add_property( graph, name, ref=None, ob=None, literal=None, datatype=None, iterate=False ): value_node = BNode() if ref: graph.add(( ref, spkcgraph["properties"], value_node )) graph.add(( value_node, spkcgraph["name"], Literal(name, datatype=XSD.string) )) if literal is None: literal = getattr(ob, name) if not iterate: literal = [literal] for l in literal: graph.add(( value_node, spkcgraph["value"], Literal(l, datatype=datatype) )) if not literal: graph.set((value_node, spkcgraph["value"], RDF.nil)) return value_node @lru_cache(maxsize=None) def get_settings_func(name, default): filterpath = getattr( settings, name, default ) if callable(filterpath): return filterpath else: module, name = filterpath.rsplit(".", 1) return getattr(import_module(module), name) def add_by_field(dic, field="__name__"): def _func(klass): # should be able to block real object if "{}.{}".format(klass.__module__, klass.__qualname__) not in getattr( settings, "SPIDER_BLACKLISTED_MODULES", _empty_set ): dic[getattr(klass, field)] = klass return klass return _func def extract_app_dicts(app, name, fieldname=None): ndic = {} for (key, value) in getattr(app, name, _empty_dict).items(): if inspect.isclass(value): if "{}.{}".format( value.__module__, value.__qualname__ ) not in getattr( settings, "SPIDER_BLACKLISTED_MODULES", _empty_set ): if fieldname: setattr(value, fieldname, key) ndic[key] = value else: # set None if Module is not always available if value and value not in getattr( settings, "SPIDER_BLACKLISTED_MODULES", _empty_set ): module, name = value.rsplit(".", 1) value = getattr(import_module(module), name) if fieldname: setattr(value, fieldname, key) ndic[key] = value return ndic def create_b64_token(size=None): if not size: from django.conf import settings size = getattr(settings, "TOKEN_SIZE", 30) if size > MAX_TOKEN_SIZE: logging.warning("Nonce too big") return base64.urlsafe_b64encode( os.urandom(size) ).decode('ascii').rstrip("=") def create_b64_id_token(id, sep="_", size=None): return sep.join((hex(id)[2:], create_b64_token(size))) _cleanstr = re.compile(r'<+.*>+') _whitespsplit = re.compile(r'\s+') def prepare_description(raw_html, amount=0): text = _cleanstr.sub(' ', raw_html).\ replace("<", " ").replace(">", " ").strip() return _whitespsplit.split(text, amount) _check_scheme = re.compile(r'^[a-z]+://', re.I) def extract_host(url): url = url.lstrip(":/") if _check_scheme.search(url) is None: urlparsed = urlsplit("://".join(("https", url))) else: urlparsed = urlsplit(url) return "://".join(urlparsed[:2]) def merge_get_url(_url, **kwargs): if not _url.isprintable(): raise ValidationError( _("Url contains control characters"), code="control_characters" ) _url = _url.lstrip(":/") if _check_scheme.search(_url) is None: urlparsed = urlsplit("://".join(("https", _url))) else: urlparsed = urlsplit(_url) _strip = [] for i in kwargs.keys(): if not kwargs[i]: _strip.append(i) GET = parse_qs(urlparsed.query) GET.update(kwargs) for item in _strip: GET.pop(item, None) ret = urlunsplit((*urlparsed[:3], urlencode(GET), "")) return ret PK!f)ff6spkcspider/apps/spider/locale/de/LC_MESSAGES/django.po# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-03 00:50+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: spkcspider/apps/spider/constants/settings.py:16 msgid "default ({} Bytes)" msgstr "" #: spkcspider/apps/spider/constants/settings.py:17 msgid "low ({} Bytes)" msgstr "" #: spkcspider/apps/spider/constants/settings.py:18 msgid "medium ({} Bytes)" msgstr "" #: spkcspider/apps/spider/constants/settings.py:19 msgid "high ({} Bytes)" msgstr "" #: spkcspider/apps/spider/contents.py:213 msgid "No User Input required" msgstr "" #: spkcspider/apps/spider/contents.py:359 #, python-format msgid "Add \"%s\"" msgstr "" #: spkcspider/apps/spider/contents.py:362 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_form.html:22 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:135 msgid "Create" msgstr "" #: spkcspider/apps/spider/contents.py:371 #, python-format msgid "Update \"%s\"" msgstr "" #: spkcspider/apps/spider/contents.py:374 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:45 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:67 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_form.html:24 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:38 #: spkcspider/apps/spider_accounts/templates/registration/profile.html:6 msgid "Update" msgstr "" #: spkcspider/apps/spider/contents.py:451 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:40 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_form.html:36 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:139 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:140 msgid "View" msgstr "" #: spkcspider/apps/spider/forms/_base.py:18 msgid "" "Generate a new nonce token with variable strength
\n" "Nonces protect against bruteforce and attackers
\n" "If you have problems with attackers (because they know the nonce),\n" "you can invalidate it with this option and/or add protections
\n" "\n" "\n" "\n" "\n" "\n" "\n" "
Warning:this removes also access for all services you gave the " "link\n" "
Warning:public user components " "disclose nonce, regenerate after removing\n" "public attribute from component for restoring protection
\n" msgstr "" #: spkcspider/apps/spider/forms/_base.py:37 #: spkcspider/apps/spider/forms/_base.py:207 msgid "New Nonce" msgstr "" #: spkcspider/apps/spider/forms/_base.py:49 msgid "Name of User Component already exists" msgstr "" #: spkcspider/apps/spider/forms/_base.py:79 msgid "" "How many protections must be passed to login?
Minimum is 1, no matter " "what selected" msgstr "" #: spkcspider/apps/spider/forms/_base.py:123 msgid "Forbidden Name" msgstr "" #: spkcspider/apps/spider/forms/_base.py:145 #, python-format msgid "Protection >1 mb: %(name)s" msgstr "" #: spkcspider/apps/spider/forms/_base.py:217 #: spkcspider/apps/spider/models/content_base.py:239 msgid "Unique Content already exists" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:22 msgid "No self-protection (not recommended)" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:23 #: spkcspider/apps/spider/protections.py:265 msgid "Password" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:24 #: spkcspider/apps/spider/forms/_contents.py:63 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:84 msgid "Token" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:27 msgid "Keep protection" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:30 msgid "" "Disallows user to disable travel protection if active.\n" " Can be used in connection with \"secret\" to allow unlocking via secret" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:54 msgid "Old Password" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:59 msgid "Self-protection" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:64 msgid "please export/copy for disabling travel mode" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:68 msgid "New Password" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:72 msgid "New Password (Retype)" msgstr "" #: spkcspider/apps/spider/forms/_contents.py:148 #: spkcspider/apps/spider/forms/_contents.py:151 msgid "The two password fields didn't match." msgstr "" #: spkcspider/apps/spider/forms/flatpages.py:9 msgid "" "Example: '/about/contact/'. Make sure to have leading and trailing slashes.\n" " \"
Special Urls (ordered by url):
\n" " /home/heading/*/ : flatpages used for heading on frontpage
\n" " /home/main/*/ : flatpages used for general information on frontpage" msgstr "" #: spkcspider/apps/spider/models/content_base.py:71 #, python-format msgid "%(value)s ends not with \"\\n\"" msgstr "" #: spkcspider/apps/spider/models/content_base.py:77 #, python-format msgid "%(value)s starts not with \"\\n\"" msgstr "" #: spkcspider/apps/spider/models/content_base.py:94 #, python-format msgid "flag not unique: %(element)s in %(value)s" msgstr "" #: spkcspider/apps/spider/models/content_base.py:221 msgid "Not an allowed ContentVariant for this user" msgstr "" #: spkcspider/apps/spider/models/content_base.py:226 #: spkcspider/apps/spider/models/user.py:168 #, python-format msgid "Protection strength too low, required: %(strength)s" msgstr "" #: spkcspider/apps/spider/models/contents.py:97 msgid "Create Content Link" msgstr "" #: spkcspider/apps/spider/models/contents.py:102 msgid "Update Content Link" msgstr "" #: spkcspider/apps/spider/models/contents.py:126 msgid "No Login protection" msgstr "" #: spkcspider/apps/spider/models/contents.py:128 msgid "Fake login" msgstr "" #: spkcspider/apps/spider/models/contents.py:130 msgid "Wipe" msgstr "" #: spkcspider/apps/spider/models/contents.py:131 msgid "Wipe User" msgstr "" #: spkcspider/apps/spider/models/contents.py:136 msgid "" "No Login Protection: normal, default
Fake Login: fake login and index
Wipe: Wipe protected content, except they are protected by a deletion " "period
Wipe User: destroy user on login" msgstr "" #: spkcspider/apps/spider/models/contents.py:225 msgid "Create Travel Protection" msgstr "" #: spkcspider/apps/spider/models/contents.py:230 msgid "Update Travel Protection" msgstr "" #: spkcspider/apps/spider/models/protections.py:196 msgid "" "Auth fails if test fails, stronger than required_passes\n" "Works even if required_passes=0\n" "Does not contribute to required_passes, ideal for side effects" msgstr "" #: spkcspider/apps/spider/models/user.py:36 msgid "" "Name of the component.
Note: there are special named components with " "different protection types and scopes.
Most prominent: \"index\" for " "authentication" msgstr "" #: spkcspider/apps/spider/models/user.py:44 msgid "" "How many protection must be passed? Set greater 0 to enable protection based " "access" msgstr "" #: spkcspider/apps/spider/models/user.py:49 msgid "Appears as featured on \"home\" page" msgstr "" #: spkcspider/apps/spider/models/user.py:82 msgid "" "Is public? Is listed and searchable?
Note: This field is maybe not " "deactivatablebecause of assigned content" msgstr "" #: spkcspider/apps/spider/models/user.py:90 msgid "Description of user component." msgstr "" #: spkcspider/apps/spider/models/user.py:319 #, python-format msgid "Exceeds quota by %(diff)s Bytes" msgstr "" #: spkcspider/apps/spider/protections.py:78 msgid "Active" msgstr "" #: spkcspider/apps/spider/protections.py:80 msgid "Instant fail" msgstr "" #: spkcspider/apps/spider/protections.py:81 msgid "" "Fail instantly if not fullfilled. Don't count to required_passes. Requires " "\"active\"." msgstr "" #: spkcspider/apps/spider/protections.py:181 msgid "Users" msgstr "" #: spkcspider/apps/spider/protections.py:188 msgid "Limit access to selected users" msgstr "" #: spkcspider/apps/spider/protections.py:215 msgid "Success Rate" msgstr "" #: spkcspider/apps/spider/protections.py:217 msgid "Set success rate" msgstr "" #: spkcspider/apps/spider/protections.py:223 msgid "" "Fail/Refuse randomly. Optionally with 404 error, to disguise correct access." msgstr "" #: spkcspider/apps/spider/protections.py:242 msgctxt "protection name" msgid "Random Fail" msgstr "" #: spkcspider/apps/spider/protections.py:260 msgid "Login with user password" msgstr "" #: spkcspider/apps/spider/protections.py:306 msgid "Protect with passwords" msgstr "" #: spkcspider/apps/spider/protections.py:312 msgid "Password 2" msgstr "" #: spkcspider/apps/spider/protections.py:318 msgid "Passwords" msgstr "" #: spkcspider/apps/spider/protections.py:319 msgid "One password per line. Every password is stripped." msgstr "" #: spkcspider/apps/spider/protections.py:357 msgid "Password 2 (if required)" msgstr "" #: spkcspider/apps/spider/protections.py:381 msgid "Require captcha" msgstr "" #: spkcspider/apps/spider/protections.py:388 #: spkcspider/apps/spider_accounts/forms.py:61 msgid "Captcha" msgstr "" #: spkcspider/apps/spider/protections.py:400 msgid "instant_fail is for login required" msgstr "" #: spkcspider/apps/spider/protections.py:406 msgid "captcha is for login required (admin setting)" msgstr "" #: spkcspider/apps/spider/protections.py:413 msgctxt "protection name" msgid "Captcha Protection" msgstr "" #: spkcspider/apps/spider/protections.py:432 msgid "Deny access if valid travel protection is active" msgstr "" #: spkcspider/apps/spider/serializing.py:168 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:4 #, python-format msgid "Create Content: %(name)s" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:6 #, python-format msgid "Update Content: %(name)s" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:8 #, python-format msgid "Content: %(name)s" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:30 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:25 msgid "Index" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:36 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_confirm_delete.html:35 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:148 #: spkcspider/apps/spider/templates/spider_base/usercomponent_confirm_delete.html:35 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:38 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:86 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:108 msgid "Delete" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:52 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:42 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:34 msgid "Link/Export" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:56 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:45 msgid "Strength:" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:62 msgid "Create Content" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:69 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:73 msgid "User Component" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_access.html:72 msgid "User Component:" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_confirm_delete.html:3 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_confirm_delete.html:13 msgid "Delete Content" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_confirm_delete.html:11 #: spkcspider/apps/spider/templates/spider_base/usercomponent_confirm_delete.html:11 msgid "Back" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_confirm_delete.html:21 msgid "" "Warning this deletes the content.
Go back if you don't want to this and " "you didn't hit delete.
You can abort the deletion process with reset if " "it is shown.
It is always shown except no deletion is in progress." msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_confirm_delete.html:43 #: spkcspider/apps/spider/templates/spider_base/usercomponent_confirm_delete.html:43 msgid "Reset" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_form.html:11 msgid "Content Settings" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_form.html:30 msgid "Update Raw Content" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:3 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:50 msgid "Content of:" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:5 msgid "Content List" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:31 msgid "Up (Owned)" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:33 msgid "up (Public)" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:62 msgid "Content:" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:90 msgid "Add new content" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:99 #, python-format msgid "Requires Strength: %(required_strength)s" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:109 msgid "No Content Variants available." msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:131 #: spkcspider/apps/spider/templates/spider_base/usercomponent_list_fragment.html:38 #, python-format msgid "Modified: %(time)s ago" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:144 #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:145 #: spkcspider/apps/spider/templates/spider_base/usercomponent_list_fragment.html:50 #: spkcspider/apps/spider/templates/spider_base/usercomponent_list_fragment.html:51 msgid "Edit" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:149 msgid "Del" msgstr "" #: spkcspider/apps/spider/templates/spider_base/assignedcontent_list.html:156 msgid "No User Content available." msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:61 msgid "Add Component" msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:65 msgid "List public Components" msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:69 msgid "List owned Components" msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:73 msgid "List private Content" msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:91 msgid "Profile" msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:95 msgid "Security" msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:100 msgid "Admin" msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:105 msgid "Logout" msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:115 msgid "Register" msgstr "" #: spkcspider/apps/spider/templates/spider_base/base.html:121 msgid "Login" msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/form_errors.html:6 msgid "Please correct the error below." msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/form_errors.html:6 msgid "Please correct the errors below." msgstr "" #: spkcspider/apps/spider/templates/spider_base/home.html:4 #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:29 msgid "Home" msgstr "" #: spkcspider/apps/spider/templates/spider_base/home.html:33 msgid "Welcome to spkcspider" msgstr "" #: spkcspider/apps/spider/templates/spider_base/home.html:53 msgid "Featured:" msgstr "" #: spkcspider/apps/spider/templates/spider_base/home.html:70 msgid "List public components (spiders)..." msgstr "" #: spkcspider/apps/spider/templates/spider_base/list_footer.html:13 #: spkcspider/apps/spider/templates/spider_base/list_footer.html:18 msgid "Search" msgstr "" #: spkcspider/apps/spider/templates/spider_base/list_footer.html:21 msgid "clear" msgstr "" #: spkcspider/apps/spider/templates/spider_base/list_footer.html:29 msgid "" "Use ! to negate. Use !! to start a search tag with !. Press Enter to confirm " "tag. The button with the magnifying glass on the right starts the search." msgstr "" #: spkcspider/apps/spider/templates/spider_base/list_footer.html:53 msgid "previous" msgstr "" #: spkcspider/apps/spider/templates/spider_base/list_footer.html:57 #, python-format msgid "Page %(page)s of %(num_pages)s." msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/modalpresenter.html:10 msgid "QR/Export-Mode" msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/modalpresenter.html:19 msgid "With access token" msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/modalpresenter.html:26 msgid "Link" msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/modalpresenter.html:30 msgid "Raw" msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/modalpresenter.html:32 #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:53 msgid "Export" msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/token_lifetime.html:2 msgid "Remaining lifetime of token" msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/token_lifetime.html:6 #: spkcspider/apps/spider/templates/spider_base/partials/token_lifetime.html:26 #, python-format msgid "Days: %(days)s %(hours)sh:%(minutes)sm:%(seconds)ss" msgstr "" #: spkcspider/apps/spider/templates/spider_base/partials/token_lifetime.html:31 msgid "Expired" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_confirm_delete.html:3 #, python-format msgid "Delete: %(name)s" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_confirm_delete.html:13 msgid "Delete User Component:" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_confirm_delete.html:21 msgid "" "Warning this deletes the component and all depending content.
Go back " "if you don't want to this and you didn't hit delete.
You can abort the " "deletion process with reset if it is shown.
It is always shown except " "no deletion is in progress.
" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:5 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:48 msgid "Change Login Protection" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:6 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:50 msgid "Change Component" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:8 msgid "Create Component" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:30 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:139 msgid "Contents" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:42 msgid "Strength" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:54 msgid "Create new Component" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:71 msgid "Login Protection" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:81 msgid "Active Tokens" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:85 msgid "Valid till" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:114 #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:135 msgid "Change" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:118 #: spkcspider/apps/spider/templates/spider_base/protections/protections.html:10 msgid "Protections" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_form.html:129 msgid "No protections: create component first" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:3 #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:60 msgid "Public Components" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:5 #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:62 msgid "Owned Components" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:7 #, python-format msgid "Component List of %(name)s" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:34 msgid "Up (Public)" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:43 msgid "Add New Component" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:48 msgid "Owned" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list.html:64 msgid "Component List of" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list_fragment.html:22 msgid "Private Component" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list_fragment.html:45 #: spkcspider/apps/spider/templates/spider_base/usercomponent_list_fragment.html:46 msgid "List" msgstr "" #: spkcspider/apps/spider/templates/spider_base/usercomponent_list_fragment.html:58 msgid "No Components available." msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/protections.html:23 msgid "Send" msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:7 msgid "Allow" msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:17 msgid "Access to" msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:21 msgid "Scope" msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:22 #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:37 msgid "Name" msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:28 #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:35 msgid "Component" msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:30 msgid "Description" msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:43 #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:51 msgid "Content" msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:61 msgid "Confirm Access" msgstr "" #: spkcspider/apps/spider/templates/spider_base/protections/referring.html:65 msgid "Cancel" msgstr "" #: spkcspider/apps/spider/views/_core.py:55 #: spkcspider/apps/spider/views/_core.py:321 msgid "Token creation failed, try again" msgstr "" #: spkcspider/apps/spider/views/_core.py:299 #, python-format msgid "Insecure url: %(url)s" msgstr "" #: spkcspider/apps/spider_accounts/admin.py:17 msgid "Personal info" msgstr "" #: spkcspider/apps/spider_accounts/admin.py:18 msgid "Permissions" msgstr "" #: spkcspider/apps/spider_accounts/admin.py:21 msgid "Important dates" msgstr "" #: spkcspider/apps/spider_accounts/forms.py:32 msgid "Email Address" msgstr "" #: spkcspider/apps/spider_accounts/forms.py:33 msgid "(optional)" msgstr "" #: spkcspider/apps/spider_accounts/forms.py:81 msgid "Optional" msgstr "" #: spkcspider/apps/spider_accounts/models.py:27 msgid "Quota in Bytes, null to use standard" msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/logged_out.html:10 msgid "Logout successful." msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/logged_out.html:11 #: spkcspider/apps/spider_accounts/templates/registration/password_change_done.html:11 #: spkcspider/apps/spider_accounts/templates/registration/thanks.html:10 msgid "Click if not redirected" msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/login.html:16 msgid "Authenticate with" msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/login.html:31 msgid "No account? Signup?" msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/password_change_done.html:10 msgid "Your password was changed." msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/password_change_form.html:9 msgid "Change Password" msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/password_change_form.html:10 msgid "" "Please enter your old password, for security's sake, and then enter your new " "password twice so we can verify you typed it in correctly." msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/password_change_form.html:15 msgid "Change my password" msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/profile.html:5 msgid "Update User Profile" msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/signup.html:5 #: spkcspider/apps/spider_accounts/templates/registration/signup.html:11 msgid "Sign up" msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/signup.html:12 msgid "Already an account? Login?" msgstr "" #: spkcspider/apps/spider_accounts/templates/registration/thanks.html:9 #, python-format msgid " Signup successful. Thank you for using %(name)s " msgstr "" PK!l<<<spkcspider/apps/spider/management/commands/fix_jsonfields.py__all__ = ("Command",) import json from django.core.management.base import BaseCommand from django.apps import apps class Command(BaseCommand): help = ( "fix json fields in string format" ) def fix_instance(self, instance, field): if isinstance(getattr(instance, field), str): try: setattr( instance, field, json.loads( getattr(instance, field).replace("'", "\"") .replace("False", "false") .replace("True", "true") ) ) self.stdout.write("Fixed instance: {}".format(instance)) except Exception as exc: self.stdout.write(str(exc)) self.stdout.write("\n") def handle(self, **options): from jsonfield import JSONField as step JSONField = type(step()) for model in apps.get_models(): requires_hack = [] for field in model._meta.get_fields(): if isinstance(field, JSONField): requires_hack.append(field.name) if requires_hack: for i in model.objects.all(): for field in requires_hack: self.fix_instance(i, field) try: i.clean() i.save() except Exception as exc: self.stdout.write(str(exc)) self.stdout.write("\n") PK!䊉  @spkcspider/apps/spider/management/commands/revoke_auth_tokens.py__all__ = ("Command",) import datetime from django.core.management.base import BaseCommand class Command(BaseCommand): help = ( "Delete old/all auth tokens" ) def add_arguments(self, parser): parser.add_argument( '--days', action='store', dest='days', default=None, help='Delete tokens older than days', ) parser.add_argument( '--oldest', action='store', dest='oldest', default=None, help='Delete oldest x tokens', ) parser.add_argument( '--referrer', action='store', dest='referrer', default=None, help='Delete tokens of referrer', ) parser.add_argument( '--anchor', action='store', dest='anchor', default=None, help=( 'Delete tokens of anchor id:\n' '0/"component" for component,\n' '"all": all tokens,\n' '"persist": persistent tokens' ), ) def handle(self, oldest=None, days=None, anchor=None, **options): from spkcspider.apps.spider.models import AuthToken q = AuthToken.objects.all() if options['referrer']: q = q.filter(referrer__url=options["referrer"]) if anchor == "all" or not anchor: pass elif anchor == "component": q = q.filter(persist=0) elif anchor == "persist": q = q.filter(persist__gte=0) else: q = q.filter(persist=int(anchor)) elif anchor == "component": q = q.filter(persist=0) elif anchor == "persist": q = q.filter(persist__gte=0) elif anchor == "all": pass elif anchor: q = q.filter(persist=int(anchor)) else: q = q.filter(persist=-1) if days: dt = datetime.datetime.now() - \ datetime.timedelta(days=int(days)) q.filter(created__lt=dt) if oldest: # create list with ids ids = q.order_by("created").values_list( 'id', flat=True )[:int(oldest)] # recreate Query q = AuthToken.objects.filter(id__in=ids) self.stdout.write("count: %s\n" % q.delete()[0]) PK!.ZDspkcspider/apps/spider/management/commands/update_dynamic_content.pyfrom django.core.management.base import BaseCommand import logging class Command(BaseCommand): help = 'Update dynamic spider content e.g. permissions, content' def handle(self, *args, **options): from spkcspider.apps.spider.signals import update_dynamic self.log = logging.getLogger(__name__) for handler in self.log.handlers: self.log.removeHandler(handler) self.log.addHandler(logging.StreamHandler(self.stdout)) results = update_dynamic.send_robust(self) for (receiver, result) in results: if isinstance(result, Exception): self.log.error( "%s failed", receiver, exc_info=result ) PK!ن551spkcspider/apps/spider/migrations/0001_initial.py# Generated by Django 2.1 on 2018-08-30 13:52 import datetime from django.conf import settings from django.db import migrations, models import django.db.models.deletion import jsonfield.fields import spkcspider.apps.spider.helpers import spkcspider.apps.spider.models.content_base import spkcspider.apps.spider.models.base import spkcspider.apps.spider.models.protections class Migration(migrations.Migration): initial = True dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('contenttypes', '0002_remove_content_type_name'), ] operations = [ migrations.CreateModel( name='AssignedContent', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('nonce', models.SlugField(db_index=False, default=spkcspider.apps.spider.helpers.create_b64_token, max_length=120)), ('created', models.DateTimeField(auto_now_add=True)), ('modified', models.DateTimeField(auto_now=True)), ('deletion_requested', models.DateTimeField(blank=True, default=None, null=True)), ('info', models.TextField(editable=False, validators=[spkcspider.apps.spider.models.base.info_field_validator])), ('object_id', models.BigIntegerField(editable=False)), ('content_type', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), ], ), migrations.CreateModel( name='AssignedProtection', fields=[ ('id', models.BigAutoField(primary_key=True, serialize=False)), ('data', jsonfield.fields.JSONField(default=dict)), ('created', models.DateTimeField(auto_now_add=True)), ('modified', models.DateTimeField(auto_now=True)), ('active', models.BooleanField(default=True)), ('instant_fail', models.BooleanField(default=False, help_text='Auth fails if test fails, stronger than required_passes\nWorks even if required_passes=0\nDoes not contribute to required_passes, ideal for side effects')), ], ), migrations.CreateModel( name='AuthToken', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('session_key', models.CharField(max_length=40, null=True)), ('token', models.SlugField(max_length=136)), ('created', models.DateTimeField(auto_now_add=True)), ], ), migrations.CreateModel( name='ContentVariant', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('ctype', models.CharField(max_length=10)), ('code', models.CharField(max_length=255)), ('name', models.SlugField(max_length=255, unique=True)), ], ), migrations.CreateModel( name='LinkContent', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('content', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='spider_base.AssignedContent')), ], options={ 'abstract': False, 'default_permissions': [], }, ), migrations.CreateModel( name='Protection', fields=[ ('code', models.SlugField(db_index=False, max_length=10, primary_key=True, serialize=False)), ('ptype', models.CharField(default='b', max_length=10)), ], ), migrations.CreateModel( name='UserComponent', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('nonce', models.SlugField(db_index=False, default=spkcspider.apps.spider.helpers.create_b64_token, max_length=120)), ('public', models.BooleanField(default=False, help_text=( "Is public? Is listed and searchable?
" "Note: This field is maybe not deactivatable" "because of assigned content" ))), ('required_passes', models.PositiveIntegerField(default=0, help_text='How many protection must be passed? Set greater 0 to enable protection based access')), ('name', models.SlugField(db_index=False, help_text='\nName of the component.
\nNote: there are special named components\nwith different protection types and scopes.
\nMost prominent: "index" for authentication\n')), ('created', models.DateTimeField(auto_now_add=True)), ('modified', models.DateTimeField(auto_now=True)), ('token_duration', models.DurationField(default=datetime.timedelta(7))), ('deletion_requested', models.DateTimeField(default=None, null=True)), ('user', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( name='UserInfo', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('used_space', models.BigIntegerField(default=0, editable=False)), ('allowed_content', models.ManyToManyField(editable=False, related_name='_userinfo_allowed_content_+', to='spider_base.ContentVariant')), ('user', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='spider_info', to=settings.AUTH_USER_MODEL)), ], options={ 'default_permissions': [], }, ), migrations.AddField( model_name='authtoken', name='usercomponent', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='authtokens', to='spider_base.UserComponent'), ), migrations.AddField( model_name='assignedprotection', name='protection', field=models.ForeignKey(editable=False, limit_choices_to=spkcspider.apps.spider.models.protections.get_limit_choices_assigned_protection, on_delete=django.db.models.deletion.CASCADE, related_name='assigned', to='spider_base.Protection'), ), migrations.AddField( model_name='assignedprotection', name='usercomponent', field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='protections', to='spider_base.UserComponent'), ), migrations.AddField( model_name='assignedcontent', name='ctype', field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='spider_base.ContentVariant'), ), migrations.AddField( model_name='assignedcontent', name='usercomponent', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contents', to='spider_base.UserComponent'), ), migrations.AlterUniqueTogether( name='usercomponent', unique_together={('user', 'name')}, ), migrations.AlterUniqueTogether( name='authtoken', unique_together={('usercomponent', 'token')}, ), migrations.AlterUniqueTogether( name='assignedprotection', unique_together={('protection', 'usercomponent')}, ), ] operations.append( migrations.AlterUniqueTogether( name='assignedcontent', unique_together={('content_type', 'object_id')}, ) ) PK!K,].].;spkcspider/apps/spider/migrations/0002_20190127_squashed.py# Generated by Django 2.1.5 on 2019-01-27 14:47 from django.conf import settings import django.core.validators from django.db import migrations, models import django.db.models.deletion import jsonfield.fields import spkcspider.apps.spider.models.contents import spkcspider.apps.spider.models.protections class Migration(migrations.Migration): dependencies = [ ('spider_base', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.AddField( model_name='assignedcontent', name='strength', field=models.PositiveSmallIntegerField(default=0, editable=False, validators=[django.core.validators.MaxValueValidator(10)]), ), migrations.AddField( model_name='assignedcontent', name='strength_link', field=models.PositiveSmallIntegerField(default=0, editable=False, validators=[django.core.validators.MaxValueValidator(11)]), ), migrations.AddField( model_name='contentvariant', name='strength', field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(10)]), ), migrations.AddField( model_name='usercomponent', name='strength', field=models.PositiveSmallIntegerField(default=0, editable=False, validators=[django.core.validators.MaxValueValidator(10)]), ), migrations.AlterField( model_name='contentvariant', name='ctype', field=models.CharField(default='', max_length=10), ), migrations.AlterField( model_name='contentvariant', name='name', field=models.SlugField(unique=True), ), migrations.CreateModel( name='TravelProtection', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('active', models.BooleanField(default=False)), ('start', models.DateTimeField(default=spkcspider.apps.spider.models.contents.default_start)), ('stop', models.DateTimeField(default=spkcspider.apps.spider.models.contents.default_stop, null=True)), ('self_protection', models.BooleanField(default=True, help_text='\n Disallows user to disable travel protection if active.\n Can be used in connection with "secret" to allow unlocking via secret\n')), ('login_protection', models.CharField(choices=[('a', 'No Login protection')], default='a', help_text='\n No Login Protection: normal, default\n Fake Login: fake login and index (experimental)\n Wipe: Wipe protected content,\n except they are protected by a deletion period\n Wipe User: destroy user on login\n\n\n
\n Danger: every option other than: "No Login Protection" can screw you.\n "Fake Login" can trap you in a parallel reality\n
\n', max_length=10)), ('secret', models.SlugField(default='', max_length=120)), ], options={ 'abstract': False, 'default_permissions': [], }, ), migrations.AlterField( model_name='assignedprotection', name='protection', field=models.ForeignKey(limit_choices_to=spkcspider.apps.spider.models.protections.get_limit_choices_assigned_protection, on_delete=django.db.models.deletion.CASCADE, related_name='assigned', to='spider_base.Protection'), ), migrations.AlterField( model_name='usercomponent', name='deletion_requested', field=models.DateTimeField(blank=True, default=None, null=True), ), migrations.AlterField( model_name='usercomponent', name='user', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), ), migrations.AddField( model_name='travelprotection', name='disallow', field=models.ManyToManyField(blank=True, related_name='travel_protected', to='spider_base.UserComponent'), ), migrations.AlterModelOptions( name='usercomponent', options={'permissions': [('can_feature', 'Can feature User Components')]}, ), migrations.AddField( model_name='usercomponent', name='featured', field=models.BooleanField(default=False, help_text='Appears as featured on "home" page'), ), migrations.AlterField( model_name='usercomponent', name='name', field=models.SlugField(allow_unicode=True, help_text='\nName of the component.
\nNote: there are special named components\nwith different protection types and scopes.
\nMost prominent: "index" for authentication\n'), ), migrations.AddField( model_name='usercomponent', name='description', field=models.TextField(blank=True, default='', help_text='Description of user component.'), ), migrations.AddField( model_name='assignedcontent', name='fake_id', field=models.BigIntegerField(editable=False, null=True), ), migrations.RemoveField( model_name='travelprotection', name='secret', ), migrations.RemoveField( model_name='travelprotection', name='self_protection', ), migrations.AddField( model_name='travelprotection', name='hashed_secret', field=models.CharField(max_length=128, null=True), ), migrations.AddField( model_name='assignedcontent', name='references', field=models.ManyToManyField(editable=False, related_name='referenced_by', to='spider_base.AssignedContent'), ), migrations.AddField( model_name='authtoken', name='created_by_special_user', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL), ), migrations.AddField( model_name='authtoken', name='extra', field=jsonfield.fields.JSONField(blank=True, default=dict), ), migrations.AlterField( model_name='travelprotection', name='login_protection', field=models.CharField(choices=[('a', 'No Login protection'), ('b', 'Fake login'), ('c', 'Wipe'), ('d', 'Wipe User')], default='a', help_text='\n No Login Protection: normal, default\n Fake Login: fake login and index (experimental)\n Wipe: Wipe protected content,\n except they are protected by a deletion period\n Wipe User: destroy user on login\n\n\n
\n Danger: every option other than: "No Login Protection" can screw you.\n "Fake Login" can trap you in a parallel reality\n
\n', max_length=10), ), migrations.AddField( model_name='travelprotection', name='is_fake', field=models.BooleanField(default=False), ), migrations.AlterField( model_name='travelprotection', name='login_protection', field=models.CharField(choices=[('a', 'No Login protection'), ('b', 'Fake login'), ('c', 'Wipe'), ('d', 'Wipe User')], default='a', help_text='No Login Protection: normal, default
Fake Login: fake login and index
Wipe: Wipe protected content, except they are protected by a deletion period
Wipe User: destroy user on login', max_length=10), ), migrations.AlterField( model_name='usercomponent', name='name', field=models.SlugField(allow_unicode=True, help_text='Name of the component.
Note: there are special named components with different protection types and scopes.
Most prominent: "index" for authentication'), ), migrations.AddField( model_name='usercomponent', name='features', field=models.ManyToManyField(blank=True, limit_choices_to=models.Q(ctype__contains='a'), related_name='supports', to='spider_base.ContentVariant'), ), migrations.AlterField( model_name='assignedcontent', name='ctype', field=models.ForeignKey(editable=False, limit_choices_to=models.Q(_negated=True, ctype__contains='a'), null=True, on_delete=django.db.models.deletion.SET_NULL, to='spider_base.ContentVariant'), ), migrations.AlterField( model_name='authtoken', name='token', field=models.SlugField(max_length=136, unique=True), ), migrations.AlterUniqueTogether( name='authtoken', unique_together=set(), ), migrations.AlterField( model_name='assignedcontent', name='ctype', field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='spider_base.ContentVariant'), ), migrations.RenameField( model_name='userinfo', old_name='used_space', new_name='used_space_local', ), migrations.AddField( model_name='userinfo', name='used_space_remote', field=models.BigIntegerField(default=0, editable=False), ), migrations.AddField( model_name='assignedcontent', name='priority', field=models.SmallIntegerField(blank=True, default=0), ), migrations.AddField( model_name='linkcontent', name='push', field=models.BooleanField(blank=True, default=False, help_text='Improve ranking of this Link.'), ), migrations.AddField( model_name='authtoken', name='referrer', field=models.URLField(blank=True, max_length=400, null=True), ), migrations.AddField( model_name='authtoken', name='persist', field=models.BigIntegerField(blank=True, db_index=True, default=-1), ), migrations.AddField( model_name='usercomponent', name='can_auth', field=models.BooleanField(default=False, editable=False), ), migrations.AddField( model_name='usercomponent', name='primary_anchor', field=models.ForeignKey(blank=True, help_text='Select main identyifing anchor. Also used for attaching persisting tokens (elsewise they are attached to component)', limit_choices_to={'info__contains': '\nanchor\n'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_anchor_for', to='spider_base.AssignedContent'), ), migrations.AlterField( model_name='linkcontent', name='content', field=models.ForeignKey(limit_choices_to={'strength_link__lte': 10}, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='spider_base.AssignedContent'), ), migrations.AlterField( model_name='usercomponent', name='public', field=models.BooleanField(default=False, help_text='Is public? Is listed and searchable?
Note: This field is maybe not deactivatable because of assigned content'), ), migrations.AddField( model_name='assignedcontent', name='persist_token', field=models.ForeignKey(blank=True, limit_choices_to={'persist__gte': 0}, null=True, on_delete=django.db.models.deletion.CASCADE, to='spider_base.AuthToken'), ), ] PK!D3@@<spkcspider/apps/spider/migrations/0003_auto_20190128_1339.py# Generated by Django 2.1.5 on 2019-01-28 13:39 import django.core.validators from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_base', '0002_20190127_squashed'), ] operations = [ migrations.AddField( model_name='assignedcontent', name='token', field=models.CharField(blank=True, db_index=True, max_length=138, null=True, unique=True, validators=[django.core.validators.RegexValidator('^[-a-zA-Z0-9_/]+\\Z', 'Enter a valid token.', 'invalid')]), ), migrations.AddField( model_name='usercomponent', name='token', field=models.CharField(blank=True, db_index=True, max_length=138, null=True, unique=True, validators=[django.core.validators.RegexValidator('^[-a-zA-Z0-9_/]+\\Z', 'Enter a valid token.', 'invalid')]), ), migrations.AlterField( model_name='assignedcontent', name='nonce', field=models.SlugField(blank=True, db_index=False, max_length=120, null=True), ), migrations.AlterField( model_name='authtoken', name='token', field=models.CharField(db_index=True, max_length=137, unique=True, validators=[django.core.validators.RegexValidator('^[-a-zA-Z0-9_/]+\\Z', 'Enter a valid token.', 'invalid')]), ), migrations.AlterField( model_name='usercomponent', name='nonce', field=models.SlugField(blank=True, db_index=False, max_length=120, null=True), ), ] PK! >:_<spkcspider/apps/spider/migrations/0004_auto_20190128_1349.py# Generated by Django 2.1.5 on 2019-01-28 13:49 from django.db import migrations def move_tokens_forward(apps, schema_editor): AssignedContent = apps.get_model('spider_base', 'AssignedContent') UserComponent = apps.get_model('spider_base', 'UserComponent') for row in AssignedContent.objects.all(): if row.nonce: row.token = '%s/%s' % (row.id, row.nonce) row.nonce = None row.save() for row in UserComponent.objects.all(): if row.nonce: row.token = '%s/%s' % (row.id, row.nonce) row.nonce = None row.save() def move_tokens_back(apps, schema_editor): AssignedContent = apps.get_model('spider_base', 'AssignedContent') UserComponent = apps.get_model('spider_base', 'UserComponent') for row in AssignedContent.objects.all(): if "/" in row.token: row.nonce = row.token.split("/", 1)[1] else: row.nonce = row.token.split("_", 1)[1] row.save() for row in UserComponent.objects.all(): if "/" in row.token: row.nonce = row.token.split("/", 1)[1] else: row.nonce = row.token.split("_", 1)[1] row.save() class Migration(migrations.Migration): dependencies = [ ('spider_base', '0003_auto_20190128_1339'), ] operations = [ migrations.RunPython(move_tokens_forward, move_tokens_back), ] PK!u11<spkcspider/apps/spider/migrations/0005_auto_20190128_2230.py# Generated by Django 2.1.5 on 2019-01-28 22:30 import django.core.validators from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_base', '0004_auto_20190128_1349'), ] operations = [ migrations.AlterField( model_name='authtoken', name='token', field=models.CharField(db_index=True, max_length=138, unique=True, validators=[django.core.validators.RegexValidator('^[-a-zA-Z0-9_/]+\\Z', 'Enter a valid token.', 'invalid')]), ), ] PK!9><spkcspider/apps/spider/migrations/0008_auto_20190225_2034.py# Generated by Django 2.1.7 on 2019-02-25 20:34 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ('spider_base', '0007_usercomponent_allow_domain_mode'), ] def remove_breaking(apps, schema_editor): AuthToken = apps.get_model('spider_base', 'AuthToken') AuthToken.objects.filter(referrer__isnull=False).delete() operations = [ migrations.CreateModel( name='ReferrerObject', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('url', models.URLField(db_index=True, editable=False, max_length=600, unique=True)), ], ), migrations.RunPython(remove_breaking), migrations.AlterField( model_name='authtoken', name='referrer', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to='spider_base.ReferrerObject'), ), ] PK!G2<spkcspider/apps/spider/migrations/0009_auto_20190317_1405.py# Generated by Django 2.1.7 on 2019-03-17 14:05 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ('spider_base', '0008_auto_20190225_2034'), ] operations = [ migrations.AddField( model_name='assignedcontent', name='allow_domain_mode', field=models.BooleanField(default=False), ), migrations.AddField( model_name='assignedcontent', name='attached_to_content', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attached_contents', to='spider_base.AssignedContent'), ), migrations.AddField( model_name='assignedcontent', name='features', field=models.ManyToManyField(blank=True, limit_choices_to=models.Q(ctype__contains='g'), related_name='feature_for_contents', to='spider_base.ContentVariant'), ), migrations.AlterField( model_name='usercomponent', name='features', field=models.ManyToManyField(blank=True, limit_choices_to=models.Q(ctype__contains='f'), related_name='feature_for_components', to='spider_base.ContentVariant'), ), ] PK!e%Tspkcspider/apps/spider/migrations/0010_assignedcontent_attached_to_primary_anchor.py# Generated by Django 2.1.7 on 2019-03-22 19:35 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_base', '0009_auto_20190317_1405'), ] operations = [ migrations.AddField( model_name='assignedcontent', name='attached_to_primary_anchor', field=models.BooleanField(default=False, editable=False, help_text='Content references primary anchor'), ), ] PK!U{{<spkcspider/apps/spider/migrations/0011_auto_20190331_1222.py# Generated by Django 2.1.7 on 2019-03-31 12:22 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ('spider_base', '0010_assignedcontent_attached_to_primary_anchor'), ] operations = [ migrations.AddField( model_name='usercomponent', name='default_content_features', field=models.ManyToManyField(blank=True, help_text='Select features used by default for contents in this component', limit_choices_to=models.Q(ctype__contains='g'), related_name='default_feature_for_contents', to='spider_base.ContentVariant'), ), migrations.AlterField( model_name='usercomponent', name='primary_anchor', field=models.ForeignKey(blank=True, help_text='Select main identifying anchor. Also used for attaching persisting tokens (elsewise they are attached to component)', limit_choices_to={'info__contains': '\nanchor\n'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_anchor_for', to='spider_base.AssignedContent'), ), ] PK! <spkcspider/apps/spider/migrations/0012_auto_20190404_2116.py# Generated by Django 2.2 on 2019-04-04 21:16 import django.core.validators from django.db import migrations, models import spkcspider.apps.spider.validators class Migration(migrations.Migration): dependencies = [ ('spider_base', '0011_auto_20190331_1222'), ] operations = [ migrations.RemoveField( model_name='assignedcontent', name='fake_id', ), migrations.AddField( model_name='assignedcontent', name='description', field=models.TextField(blank=True, default=''), ), migrations.AddField( model_name='assignedcontent', name='name', field=models.CharField(blank=True, default='', max_length=255, validators=[ spkcspider.apps.spider.validators.content_name_validator ]), ), migrations.AlterField( model_name='usercomponent', name='name', field=models.CharField(db_index=True, help_text='Name of the component.
Note: there are special named components with different protection types and scopes.
Most prominent: "index" for authentication', max_length=255, validators=[django.core.validators.RegexValidator('^(\\w[\\w ]*\\w|\\w?)$')]), ), ] PK!6=<spkcspider/apps/spider/migrations/0013_auto_20190407_1313.py# Generated by Django 2.1.7 on 2019-04-07 13:13 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ('spider_base', '0012_auto_20190404_2116'), ] operations = [ migrations.AlterField( model_name='usercomponent', name='primary_anchor', field=models.ForeignKey(blank=True, help_text='Select main identifying anchor. Also used for attaching persisting tokens (elsewise they are attached to component)', limit_choices_to={'info__contains': '\x1eanchor\x1e'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_anchor_for', to='spider_base.AssignedContent'), ), ] PK!dx,ww<spkcspider/apps/spider/migrations/0014_auto_20190407_1416.py# Generated by Django 2.2 on 2019-04-07 14:16 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('contenttypes', '0002_remove_content_type_name'), ('spider_base', '0013_auto_20190407_1313'), ] operations = [ migrations.AlterUniqueTogether( name='assignedcontent', unique_together={('content_type', 'object_id')}, ), migrations.AddConstraint( model_name='assignedcontent', constraint=models.UniqueConstraint(fields=('usercomponent', 'info'), name='unique_info'), ), ] PK!U-c<spkcspider/apps/spider/migrations/0015_auto_20190414_1743.py# Generated by Django 2.2 on 2019-04-14 17:43 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('spider_base', '0014_auto_20190407_1416'), ] operations = [ migrations.RenameField( model_name='assignedcontent', old_name='persist_token', new_name='attached_to_token', ), ] PK!R  <spkcspider/apps/spider/migrations/0016_auto_20190414_1743.py# Generated by Django 2.2 on 2019-04-14 17:43 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ('spider_base', '0015_auto_20190414_1743'), ] operations = [ migrations.AlterField( model_name='assignedcontent', name='attached_to_token', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='spider_base.AuthToken'), ), ] PK!-spkcspider/apps/spider/migrations/__init__.pyPK!0P)spkcspider/apps/spider/models/__init__.pyfrom .base import * # noqa: F401 F403 from .content_base import * # noqa: F401 F403 from .contents import * # noqa: F401 F403 from .protections import * # noqa: F401 F403 from .user import * # noqa: F401 F403 # db dependencies: user implementation # no db dependency: session PK!'%spkcspider/apps/spider/models/base.py__all__ = ["BaseInfoModel", "ReferrerObject", "info_and", "info_or"] import re from django.db import models from django.core.exceptions import ValidationError from django.utils.translation import gettext from django.utils.functional import cached_property from ..helpers import extract_host _info_replacer_templ = '\x1e{}.*\x1e' def info_and(*args, **kwargs): q = models.Q() for query in args: if isinstance(query, models.Q): q &= query else: q &= models.Q( info__contains="\x1e{}\x1e".format(query) ) for name, query in kwargs.items(): if query is None: q &= models.Q( info__contains="\x1e{}=".format(name) ) else: q &= models.Q( info__contains="\x1e{}={}\x1e".format(name, query) ) return q def info_or(*args, **kwargs): q = models.Q() for query in args: if isinstance(query, models.Q): q |= query else: q |= models.Q( info__contains="\x1e{}\x1e".format(query) ) for name, query in kwargs.items(): if query is None: q |= models.Q( info__contains="\x1e{}=".format(name) ) else: q |= models.Q( info__contains="\x1e{}={}\x1e".format(name, query) ) return q def info_field_validator(value): _ = gettext if value[-1] != "\x1e": raise ValidationError( _('%(value)s ends not with "\\x1e"'), code="syntax", params={'value': value}, ) if value[0] != "\x1e": raise ValidationError( _('%(value)s starts not with "\\x1e"'), code="syntax", params={'value': value}, ) # check elements for elem in value.split("\x1e"): if elem == "": continue f = elem.find("=") # no flag => allow multiple instances if f != -1: continue counts = 0 counts += value.count("\x1e%s\x1e" % elem) # check: is flag used as key in key, value storage counts += value.count("\x1e%s=" % elem) assert counts > 0, value if counts > 1: raise ValidationError( _('flag not unique: %(element)s in %(value)s'), params={'element': elem, 'value': value}, ) class ReferrerObject(models.Model): id = models.BigAutoField(primary_key=True, editable=False) url = models.URLField( max_length=600, db_index=True, unique=True, editable=False ) @cached_property def host(self): return extract_host(self.url) class BaseInfoModel(models.Model): class Meta: abstract = True # for extra information over content, admin only editing # format: \nflag1\nflag2\nfoo=true\nfoo2=xd\n...\nendfoo=xy\n # every section must start and end with \n every keyword must be unique and # in this format: keyword= # no unneccessary spaces! info = models.TextField( null=False, editable=False, validators=[info_field_validator] ) def getflag(self, flag): if "\x1e%s\x1e" % flag in self.info: return True return False def getlist(self, key, amount=None): info = self.info ret = [] pstart = info.find("\x1e%s=" % key) while pstart != -1: tmpstart = pstart+len(key)+2 pend = info.find("\x1e", tmpstart) if pend == -1: raise Exception( "Info field error: doesn't end with \"\\x1e\": \"%s\"" % info ) ret.append(info[tmpstart:pend]) pstart = info.find("\x1e%s=" % key, pend) # if amount=0 => bool(amount) == false if amount and amount <= len(ret): break return ret def replace_info(self, **kwargs): """ Warning! Order dependend (especially for unique tests) """ rep_replace = [] rep_missing = [] self.info = self.info.replace("{}", "{{}}") for name, val in kwargs.items(): pattern = re.compile( _info_replacer_templ.format(re.escape(name)), re.M ) if not val: # remove name self.info = pattern.sub( "\x1e", self.info, 0 ) continue # count replacements self.info, count1 = pattern.subn( "\x1e{}\x1e", self.info, 1 ) if count1 == 0: rep = rep_missing else: rep = rep_replace self.info = pattern.sub( "\x1e", self.info, 0 ) if val is True: rep.append(name) elif isinstance(val, (tuple, list)): rep.append("\x1e".join( map( lambda x: "{}={}".format(name, x), val ) )) else: rep.append("{}={}".format(name, val)) self.info = self.info.format(*rep_replace) if rep_missing: self.info = "{}\x1e{}\x1e".format( self.info, "\x1e".join(rep_missing) ) return rep_missing def info_and(self, *args, **kwargs): return info_and(*args, **kwargs) def info_or(self, *args, **kwargs): return info_or(*args, **kwargs) PK!u 6!!-spkcspider/apps/spider/models/content_base.py""" Base for Contents namespace: spider_base """ __all__ = [ "ContentVariant", "AssignedContent" ] import logging from django.db import models from django.utils.translation import gettext, gettext_lazy as _ from django.urls import reverse from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.core import validators from ..contents import installed_contents from ..protections import installed_protections # from ..constants import VariantType from ..helpers import validator_token, create_b64_id_token from ..constants import ( MAX_TOKEN_B64_SIZE, VariantType, hex_size_of_bigid ) from ..validators import content_name_validator from .base import BaseInfoModel logger = logging.getLogger(__name__) # ContentType is already occupied class ContentVariant(models.Model): id = models.BigAutoField(primary_key=True, editable=False) # usercontent abilities/requirements ctype = models.CharField( max_length=10, default="", ) code = models.CharField(max_length=255) name = models.SlugField(max_length=50, unique=True, db_index=True) # required protection strength (selection) strength = models.PositiveSmallIntegerField( default=0, validators=[validators.MaxValueValidator(10)] ) @property def installed_class(self): return installed_contents[self.code] @property def feature_urls(self): return installed_contents[self.code].cached_feature_urls(self.name) def localize_name(self): if self.code not in installed_protections: return self.name return self.installed_class.localize_name(self.name) @property def unique_for_component(self): return VariantType.unique.value in self.ctype def __str__(self): return self.localize_name() def __repr__(self): return "" % self.__str__() class UserContentManager(models.Manager): def create(self, **kwargs): ret = self.get_queryset().create(**kwargs) if not ret.token: ret.token = create_b64_id_token(ret.id, "/") ret.save(update_fields=["token"]) return ret def update_or_create(self, **kwargs): ret = self.get_queryset().update_or_create(**kwargs) if not ret[0].token: ret[0].token = create_b64_id_token(ret[0].id, "/") ret[0].save(update_fields=["token"]) return ret def get_or_create(self, defaults=None, **kwargs): ret = self.get_queryset().get_or_create(**kwargs) if not ret[0].token: ret[0].token = create_b64_id_token(ret[0].id, "/") ret[0].save(update_fields=["token"]) return ret class AssignedContent(BaseInfoModel): id = models.BigAutoField(primary_key=True, editable=False) # fake_level = models.PositiveIntegerField(null=False, default=0) attached_to_token = models.ForeignKey( "spider_base.AuthToken", blank=True, null=True, on_delete=models.CASCADE ) # don't use extensive recursion, # this can cause performance problems and headaches # this is not enforced for allowing some small chains # see SPIDER_MAX_EMBED_DEPTH setting for limits (default around 5) attached_to_content = models.ForeignKey( "self", blank=True, null=True, related_name="attached_contents", on_delete=models.CASCADE ) allow_domain_mode = models.BooleanField(default=False) # set to indicate creating a new token token_generate_new_size = None # brute force protection and identifier, replaces nonce # 16 = usercomponent.id in hexadecimal # +1 for seperator token = models.CharField( max_length=(MAX_TOKEN_B64_SIZE)+hex_size_of_bigid+2, db_index=True, unique=True, null=True, blank=True, validators=[validator_token] ) # regex disables controlcars and disable special spaces # and allows some of special characters name = models.CharField( max_length=255, blank=True, default="", validators=[ content_name_validator ] ) description = models.TextField( default="", blank=True ) usercomponent = models.ForeignKey( "spider_base.UserComponent", on_delete=models.CASCADE, related_name="contents", null=False, blank=False ) features = models.ManyToManyField( "spider_base.ContentVariant", related_name="feature_for_contents", blank=True, limit_choices_to=models.Q( ctype__contains=VariantType.content_feature.value ) ) # ctype is here extended: VariantObject with abilities, name, model_name ctype = models.ForeignKey( ContentVariant, editable=False, null=True, on_delete=models.SET_NULL ) priority = models.SmallIntegerField(default=0, blank=True) # creator = models.ForeignKey( # settings.AUTH_USER_MODEL, editable=False, null=True, # on_delete=models.SET_NULL # ) created = models.DateTimeField(auto_now_add=True, editable=False) modified = models.DateTimeField(auto_now=True, editable=False) # only editable for admins deletion_requested = models.DateTimeField( null=True, blank=True, default=None ) # required protection strength (real) strength = models.PositiveSmallIntegerField( default=0, validators=[validators.MaxValueValidator(10)], editable=False ) # required protection strength for links, 11 to disable links strength_link = models.PositiveSmallIntegerField( default=0, validators=[validators.MaxValueValidator(11)], editable=False ) attached_to_primary_anchor = models.BooleanField( default=False, editable=False, null=False, help_text=_( "Content references primary anchor" ) ) content_type = models.ForeignKey( ContentType, on_delete=models.CASCADE, editable=False ) object_id = models.BigIntegerField(editable=False) content = GenericForeignKey( 'content_type', 'object_id', for_concrete_model=False ) # for quick retrieval!! even maybe a duplicate # layouts referencing models are not appearing here, so do it here references = models.ManyToManyField( "self", related_name="referenced_by", editable=False, symmetrical=False ) # info extra flags: # primary: primary content of type for usercomponent # unlisted: not listed objects = UserContentManager() class Meta: unique_together = [ ('content_type', 'object_id'), ] constraints = [ models.UniqueConstraint( fields=['usercomponent', 'info'], name='unique_info' ), # models.UniqueConstraint( # fields=['usercomponent', 'name'], name='unique_name', # condition=~models.Q(name='') # ) ] def __str__(self): return self.name def __repr__(self): return "".format( self.usercomponent.username, self.name ) def get_absolute_url(self, scope="view"): return reverse( "spider_base:ucontent-access", kwargs={"token": self.token, "access": scope} ) # use next_object, last_object instead get_next_by_FOO, ... # for preventing disclosure of elements def get_size(self): if not self.content: return 0 return self.content.get_size() def localized_description(self): """ """ if not self.content: return self.description return self.content.localized_description() def clean(self): _ = gettext if VariantType.persist.value in self.ctype.ctype: if not self.attached_to_token: raise ValidationError( _('Persistent token required'), code="persist", ) if not self.usercomponent.user_info.allowed_content.filter( name=self.ctype.name ).exists(): raise ValidationError( message=_('Not an allowed ContentVariant for this user'), code='disallowed_contentvariant' ) if self.strength > self.usercomponent.strength: raise ValidationError( _('Protection strength too low, required: %(strength)s'), code="strength", params={'strength': self.strength}, ) super().clean() PK!W`"`")spkcspider/apps/spider/models/contents.py""" Basic Contents like TravelProtection, or Links namespace: spider_base """ __all__ = [ "LinkContent", "TravelProtection" ] import logging from datetime import timedelta from django.db import models from django.utils.html import escape from django.shortcuts import redirect from django.utils.translation import gettext from django.middleware.csrf import CsrfViewMiddleware from django.http.response import HttpResponseBase from django.utils import timezone from django.urls import reverse from django.contrib.auth.hashers import ( check_password, make_password ) from django.utils.translation import gettext_lazy as _ from ..contents import BaseContent, add_content from ..constants import ( TravelLoginType, VariantType, ActionUrl ) logger = logging.getLogger(__name__) # raw and export: use references link_abilities = frozenset(["add", "update", "update_raw", "raw", "export"]) @add_content class PersistenceFeature(BaseContent): appearances = [ { "name": "Persistence", "ctype": VariantType.component_feature.value, "strength": 0 }, ] class Meta: abstract = True @classmethod def feature_urls(cls, name): return [ ActionUrl( reverse("spider_base:token-renew"), "renew-token" ), ActionUrl( reverse("spider_base:token-delete-request"), "delete-token" ) ] @add_content class LinkContent(BaseContent): appearances = [{ "name": "Link", "ctype": VariantType.raw_update.value }] expose_name = False expose_description = False content = models.ForeignKey( "spider_base.AssignedContent", related_name="+", on_delete=models.CASCADE, limit_choices_to={ "strength_link__lte": 10, } ) push = models.BooleanField( blank=True, default=False, help_text=_("Improve ranking of this Link.") ) def get_abilities(self, context): ret = set() if self.associated.getflag("anchor"): ret.add("anchor") return ret def get_content_name(self): return self.content.content.get_content_name() def get_content_description(self): return self.content.content.get_content_description() def get_strength(self): return self.content.content.get_strength() def get_priority(self): priority = self.content.content.get_priority() # pin to top if self.push and priority < 1: return 1 return priority def get_strength_link(self): # don't allow links linking on links return 11 def get_info(self): ret = self.content.content.get_info() return "%ssource=%s\x1elink\x1e" % ( ret, self.associated.pk ) def get_references(self): if not self.content: return [] return [self.content] def get_form(self, scope): from ..forms import LinkForm if scope in link_abilities: return LinkForm # maybe not required return self.content.content.get_form(scope) def get_form_kwargs(self, **kwargs): if kwargs["scope"] in link_abilities: ret = super().get_form_kwargs(**kwargs) ret["uc"] = kwargs["uc"] ret["request"] = kwargs["request"] else: # maybe not required anymore ret = self.content.content.get_form_kwargs(**kwargs) return ret def access_add(self, **kwargs): _ = gettext kwargs["legend"] = escape(_("Create Content Link")) return super().access_add(**kwargs) def access_update(self, **kwargs): _ = gettext kwargs["legend"] = escape(_("Update Content Link")) return super().access_update(**kwargs) def access_raw_update(self, **kwargs): return redirect( 'spider_base:ucontent-access', token=self.content.token, access='update' ) def access(self, context): # context is updated and used outside!! # so make sure that func gets only a copy (**) context.setdefault("extra_outer_forms", []) func = self.access_default if context["scope"] in link_abilities: func = getattr(self, "access_{}".format(context["scope"])) # check csrf tokens manually csrferror = CsrfViewMiddleware().process_view( context["request"], None, (), {} ) if csrferror is not None: # csrferror is HttpResponse return csrferror ret = func(**context) if context["scope"] == "update": # update function should never return HttpResponse # (except csrf error) assert(not isinstance(ret, HttpResponseBase)) return ret else: context["source"] = self context["uc"] = self.content.usercomponent ret = self.access(context) context["uc"] = self.usercomponent return ret login_choices = [ (TravelLoginType.none.value, _("No Login protection")), (TravelLoginType.fake_login.value, _("Fake login")), # TODO: to prevent circumventing deletion_period, tie to modified (TravelLoginType.wipe.value, _("Wipe")), (TravelLoginType.wipe_user.value, _("Wipe User")), ] _login_protection = _( "No Login Protection: normal, default
" "Fake Login: fake login and index
" "Wipe: Wipe protected content, " "except they are protected by a deletion period
" "Wipe User: destroy user on login" ) class TravelProtectionManager(models.Manager): def get_active(self, now=None, no_stop=False): if not now: now = timezone.now() q = models.Q(active=True, start__lte=now) if not no_stop: q &= (models.Q(stop__isnull=True) | models.Q(stop__gte=now)) return self.get_queryset().filter(q) def default_start(): return timezone.now()+timedelta(hours=3) def default_stop(): return timezone.now()+timedelta(days=7) @add_content class TravelProtection(BaseContent): appearances = [ { "name": "TravelProtection", "strength": 10, # "ctype": VariantType.unique.value } ] objects = TravelProtectionManager() active = models.BooleanField(default=False) is_fake = models.BooleanField(default=False) start = models.DateTimeField(default=default_start, null=False) # no stop for no termination stop = models.DateTimeField(default=default_stop, null=True) login_protection = models.CharField( max_length=10, choices=login_choices, default=TravelLoginType.none.value, help_text=_login_protection ) # use defaults from user hashed_secret = models.CharField( null=True, max_length=128 ) disallow = models.ManyToManyField( "spider_base.UserComponent", related_name="travel_protected", blank=True ) def get_abilities(self, context): return ("deactivate",) def check_password(self, raw_password): """ Return a boolean of whether the raw_password was correct. Handles hashing formats behind the scenes. """ def setter(raw_password): self.hashed_secret = make_password(raw_password) self.save(update_fields=["hashed_secret"]) return check_password( raw_password, self.hashed_secret, setter ) def get_strength_link(self): return 5 def get_priority(self): # pin to top with higher priority return 2 def get_form(self, scope): from ..forms import TravelProtectionForm return TravelProtectionForm def get_form_kwargs(self, **kwargs): ret = super().get_form_kwargs(**kwargs) ret["uc"] = kwargs["uc"] ret["request"] = kwargs["request"] return ret def access_add(self, **kwargs): _ = gettext kwargs["legend"] = escape(_("Create Travel Protection")) return super().access_add(**kwargs) def access_update(self, **kwargs): _ = gettext kwargs["legend"] = escape(_("Update Travel Protection")) return super().access_update(**kwargs) def access_view(self, **kwargs): return "" def access_deactivate(self, **kwargs): if self.hashed_secret: if not self.check_password( kwargs["request"].GET.get("travel", "") ): return "failure" self.active = False self.save() return "success" PK!Ùĉ++,spkcspider/apps/spider/models/protections.py""" Protections namespace: spider_base """ __all__ = ["Protection", "AssignedProtection", "AuthToken"] import logging from django.db import models from django.conf import settings from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.views.decorators.debug import sensitive_variables from jsonfield import JSONField from ..constants import ( MAX_TOKEN_B64_SIZE, hex_size_of_bigid, TokenCreationError ) from ..helpers import create_b64_id_token, validator_token from ..protections import installed_protections from ..constants import ProtectionType, ProtectionResult logger = logging.getLogger(__name__) _striptoken = getattr(settings, "TOKEN_SIZE", 30)*4//3 # show 1/3 of token _striptoken = _striptoken-_striptoken//3 class ProtectionManager(models.Manager): def invalid(self): return self.get_queryset().exclude(code__in=installed_protections) def valid(self): return self.get_queryset().filter(code__in=installed_protections) # don't confuse with Protection objects used with add_protection # this is pure DB class Protection(models.Model): objects = ProtectionManager() # autogenerated, no choices required code = models.SlugField(max_length=10, primary_key=True, db_index=False) # protection abilities/requirements ptype = models.CharField( max_length=10, default=ProtectionType.authentication.value ) @property def installed_class(self): return installed_protections[self.code] def __str__(self): return self.localize_name() def __repr__(self): return "" % self.__str__() def localize_name(self): if self.code not in installed_protections: return self.code return self.installed_class.localize_name(self.code) def auth_localize_name(self): if self.code not in installed_protections: return self.code return self.installed_class.auth_localize_name(self.code) @sensitive_variables("kwargs") def auth(self, request, obj=None, **kwargs): # never ever allow authentication if not active if obj and not obj.active: return False if self.code not in installed_protections: return False return self.installed_class.auth( obj=obj, request=request, **kwargs.copy() ) @classmethod @sensitive_variables("kwargs") def auth_query(cls, request, query, required_passes=1, **kwargs): initial_required_passes = required_passes ret = [] max_result = 0 for item in query: obj = None _instant_fail = False if hasattr(item, "protection"): # is AssignedProtection item, obj = item.protection, item _instant_fail = obj.instant_fail # would be surprising if auth fails with required_passes == 0 # achievable by required_passes = amount of protections if initial_required_passes == 0: _instant_fail = False result = item.auth( request=request, obj=obj, query=query, required_passes=initial_required_passes, **kwargs ) if _instant_fail: # instant_fail does not reduce required_passes if type(result) is not int: # False or form # set limit unreachable required_passes = len(query) else: if result > max_result: max_result = result elif type(result) is int: required_passes -= 1 if result > max_result: max_result = result if result is not False: # False will be not rendered ret.append(ProtectionResult(result, item)) # after side effects like raise Http404 if ( request.GET.get("protection", "") == "false" and initial_required_passes > 0 ): return False # don't require lower limit this way # against timing attacks if required_passes <= 0: return max_result return ret @classmethod @sensitive_variables("kwargs") def authall(cls, request, required_passes=1, ptype=ProtectionType.authentication.value, protection_codes=None, **kwargs): """ Usage: e.g. prerendering for login fields, because no assigned object is available there is no config """ query = cls.objects.filter(ptype__contains=ptype) # before protection_codes, for not allowing users # to manipulate required passes if required_passes > 0: # required_passes 1 and no protection means: login or token only required_passes = max(min(required_passes, len(query)), 1) else: query = query.filter( ptype__contains=ProtectionType.side_effects.value ) if protection_codes: query = query.filter( code__in=protection_codes ) return cls.auth_query( request, query.order_by("code"), required_passes=required_passes, ptype=ptype ) def get_form(self, prefix=None, **kwargs): if prefix: protection_prefix = "{}_protections_{{}}".format(prefix) else: protection_prefix = "protections_{}" return self.installed_class( protection=self, prefix=protection_prefix.format(self.code), **kwargs ) def render_raw(self, result): return {self.code: self.installed_class.render_raw(result)} @classmethod def get_forms(cls, ptype=None, **kwargs): protections = cls.objects.valid() if ptype: protections = protections.filter(ptype__contains=ptype) else: ptype = "" return map(lambda x: x.get_form(ptype=ptype, **kwargs), protections) def get_limit_choices_assigned_protection(): # django cannot serialize static, classmethods # possible????? index = models.Q(usercomponent__strength=10) restriction = models.Q( ~index, ptype__contains=ProtectionType.access_control.value ) restriction |= models.Q( index, ptype__contains=ProtectionType.authentication.value ) return models.Q(code__in=Protection.objects.valid()) & restriction class AssignedProtection(models.Model): id = models.BigAutoField(primary_key=True) # fix linter warning objects = models.Manager() protection = models.ForeignKey( Protection, on_delete=models.CASCADE, related_name="assigned", limit_choices_to=get_limit_choices_assigned_protection ) usercomponent = models.ForeignKey( "spider_base.UserComponent", related_name="protections", on_delete=models.CASCADE, editable=False ) # data for protection data = JSONField(default=dict, null=False) created = models.DateTimeField(auto_now_add=True, editable=False) modified = models.DateTimeField(auto_now=True, editable=False) active = models.BooleanField(default=True) instant_fail = models.BooleanField( default=False, help_text=_("Auth fails if test fails, stronger than required_passes\n" "Works even if required_passes=0\n" "Does not contribute to required_passes, " "ideal for side effects" ) ) class Meta: unique_together = [("protection", "usercomponent")] def __str__(self): return "%s -> %s" % ( self.usercomponent, self.protection.localize_name() ) def __repr__(self): return "" % ( self.__str__() ) @classmethod def authall(cls, request, usercomponent, ptype=ProtectionType.access_control.value, protection_codes=None, **kwargs): query = cls.objects.filter( protection__ptype__contains=ptype, active=True, usercomponent=usercomponent ) # before protection_codes, for not allowing users # to manipulate required passes if usercomponent.required_passes > 0: required_passes = max( min( usercomponent.required_passes, len(query.exclude(instant_fail=True)) ), 1 ) elif ptype == ProtectionType.authentication.value: # enforce a minimum of required_passes, if auth (e.g. index) required_passes = 1 else: required_passes = 0 # only protections with side effects query = query.filter( protection__ptype__contains=ProtectionType.side_effects.value ) if protection_codes: query = query.filter( protection__code__in=protection_codes ) return Protection.auth_query( request, query.order_by("protection__code"), required_passes=required_passes, ptype=ptype ) @property def user(self): return self.usercomponent.user class AuthToken(models.Model): id = models.BigAutoField(primary_key=True, editable=False) usercomponent = models.ForeignKey( "spider_base.UserComponent", on_delete=models.CASCADE, related_name="authtokens" ) # -1=false,0=usercomponent,1-...=anchor persist = models.BigIntegerField(blank=True, default=-1, db_index=True) # brute force protection # 16 = usercomponent.id in hexadecimal # +2 for seperators token = models.CharField( max_length=MAX_TOKEN_B64_SIZE+hex_size_of_bigid+2, db_index=True, unique=True, validators=[ validator_token ] ) referrer = models.ForeignKey( "spider_base.ReferrerObject", on_delete=models.CASCADE, related_name="tokens", blank=True, null=True ) created_by_special_user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="+", blank=True, null=True ) session_key = models.CharField(max_length=40, null=True) extra = JSONField(default=dict, blank=True) created = models.DateTimeField(auto_now_add=True, editable=False) def __str__(self): return "{}...".format(self.token[:-_striptoken]) def create_auth_token(self): self.token = create_b64_id_token( self.usercomponent.id, "_", getattr(settings, "TOKEN_SIZE", 30) ) def save(self, *args, **kwargs): for i in range(0, 1000): if i >= 999: raise TokenCreationError( 'A possible infinite loop was detected' ) self.create_auth_token() try: self.validate_unique() break except ValidationError: pass super().save(*args, **kwargs) PK!i///%spkcspider/apps/spider/models/user.py""" User Components/Info namespace: spider_base """ __all__ = [ "UserComponent", "UserComponentManager", "TokenCreationError", "UserInfo" ] import logging from django.db import models from django.conf import settings from django.apps import apps from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext from django.urls import reverse from django.core.exceptions import ValidationError from django.core import validators from ..helpers import get_settings_func, validator_token, create_b64_id_token from ..constants import ( ProtectionType, VariantType, MAX_TOKEN_B64_SIZE, TokenCreationError, index_names, hex_size_of_bigid ) from ..conf import default_uctoken_duration, force_captcha logger = logging.getLogger(__name__) _name_help = _( "Name of the component.
" "Note: there are special named components " "with different protection types and scopes.
" "Most prominent: \"index\" for authentication" ) _required_passes_help = _( "How many protection must be passed? " "Set greater 0 to enable protection based access" ) _feature_help = _( "Appears as featured on \"home\" page" ) class UserComponentManager(models.Manager): def _update_args(self, defaults, kwargs): if defaults is None: defaults = {} if kwargs is None: kwargs = defaults name = kwargs.get("name", defaults.get("name", None)) if name in index_names and force_captcha: defaults["required_passes"] = 2 defaults["strength"] = 10 elif name in index_names: defaults["required_passes"] = 1 defaults["strength"] = 10 elif kwargs.get("public", defaults.get("public", False)): if kwargs.get( "required_passes", defaults.get("required_passes", 0) ) == 0: defaults["strength"] = 0 else: defaults["strength"] = 4 else: if kwargs.get( "required_passes", defaults.get("required_passes", 0) ) == 0: defaults["strength"] = 5 else: defaults["strength"] = 9 return defaults def create(self, **kwargs): ret = self.get_queryset().create( **self._update_args(kwargs, None) ) if not ret.token: ret.token = create_b64_id_token(ret.id, "/") ret.save(update_fields=["token"]) return ret def update_or_create(self, defaults=None, **kwargs): ret = self.get_queryset().update_or_create( defaults=self._update_args(defaults, kwargs), **kwargs ) if not ret[0].token: ret[0].token = create_b64_id_token(ret[0].id, "/") ret[0].save(update_fields=["token"]) return ret def get_or_create(self, defaults=None, **kwargs): ret = self.get_queryset().get_or_create( defaults=self._update_args(defaults, kwargs), **kwargs ) if not ret[0].token: ret[0].token = create_b64_id_token(ret[0].id, "/") ret[0].save(update_fields=["token"]) return ret class UserComponent(models.Model): id = models.BigAutoField(primary_key=True, editable=False) # brute force protection and identifier, replaces nonce # 16 = usercomponent.id in hexadecimal # +1 for seperator token = models.CharField( max_length=(MAX_TOKEN_B64_SIZE)+hex_size_of_bigid+2, db_index=True, unique=True, null=True, blank=True, validators=[ validator_token ] ) public = models.BooleanField( default=False, help_text=_( "Is public? Is listed and searchable?
" "Note: This field is maybe not deactivatable " "because of assigned content" ) ) # special name: index, (fake_index): # protections are used for authentication # attached content is only visible for admin and user # db_index=True: "index", "fake_index" requests can speed up # regex disables controlcars and disable special spaces name = models.CharField( max_length=255, null=False, db_index=True, help_text=_name_help, validators=[validators.RegexValidator(r"^(\w[\w ]*\w|\w?)$")] ) description = models.TextField( default="", help_text=_( "Description of user component." ), blank=True ) required_passes = models.PositiveIntegerField( default=0, help_text=_required_passes_help ) # cached protection strength strength = models.PositiveSmallIntegerField( default=0, validators=[validators.MaxValueValidator(10)], editable=False ) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, ) features = models.ManyToManyField( "spider_base.ContentVariant", related_name="feature_for_components", blank=True, limit_choices_to=models.Q( ctype__contains=VariantType.component_feature.value ) ) default_content_features = models.ManyToManyField( "spider_base.ContentVariant", related_name="default_feature_for_contents", blank=True, limit_choices_to=models.Q( ctype__contains=VariantType.content_feature.value ), help_text=_( "Select features used by default for contents in this " "component" ) ) primary_anchor = models.ForeignKey( "spider_base.AssignedContent", related_name="primary_anchor_for", null=True, blank=True, limit_choices_to={ "info__contains": "\x1eanchor\x1e", }, on_delete=models.SET_NULL, help_text=_( "Select main identifying anchor. Also used for attaching " "persisting tokens (elsewise they are attached to component)" ) ) created = models.DateTimeField(auto_now_add=True, editable=False) modified = models.DateTimeField(auto_now=True, editable=False) # only admin featured = models.BooleanField(default=False, help_text=_feature_help) # both should not be edited can_auth = models.BooleanField(default=False, editable=False) allow_domain_mode = models.BooleanField(default=False) token_duration = models.DurationField( default=default_uctoken_duration, null=False ) # only editable for admins deletion_requested = models.DateTimeField( null=True, default=None, blank=True ) # fix linter warning objects = UserComponentManager() contents = None # should be used for retrieving active protections, related_name protections = None class Meta: unique_together = [("user", "name")] permissions = [("can_feature", "Can feature User Components")] def __str__(self): if self.strength == 10: return "index" return self.name def __repr__(self): return "" % (self.username, self.__str__()) def get_component_quota(self): return get_settings_func( "SPIDER_GET_QUOTA", "spkcspider.apps.spider.functions.get_quota" )(self.user, "usercomponents") def clean(self): _ = gettext self.public = (self.public and self.is_public_allowed) self.featured = (self.featured and self.public) assert(self.is_index or self.strength < 10) obj = self.contents.filter( strength__gt=self.strength ).order_by("strength").last() if obj: raise ValidationError( _( 'Protection strength too low, required: %(strength)s' ), code="strength", params={'strength': obj.strength}, ) def auth(self, request, ptype=ProtectionType.access_control.value, protection_codes=None, **kwargs): AssignedProtection = apps.get_model("spider_base.AssignedProtection") return AssignedProtection.authall( request, self, ptype=ptype, protection_codes=protection_codes, **kwargs ) def get_accumulated_size(self): _local_size = 0 _remote_size = 0 for elem in self.contents.all(): if VariantType.component_feature.value in elem.ctype.ctype: _remote_size += elem.get_size() else: _local_size += elem.get_size() return _local_size, _remote_size def get_absolute_url(self): return reverse( "spider_base:ucontent-list", kwargs={ "token": self.token } ) @property def username(self): return getattr(self.user, self.user.USERNAME_FIELD) @property def user_info(self): return self.user.spider_info @property def index(self): return UserComponent.objects.get(user=self.user, name="index") @property def is_index(self): return (self.name in index_names) @property def is_public_allowed(self): """ Can the public attribute be set """ return not self.is_index and not self.contents.filter( strength__gte=5 ).exists() @property def deletion_period(self): return getattr( settings, "SPIDER_COMPONENTS_DELETION_PERIODS", {} ).get(self.__str__(), None) class UserInfo(models.Model): """ Contains generated Informations about user """ id = models.BigAutoField(primary_key=True, editable=False) user = models.OneToOneField( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, editable=False, related_name="spider_info", ) allowed_content = models.ManyToManyField( "spider_base.ContentVariant", related_name="+", editable=False ) used_space_local = models.BigIntegerField(default=0, editable=False) used_space_remote = models.BigIntegerField(default=0, editable=False) class Meta: default_permissions = [] def calculate_allowed_content(self): from ..contents import installed_contents ContentVariant = apps.get_model("spider_base.ContentVariant") allowed = [] cfilterfunc = get_settings_func( "ALLOWED_CONTENT_FILTER", "spkcspider.apps.spider.functions.allow_all_filter" ) # Content types which are not "installed" should be removed/never used # unlisted needs not to be allowed as it is only a side product for variant in ContentVariant.objects.exclude( ctype__contains=VariantType.unlisted.value ).filter( code__in=installed_contents ): if cfilterfunc(self.user, variant): allowed.append(variant) # save not required, m2m field self.allowed_content.set(allowed) def calculate_used_space(self): from . import AssignedContent self.used_space_local = 0 self.used_space_remote = 0 for c in AssignedContent.objects.filter( usercomponent__user=self.user ): if VariantType.component_feature.value in c.ctype.ctype: self.used_space_remote += c.get_size() else: self.used_space_local += c.get_size() def update_with_quota(self, size_diff, quota_type): fname = "used_space_{}".format(quota_type) qval = getattr(self, fname) quota = get_settings_func( "SPIDER_GET_QUOTA", "spkcspider.apps.spider.functions.get_quota" )(self.user, quota_type) # if over quota: reducing size is always good and should never fail if quota and size_diff > 0 and qval + size_diff > quota: raise ValidationError( _("Exceeds quota by %(diff)s Bytes"), code='quota_exceeded', params={'diff': size_diff}, ) setattr( self, fname, models.F(fname)+size_diff ) PK!k@@%spkcspider/apps/spider/protections.py""" installed_protections, add_protection namespace: spider_base """ __all__ = ("installed_protections", "BaseProtection", "ProtectionResult", "initialize_protection_models") from random import SystemRandom from django.conf import settings from django import forms from django.http import Http404 from django.utils.translation import gettext_lazy as _ from django.utils.translation import pgettext from django.contrib.auth import authenticate from django.views.decorators.debug import sensitive_variables # from django.contrib.auth.hashers import make_password from .helpers import add_by_field from django.utils.crypto import constant_time_compare from .constants import ProtectionType, ProtectionResult, index_names from .fields import MultipleOpenChoiceField from .widgets import OpenChoiceWidget installed_protections = {} _sysrand = SystemRandom() # don't spam set objects _empty_set = frozenset() # for debug/min switch _extra = '' if settings.DEBUG else '.min' def initialize_protection_models(apps=None): if not apps: from django.apps import apps UserComponent = apps.get_model("spider_base", "UserComponent") AssignedProtection = apps.get_model("spider_base", "AssignedProtection") ProtectionModel = apps.get_model("spider_base", "Protection") for code, val in installed_protections.items(): ret = ProtectionModel.objects.get_or_create( defaults={"ptype": val.ptype}, code=code )[0] if ret.ptype != val.ptype: ret.ptype = val.ptype ret.save() login = ProtectionModel.objects.filter(code="login").first() if login: for uc in UserComponent.objects.filter(name="index"): asuc = AssignedProtection.objects.get_or_create( defaults={"active": True}, usercomponent=uc, protection=login )[0] if not asuc.active: asuc.active = True asuc.save() UserComponent.objects.filter(name__in=index_names).update(strength=10) invalid_models = ProtectionModel.objects.exclude( code__in=installed_protections.keys() ) if invalid_models.exists(): print("Invalid protections, please update or remove them:", [t.code for t in invalid_models]) class BaseProtection(forms.Form): """ Base for Protections Usage: use form to define some configurable fields for configuration use auth to validate: in this case: template_name, render, form variable are used """ use_required_attribute = False active = forms.BooleanField(label=_("Active"), required=False) instant_fail = forms.BooleanField( label=_("Instant fail"), required=False, help_text=_("Fail instantly if not fullfilled. " "Don't count to required_passes.") ) # unique code name max 10 slug chars # if imported by extract_app_dicts, name is automatically set to key name # name = foo # ptype valid for, is overwritten with current ptype ptype = ProtectionType.access_control.value # description of Protection description = None template_name = None # form for authentication form = None # optional render function # render = None @classmethod def render_raw(cls, result): return {} # auto populated, instance protection = None instance = None # initial values initial = {} def __init__(self, protection, ptype, request, assigned=None, **kwargs): self.protection = protection self.ptype = ptype if assigned: self.instance = assigned.filter(protection=self.protection).first() initial = self.get_initial() # does assigned protection exist? if self.instance: # if yes force instance.active information initial["active"] = self.instance.active initial["instant_fail"] = self.instance.instant_fail super().__init__(initial=initial, **kwargs) self.fields["active"].help_text = self.description def get_initial(self): initial = self.initial.copy() if self.instance: initial.update(self.instance.data) return initial def get_strength(self): # can provide strength in range 0-4 # 0 no protection # 1 weak protection # 2 normal protection # 3 strong protection # 4 reserved for login only, can be returned to auth user # tuple for min max return (1, 1) @staticmethod def extract_form_kwargs(request): kwargs = {} if request.method in ["POST", "PUT"]: kwargs = { 'data': request.POST, 'files': request.FILES, } return kwargs @classmethod def auth(cls, request, **kwargs): if hasattr(cls, "auth_form"): form = cls.auth_form(**cls.extract_form_kwargs(request)) if form.is_valid(): return 1 return form return False @classmethod def localize_name(cls, name=None): if not name: name = cls.name return pgettext("protection name", name.title()) @classmethod def auth_localize_name(cls, name=None): return cls.localize_name(name) def __str__(self): return self.localize_name(self.name) # only friends have access @add_by_field(installed_protections, "name") class FriendProtection(BaseProtection): name = "friends" ptype = ProtectionType.access_control.value users = MultipleOpenChoiceField( label=_("Users"), required=False, widget=OpenChoiceWidget( allow_multiple_selected=True, attrs={ "style": "min-width: 250px; width:100%" } ) ) description = _("Limit access to selected users") def get_strength(self): return (3, 3) @classmethod def auth(cls, request, obj, **kwargs): if ( obj and ( getattr(request.user, request.user.USERNAME_FIELD) in obj.data["users"] ) ): return 3 else: return False @add_by_field(installed_protections, "name") class RandomFailProtection(BaseProtection): name = "randomfail" ptype = ProtectionType.access_control.value ptype += ProtectionType.authentication.value ptype += ProtectionType.side_effects.value success_rate = forms.IntegerField( label=_("Success Rate"), min_value=20, max_value=100, initial=100, widget=forms.NumberInput(attrs={'type': 'range'}), help_text=_("Set success rate") ) use_404 = forms.BooleanField(label="Use 404 errors?", required=False) description = _( "Fail/Refuse randomly. Optionally with 404 error, " "to disguise correct access." ) def __init__(self, **kwargs): super().__init__(**kwargs) if ProtectionType.authentication.value in self.ptype: # login should not be possible alone with it self.fields["instant_fail"].initial = True self.initial["instant_fail"] = True self.fields["instant_fail"].disabled = True def get_strength(self): if self.cleaned_data["success_rate"] > 70: return (0, 0) return (1, 1) @classmethod def localize_name(cls, name=None): return pgettext("protection name", "Random Fail") @classmethod def auth(cls, request, obj, **kwargs): if obj and obj.data.get("success_rate", None): if _sysrand.randrange(1, 101) <= obj.data["success_rate"]: return 0 elif obj.data.get("use_404", False): raise Http404() return False @add_by_field(installed_protections, "name") class LoginProtection(BaseProtection): name = "login" ptype = ProtectionType.authentication.value ptype += ProtectionType.access_control.value description = _("Use Login password") allow_auth = forms.BooleanField( label=_("Component authentication"), required=False ) class auth_form(forms.Form): use_required_attribute = False password = forms.CharField( label=_("Password"), strip=False, widget=forms.PasswordInput, ) def __init__(self, **kwargs): super().__init__(**kwargs) if ProtectionType.authentication.value in self.ptype: self.fields["active"].initial = True self.initial["active"] = True self.fields["active"].disabled = True del self.fields["allow_auth"] def get_strength(self): return (3, 4 if self.cleaned_data.get("allow_auth", False) else 3) @classmethod @sensitive_variables("password") def auth(cls, request, obj, **kwargs): if not obj: return cls.auth_form() username = obj.usercomponent.username for password in request.POST.getlist("password")[:2]: if authenticate( request, username=username, password=password, nospider=True ): if obj.data.get("allow_auth", False): return 4 return 3 return cls.auth_form() @classmethod def auth_localize_name(cls, name=None): return cls.localize_name("Password") @add_by_field(installed_protections, "name") class PasswordProtection(BaseProtection): name = "password" ptype = ProtectionType.access_control.value ptype += ProtectionType.authentication.value description = _("Protect with extra passwords") prefix = "protection_passwords" class auth_form(forms.Form): use_required_attribute = False password = forms.CharField( label=_("Extra Password"), strip=False, widget=forms.PasswordInput, ) passwords = MultipleOpenChoiceField( label=_("Passwords"), required=False, widget=OpenChoiceWidget( allow_multiple_selected=True, attrs={ "style": "min-width: 250px; width:100%" } ) ) auth_passwords = MultipleOpenChoiceField( label=_("Passwords (for component authentication)"), required=False, widget=OpenChoiceWidget( allow_multiple_selected=True, attrs={ "style": "min-width: 250px; width:100%" } ) ) def __init__(self, **kwargs): super().__init__(**kwargs) if ProtectionType.authentication.value in self.ptype: del self.fields["auth_passwords"] @staticmethod def eval_strength(length): if not length: return 0 elif length > 15: return 2 elif length > 40: return 3 return 1 def get_strength(self): maxstrength = self.eval_strength(self.cleaned_data["max_length"]) if len(self.cleaned_data["auth_passwords"]) > 0: maxstrength = 4 return ( self.eval_strength(self.cleaned_data["min_length"]), maxstrength ) def clean_passwords(self): passwords = set() for pw in self.cleaned_data["passwords"]: if len(pw) > 0: passwords.add(pw) return list(passwords) def clean_auth_passwords(self): passwords = set() for pw in self.cleaned_data["auth_passwords"]: if self.eval_strength(len(pw)) >= 2: passwords.add(pw) return list(passwords) def clean(self): ret = super().clean() # prevents user self lockout if ProtectionType.authentication.value in self.ptype and \ len(self.cleaned_data["passwords"]) == 0: self.cleaned_data["active"] = False min_length = None max_length = None for pw in self.cleaned_data.get("apasswords", _empty_set): lenpw = len(pw) if not min_length or lenpw < min_length: min_length = lenpw if not max_length or lenpw > max_length: max_length = lenpw for pw in self.cleaned_data.get("auth_passwords", _empty_set): lenpw = len(pw) if not min_length or lenpw < min_length: min_length = lenpw if not max_length or lenpw > max_length: max_length = lenpw ret["min_length"] = min_length ret["max_length"] = max_length return ret @classmethod @sensitive_variables("password", "pw") def auth(cls, request, obj, **kwargs): retfalse = cls.auth_form() if not obj: retfalse.fields["password"].label = _( "Extra Password (if required)" ) return retfalse success = False auth = False max_length = 0 for password in request.POST.getlist("password")[:2]: for pw in obj.data["passwords"]: if constant_time_compare(pw, password): success = True if success: max_length = max(len(password), max_length) for password in request.POST.getlist("password")[:2]: for pw in obj.data["auth_passwords"]: if constant_time_compare(pw, password): success = True if success: max_length = max(len(password), max_length) auth = True if success: if auth: return 4 return cls.eval_strength(max_length) return retfalse if getattr(settings, "USE_CAPTCHAS", False): from captcha.fields import CaptchaField @add_by_field(installed_protections, "name") class CaptchaProtection(BaseProtection): name = "captcha" ptype = ProtectionType.access_control.value ptype += ProtectionType.authentication.value description = _("Require captcha") class auth_form(forms.Form): use_required_attribute = False prefix = "protection_captcha" auth_form.declared_fields[settings.SPIDER_CAPTCHA_FIELD_NAME] = \ CaptchaField(label=_("Captcha")) auth_form.base_fields[settings.SPIDER_CAPTCHA_FIELD_NAME] = \ auth_form.declared_fields[settings.SPIDER_CAPTCHA_FIELD_NAME] def __init__(self, **kwargs): super().__init__(**kwargs) if ProtectionType.authentication.value in self.ptype: # login should not be possible with captcha alone self.initial["instant_fail"] = True self.fields["instant_fail"].initial = True self.fields["instant_fail"].disabled = True self.fields["instant_fail"].help_text = \ _("instant_fail is for login required") if getattr(settings, "REQUIRE_LOGIN_CAPTCHA", False): self.initial["active"] = True self.fields["active"].disabled = True self.fields["instant_fail"].help_text = \ _("captcha is for login required (admin setting)") def get_strength(self): return (1, 1) @classmethod def localize_name(cls, name=None): return pgettext("protection name", "Captcha Protection") @classmethod def auth(cls, request, obj, **kwargs): if not obj: return cls.auth_form() form = cls.auth_form(**cls.extract_form_kwargs(request)) if request.method != "GET" and form.is_valid(): return 1 return form # travel protection @add_by_field(installed_protections, "name") class TravelProtection(BaseProtection): name = "travel" ptype = ProtectionType.access_control.value ptype += ProtectionType.authentication.value description = _("Deny access if valid travel protection is active") def get_strength(self): return (0, 0) @classmethod def auth(cls, request, obj, **kwargs): if obj: from .models import TravelProtection as TravelProtectionContent travel = TravelProtectionContent.objects.get_active() # simple: disallow cannot be changed by user in fake mode travel = travel.filter( disallow__contains=obj.usercomponent ) if not travel.exists(): return 0 return False PK![ max_counter: break counter += 1 if len(item) == 0: continue use_strict = False if item.startswith("!!"): _item = item[1:] elif item.startswith("__"): _item = item[1:] elif item.startswith("!_"): _item = item[2:] use_strict = True elif item.startswith("!"): _item = item[1:] elif item.startswith("_"): _item = item[1:] use_strict = True else: _item = item qob = Q() if use_strict: if use_contents: qob |= Q(contents__info__contains="\x1e%s\x1e" % _item) # exclude unlisted from searchterms qob &= notsearch else: qob |= Q(description__icontains=_item) if _item == "index": qob |= Q(strength=10) elif use_strict: qob |= Q( name=_item, strength__lt=10 ) else: qob |= Q( name__icontains=_item, strength__lt=10 ) if item.startswith("!!"): searchq |= qob elif item.startswith("!"): searchq_exc |= qob else: searchq |= qob return (searchq & ~searchq_exc, counter) def filter_contents( searchlist, idlist, filter_unlisted=True, use_components=False ): searchq = Q() searchq_exc = Q() counter = 0 unlisted_active = False # against ddos max_counter = settings.SPIDER_MAX_SEARCH_PARAMETERS for item in searchlist: if filter_unlisted is True and item == "_unlisted": continue elif item == "_unlisted": unlisted_active = True if counter > max_counter: break counter += 1 if len(item) == 0: continue use_strict = False negate = False if item.startswith("!!"): _item = item[1:] elif item.startswith("__"): _item = item[1:] elif item.startswith("!_"): _item = item[2:] use_strict = True negate = True elif item.startswith("!"): _item = item[1:] negate = True elif item.startswith("_"): _item = item[1:] use_strict = True else: _item = item if use_strict: qob = Q(name=_item) qob |= Q(info__contains="\x1e%s\x1e" % _item) # can exclude/include specific usercomponents names if use_components: qob |= Q(usercomponent__name=_item) else: qob = Q(name__icontains=_item) qob |= Q(description__icontains=_item) qob |= Q(info__icontains=_item) # but don't be too broad with unspecific negation # only apply on contents if use_components and not negate: qob |= Q(usercomponent__name__icontains=_item) qob |= Q(usercomponent__description__icontains=_item) if negate: searchq_exc |= qob else: searchq |= qob if idlist: # idlist contains int and str entries try: ids = map(lambda x: int(x), idlist) except ValueError: # deny any access in case of an incorrect id ids = [] searchq &= ( Q( id__in=ids ) ) if not unlisted_active: if filter_unlisted is True: searchq_exc |= Q(info__contains="\x1eunlisted\x1e") else: searchq_exc |= Q( info__contains="\x1eunlisted\x1e", priority__lte=filter_unlisted ) return (searchq & ~searchq_exc, counter) PK!#M7&7&%spkcspider/apps/spider/serializing.py__all__ = [ "paginate_stream", "serialize_stream", "serialize_content", "serialize_component", "list_features" ] from urllib.parse import urljoin from django.http import Http404 from django.core.paginator import InvalidPage, Paginator from django.db.models import Q from rdflib import URIRef, Literal, XSD from .constants import spkcgraph, VariantType from .conf import get_anchor_domain from .helpers import add_property # TODO replace by proper tree search (connect by/recursive query) def references_q(ids, limit, prefix=""): ids = set(ids) q = Q() for i in range(0, limit+1): q |= Q(**{"{}{}id__in".format(prefix, "referenced_by__"*i): ids}) return q def list_features(graph, entity, ref_entity, context): from .models import UserComponent, ContentVariant if not ref_entity: return if isinstance(entity, UserComponent): active_features = entity.features.all() else: active_features = ContentVariant.objects.filter( Q(feature_for_contents=entity) | Q(feature_for_components=entity.usercomponent) ) add_property( graph, "features", ref=ref_entity, literal=active_features.values_list("name", flat=True), datatype=XSD.string, iterate=True ) for feature in active_features: if context["scope"] != "export": for url_feature, name in feature.feature_urls: url_feature = urljoin( context["hostpart"], url_feature ) ref_feature = URIRef(url_feature) graph.add(( ref_entity, spkcgraph["action:feature"], ref_feature )) graph.add(( ref_feature, spkcgraph["feature:name"], Literal(name, datatype=XSD.string) )) def serialize_content(graph, content, context, embed=False): if VariantType.anchor.value in content.ctype.ctype: url_content = urljoin( get_anchor_domain(), content.get_absolute_url() ) else: url_content = urljoin( context["hostpart"], content.get_absolute_url() ) ref_content = URIRef(url_content) # is already node in graph if (ref_content, spkcgraph["type"], None) in graph: return ref_content if ( context.get("ac_namespace", None) and context["sourceref"] != ref_content ): graph.add(( context["sourceref"], context["ac_namespace"], ref_content )) add_property( graph, "name", ref=ref_content, ob=content, datatype=XSD.string ) add_property( graph, "description", ref=ref_content, ob=content, datatype=XSD.string ) add_property( graph, "info", ref=ref_content, ob=content, datatype=XSD.string ) add_property( graph, "id", ref=ref_content, literal=content.id, datatype=XSD.integer ) add_property( graph, "priority", ref=ref_content, ob=content ) if ( VariantType.component_feature.value in content.ctype.ctype or VariantType.content_feature.value in content.ctype.ctype ): graph.add(( ref_content, spkcgraph["type"], Literal("Feature", datatype=XSD.string) )) else: graph.add(( ref_content, spkcgraph["type"], Literal("Content", datatype=XSD.string) )) if context["scope"] == "export": add_property( graph, "attached_to_content", ref=ref_content, ob=content ) if embed: list_features(graph, content, ref_content, context) content.content.serialize(graph, ref_content, context) return ref_content def serialize_component(graph, component, context, visible=True): # visible: everything is visible elsewise only public url_component = urljoin( context["hostpart"], component.get_absolute_url() ) ref_component = URIRef(url_component) if component.public: visible = True if not visible and ref_component != context["sourceref"]: return None graph.add(( ref_component, spkcgraph["type"], Literal("Component", datatype=XSD.string) )) if component.primary_anchor: url_content = urljoin( context["hostpart"], component.primary_anchor.get_absolute_url() ) add_property( graph, "primary_anchor", ref=ref_component, literal=url_content, datatype=XSD.anyURI ) if component.public or context["scope"] == "export": add_property( graph, "user", ref=ref_component, literal=component.username ) add_property( graph, "name", ref=ref_component, literal=component.__str__() ) add_property( graph, "description", ref=ref_component, ob=component ) if context["scope"] == "export": add_property( graph, "required_passes", ref=ref_component, ob=component ) add_property( graph, "token_duration", ref=ref_component, ob=component ) graph.add(( ref_component, spkcgraph["strength"], Literal(component.strength) )) add_property( graph, "features", ref=ref_component, literal=component.features.values_list("name", flat=True), datatype=XSD.string, iterate=True ) if ( context.get("uc_namespace", None) and context["sourceref"] != ref_component ): graph.add(( context["sourceref"], context["uc_namespace"], ref_component )) return ref_component def paginate_stream(query, page_size, limit_depth=None): # WARNING: if AssignedContent queryset is empty # no usercomponent can be retrieved # so don't use AssignedContent queryset if serializing an # empty usercomponent from .models import AssignedContent if query.model == AssignedContent: query = AssignedContent.objects.filter( references_q( query.values_list("id", flat=True), limit_depth or 30 ) ) query = query.order_by("usercomponent__id", "id") else: query = query.order_by("id") return Paginator( query, page_size, orphans=0, allow_empty_first_page=True ) def serialize_stream( graph, paginators, context, page=1, embed=False, restrict_inclusion=True, restrict_embed=False ): # restrict_inclusion: only public components of contents are included # restrict_embed: only contents with no restrictions are embedded from .models import UserComponent if not isinstance(paginators, (tuple, list)): paginators = [paginators] if page <= 1: num_pages = max(map(lambda p: p.num_pages, paginators)) per_page = sum(map(lambda p: p.per_page, paginators)) graph.add(( context["sourceref"], spkcgraph["pages.num_pages"], Literal(num_pages, datatype=XSD.positiveInteger) )) graph.add(( context["sourceref"], spkcgraph["pages.size_page"], Literal(per_page, datatype=XSD.positiveInteger) )) invalid_pages = 0 for paginator in paginators: try: page_view = paginator.get_page(page) except InvalidPage: invalid_pages += 1 continue graph.add(( context["sourceref"], spkcgraph["pages.current_page"], Literal(page_view.number, datatype=XSD.positiveInteger) )) if paginator.object_list.model == UserComponent: for component in page_view.object_list: ref_component = serialize_component( graph, component, context ) list_features(graph, component, ref_component, context) else: # either start with invalid usercomponent which will be replaced # or use bottom-1 usercomponent to detect split if page <= 1: usercomponent = None ref_component = None else: _pos = ((page - 1) * paginator.per_page) - 1 usercomponent = paginator.object_list[_pos] ref_component = URIRef(urljoin( context["hostpart"], usercomponent.get_absolute_url() )) for content in page_view.object_list: if usercomponent != content.usercomponent: usercomponent = content.usercomponent ref_component = serialize_component( graph, usercomponent, context, visible=not restrict_inclusion ) list_features(graph, usercomponent, ref_component, context) _embed = embed if restrict_embed and content.usercomponent.strength != 0: _embed = False ref_content = serialize_content( graph, content, context, embed=_embed ) if ref_component: graph.add(( ref_component, spkcgraph["contents"], ref_content )) if invalid_pages == len(paginators): raise Http404('Invalid page (%(page_number)s)' % { 'page_number': page }) PK!W7r/%/%!spkcspider/apps/spider/signals.py__all__ = ( "UpdateSpiderCb", "InitUserCb", "UpdateAnchorContent", "UpdateAnchorTargets", "UpdateAnchorComponent", "update_dynamic", "DeleteContentCb", "RemoveTokensLogout", "CleanupCb", "MovePersistentCb", "move_persistent", "failed_guess" ) from django.dispatch import Signal from django.contrib.auth import get_user_model from django.conf import settings from .constants import VariantType from .helpers import create_b64_id_token import logging update_dynamic = Signal(providing_args=[]) move_persistent = Signal(providing_args=["tokens", "to"]) # failed guess of token failed_guess = Signal(providing_args=["request"]) _empty_set = frozenset() def TriggerUpdate(sender, **_kwargs): results = update_dynamic.send_robust(sender) for (receiver, result) in results: if isinstance(result, Exception): logging.error( "%s failed", receiver, exc_info=result ) logging.info("Update of dynamic content completed") def DeleteContentCb(sender, instance, **_kwargs): # connect if your content object can be deleted without AssignedContent from django.apps import apps ContentType = apps.get_model("contenttypes", "ContentType") AssignedContent = apps.get_model("spider_base", "AssignedContent") AssignedContent.objects.filter( object_id=instance.id, content_type=ContentType.objects.get_for_model(sender) ).delete() def CleanupCb(sender, instance, **kwargs): stored_exc = None if sender._meta.model_name == "usercomponent": # if component is deleted the content deletion handler cannot find # the user. Here if the user is gone counting doesn't matter anymore if instance.user and instance.user.spider_info: try: s = instance.get_accumulated_size() # because of F expressions no atomic is required instance.user.spider_info.update_with_quota(-s[0], "local") instance.user.spider_info.update_with_quota(-s[1], "remote") instance.user.spider_info.save( update_fields=["used_space_local", "used_space_remote"] ) except Exception as exc: logging.exception( "update size failed, trigger expensive recalculation" ) stored_exc = exc elif sender._meta.model_name == "assignedcontent": if instance.usercomponent and instance.usercomponent.user: f = "local" if ( instance.ctype and VariantType.component_feature.value in instance.ctype.ctype ): f = "remote" try: instance.usercomponent.user.spider_info.update_with_quota( -instance.get_size(), f ) # because of F expressions no atomic is required instance.usercomponent.user.spider_info.save( update_fields=["used_space_local", "used_space_remote"] ) except Exception as exc: logging.exception( "update size failed, trigger expensive recalculation" ) stored_exc = exc if instance.content: instance.content.delete(False) if stored_exc: update_dynamic.send(sender) raise stored_exc def UpdateAnchorContent(sender, instance, raw=False, **kwargs): if raw: return from django.apps import apps AuthToken = apps.get_model("spider_base", "AuthToken") if instance.primary_anchor_for.exists(): if "\x1eanchor\x1e" not in instance.info: # don't call signals, be explicit with bulk=True (default) instance.primary_anchor_for.clear(bulk=True) # update here AuthToken.objects.filter( persist=instance.id ).update(persist=0) def UpdateAnchorComponent(sender, instance, raw=False, **kwargs): if raw: return from django.apps import apps AuthToken = apps.get_model("spider_base", "AuthToken") UserComponent = apps.get_model("spider_base", "UserComponent") old = UserComponent.objects.filter(pk=instance.pk).first() if old and old.primary_anchor != instance.primary_anchor: if instance.primary_anchor: persist = 0 if old.primary_anchor: persist = old.primary_anchor.id AuthToken.objects.filter( persist=persist ).update(persist=instance.primary_anchor.id) else: AuthToken.objects.filter( persist=old.primary_anchor.id ).update(persist=0) def MovePersistentCb(sender, tokens, to, **kwargs): from django.apps import apps AssignedContent = apps.get_model("spider_base", "AssignedContent") AssignedContent.objects.filter( attached_to_token__in=tokens ).update(usercomponent=to) def UpdateAnchorTargets(sender, instance, raw=False, **kwargs): if raw: return from django.apps import apps UserComponent = apps.get_model("spider_base", "UserComponent") AssignedContent = apps.get_model("spider_base", "AssignedContent") old = UserComponent.objects.filter(pk=instance.pk).first() if old and old.primary_anchor != instance.primary_anchor: if old.primary_anchor: old.primary_anchor.referenced_by.clear( old.primary_anchor.referenced_by.filter( attached_to_primary_anchor=True ) ) if instance.primary_anchor: instance.primary_anchor.referenced_by.add( AssignedContent.objects.filter( usercomponent=instance, attached_to_primary_anchor=True ) ) def UpdateSpiderCb(**_kwargs): # provided apps argument lacks model function support # so use this from django.apps import apps from django.db import transaction from .contents import initialize_content_models from .protections import initialize_protection_models initialize_content_models(apps) initialize_protection_models(apps) # regenerate info field AssignedContent = apps.get_model("spider_base", "AssignedContent") UserComponent = apps.get_model("spider_base", "UserComponent") for row in AssignedContent.objects.all(): # works only with django.apps.apps row.info = row.content.get_info() if not row.token: row.token = create_b64_id_token(row.id, "/") if not row.content.expose_name or not row.name: row.name = row.content.get_content_name() if not row.content.expose_description: row.description = row.content.get_content_description() assert(row.description is not None) assert(row.name is not None) row.save(update_fields=['name', 'description', 'info', "token"]) for row in UserComponent.objects.all(): if not row.token: row.token = create_b64_id_token(row.id, "/") row.save(update_fields=["token"]) for row in get_user_model().objects.prefetch_related( "spider_info", "usercomponent_set", "usercomponent_set__contents" ).all(): with transaction.atomic(): row.spider_info.calculate_allowed_content() row.spider_info.calculate_used_space() row.spider_info.save() def InitUserCb(sender, instance, raw=False, **kwargs): from django.apps import apps if raw: return Protection = apps.get_model("spider_base", "Protection") UserComponent = apps.get_model("spider_base", "UserComponent") UserInfo = apps.get_model("spider_base", "UserInfo") # overloaded get_or_create calculates strength, ... uc = UserComponent.objects.get_or_create( defaults={"public": False}, name="index", user=instance )[0] login = Protection.objects.filter(code="login").first() if login: uc.protections.update_or_create( defaults={"active": True}, protection=login )[0] if getattr(settings, "USE_CAPTCHAS", False): captcha = Protection.objects.filter(code="captcha").first() uc.protections.get_or_create( defaults={"active": True}, protection=captcha )[0] uinfo = UserInfo.objects.get_or_create( user=instance )[0] # save not required, m2m field uinfo.calculate_allowed_content() if kwargs.get("created", False): for name, value in getattr( settings, "DEFAULT_USERCOMPONENTS", {} ).items(): # overloaded get_or_create calculates strength, ... ob, created = UserComponent.objects.get_or_create( defaults={"public": value.get("public", False)}, name=name, user=instance ) if created: for f in value.get("features", _empty_set): feature = uinfo.allowed_content.filter( name=f ).first() if feature: ob.features.add(feature) def RemoveTokensLogout(sender, user, request, **kwargs): from django.apps import apps AuthToken = apps.get_model("spider_base", "AuthToken") AuthToken.objects.filter( created_by_special_user=user, session_key=request.session.session_key ).delete() PK!"spkcspider/apps/spider/sitemaps.py__all__ = ["sitemaps", "ComponentSitemap", "ContentSitemap", "HomeSitemap"] from django.contrib.sitemaps import GenericSitemap, Sitemap from django.urls import reverse from django.conf import settings class ComponentSitemap(GenericSitemap): priority = 0.3 changefreq = "daily" date_field = "modified" if not settings.DEBUG: protocol = "https" def __init__(self): from .models import UserComponent self.queryset = UserComponent.objects.filter( public=True ) class ContentSitemap(GenericSitemap): priority = 0.7 changefreq = "hourly" date_field = "modified" if not settings.DEBUG: protocol = "https" def __init__(self): from .models import AssignedContent self.queryset = AssignedContent.objects.filter( usercomponent__public=True ).exclude(info__contains="_unlisted") class HomeSitemap(Sitemap): priority = 0.5 changefreq = 'daily' if not settings.DEBUG: protocol = "https" def items(self): return ['home'] def location(self, item): return reverse(item) sitemaps = { 'components': ComponentSitemap, 'contents': ContentSitemap, 'home': HomeSitemap } PK! /spkcspider/apps/spider/static/schemes/spkcgraph@prefix spkc: . @prefix rdf: . @prefix rdfs: . @prefix cc: . @prefix owl: . @prefix dc: . @prefix xsd: . a owl:Ontology ; dc:title "The spkcgraph format" ; dc:description "This is the spkcgraph format used by spkcspider." ; cc:license . spkc:Entity a rdfs:Class ; rdfs:isDefinedBy ; rdfs:label "Entity" ; rdfs:comment "A spkc Entity. Contains spkc:Property." . spkc:type a rdf:value ; rdfs:isDefinedBy ; rdfs:label "type" ; rdfs:comment "The type of an Entity." ; rdfs:domain spkc:Entity ; rdfs:subClassOf rdfs:Container; rdfs:range ( rdfs:Literal rdf:nil ) . spkc:strength a rdf:value ; rdfs:isDefinedBy ; rdfs:label "Strength" ; rdfs:comment "The protection strength of an component or content access." ; rdfs:domain rdfs:Resource ; rdfs:range xsd:integer . spkc:Component a rdfs:Class ; rdfs:isDefinedBy ; rdfs:label "Component" ; rdfs:subClassOf spkc:Entity ; rdfs:comment "Container for contents. Defines Protections." . spkc:Content a rdfs:Class ; rdfs:isDefinedBy ; rdfs:label "Content" ; rdfs:subClassOf spkc:Entity ; rdfs:comment "Contains real content." . spkc:Feature a rdfs:Class ; rdfs:isDefinedBy ; rdfs:label "Feature" ; rdfs:subClassOf spkc:Entity ; rdfs:comment "Contains Feature." . spkc:Intention a rdfs:Class ; rdfs:isDefinedBy ; rdfs:label "Intention" ; rdfs:comment "What can be done with referred token." . spkc:Protection a rdfs:Class ; rdfs:isDefinedBy ; rdfs:label "Protection" ; rdfs:subClassOf spkc:Entity ; rdfs:comment "Protection" . spkc:contents a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "Contents" ; rdfs:comment "Set of Contents" ; rdf:Seq spkc:Content ; rdfs:domain rdfs:Resource . spkc:components a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "Components" ; rdfs:comment "Set of components" ; rdf:Seq spkc:Component ; rdfs:domain rdfs:Resource . spkc:Property a rdfs:Class ; rdfs:isDefinedBy ; rdfs:label "Structured Property" ; rdfs:comment "Structured Property of an Entity." . spkc:properties a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "Properties" ; rdfs:comment "Set of properties" ; rdf:Bag spkc:Property ; rdfs:domain rdfs:Entity . spkc:accessTo a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "AccessTo" ; rdfs:comment "Set of Entities which can be accessed" ; rdf:Bag rdfs:Entity ; rdfs:domain rdfs:Resource . spkc:hashableURI a rdfs:Datatype ; rdfs:isDefinedBy ; rdfs:subClassOf xsd:anyURI . spkc:value a rdf:value ; rdfs:isDefinedBy ; rdfs:label "value" ; rdfs:comment "The value of spkc:properties" ; rdfs:domain spkc:Property ; rdfs:range rdfs:Literal . spkc:name a rdf:value ; rdfs:isDefinedBy ; rdfs:label "name" ; rdfs:comment "The name of a spkc:property, protection" ; rdfs:domain spkc:Property ; rdfs:domain spkc:Protection ; rdfs:domain spkc:Entity ; rdfs:range xsd:string . spkc:fieldname a rdf:value ; rdfs:isDefinedBy ; rdfs:label "name" ; rdfs:comment "(Optional) the original fieldname of a property (in case form is used)." ; rdfs:domain spkc:Property ; rdfs:range xsd:string . spkc:hashable a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "hashable" ; rdfs:comment "Should this property be used for hashing/verification?" ; rdfs:domain spkc:Property ; rdfs:range xsd:boolean . spkc:action:view a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "view" ; rdfs:comment "View/Access Resource" ; rdfs:domain spkc:Entity ; rdfs:range xsd:anyURI . spkc:action:feature a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "feature url" ; rdfs:comment "Access Feature" ; rdfs:domain spkc:Entity ; rdfs:range rdfs:Resource . spkc:action:update a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "update url" ; rdfs:comment "(Optional) Update Resource. In raw mode not defined." ; rdfs:domain spkc:Entity ; rdfs:range rdfs:Resource . spkc:action:delete a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "delete" ; rdfs:comment "(Optional) Delete Resource. In raw mode not defined." ; rdfs:domain spkc:Entity ; rdfs:range rdfs:Resource . spkc:feature:name a rdf:value ; rdfs:isDefinedBy ; rdfs:label "feature name" ; rdfs:comment "Annotates Feature Url with name" ; rdfs:domain rdfs:Resource ; rdfs:range rdf:string . spkc:ability:name a rdf:value ; rdfs:isDefinedBy ; rdfs:label "ability name" ; rdfs:comment "Name of extra access method" ; rdfs:domain rdfs:Resource ; rdfs:range rdf:string . spkc:pages.num_pages a rdf:value ; rdfs:isDefinedBy ; rdfs:label "Amount Pages" ; rdfs:comment "The amount of pages. 1 indicates no paging" ; rdfs:domain rdfs:Resource ; rdfs:range xsd:positiveInteger . spkc:pages.size_page a rdf:value ; rdfs:isDefinedBy ; rdfs:label "Page Size" ; rdfs:comment "The size of the page." ; rdfs:domain rdfs:Resource ; rdfs:range xsd:positiveInteger . spkc:pages.current_page a rdf:value ; rdfs:isDefinedBy ; rdfs:label "Page Size" ; rdfs:comment "The current page." ; rdfs:domain rdfs:Resource ; rdfs:range xsd:positiveInteger . spkc:verified a rdf:value ; rdfs:isDefinedBy ; rdfs:label "Verified" ; rdfs:comment "false, timestamp of verification or true in case timestamp not available" ; rdfs:domain rdfs:Resource ; rdfs:range ( xsd:boolean xsd:dateTime ) . spkc:hash a rdf:value ; rdfs:isDefinedBy ; rdfs:label "Hash" ; rdfs:comment "(Optional) Hash of an external resource in hexadecimal" ; rdfs:domain rdfs:Resource ; rdfs:range xsd:string . spkc:hash.algorithm a rdf:value ; rdfs:isDefinedBy ; rdfs:label "Hash Algorithm" ; rdfs:comment "The hash algorithm used for generating hash" ; rdfs:domain spkc:hash ; rdfs:range xsd:string . spkc:hashed a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "Hashed Objects" ; rdfs:comment "Link to hashed resources. Used by verifier." ; rdfs:domain rdfs:Resource ; rdfs:range spkc:hash . spkc:protections a rdf:Property ; rdfs:isDefinedBy ; rdfs:label "Protections" ; rdfs:comment "Protections" ; rdfs:domain rdfs:Resource ; rdfs:range spkc:Protection . spkc:protections.amount a rdf:value ; rdfs:isDefinedBy ; rdfs:label "Protection Amount" ; rdfs:comment "Amount of protections" ; rdfs:subPropertyOf spkc:protections ; rdfs:range xsd:nonNegativeInteger . PK!{ibb=spkcspider/apps/spider/static/spider_base/ListEditorWidget.jsdocument.addEventListener("DOMContentLoaded", function(){ let collection = document.getElementsByClassName("SpiderListTarget"); for (let counter=0;counter image/svg+xml PK!G}554spkcspider/apps/spider/static/spider_base/robots.txtUser-agent: * Disallow: /admin/ Disallow: /accounts/ PK!kk9spkcspider/apps/spider/static/spider_base/statebutton.css.statebutton_wrap { position: relative; display: inline-block; width: 30px; height: 30px; } .statebutton_wrap input { opacity: 0; width: 0; height: 0; } .statebutton { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #000; } input:disabled + .statebutton { cursor: default; } .statebutton:before { position: absolute; content: ""; height: 26px; width: 26px; left: 2px; bottom: 2px; background-color: #FF1010; } input:focus + .statebutton { box-shadow: 0 0 1px #2196F3; } input:checked + .statebutton:before { position: absolute; content: ""; height: 26px; width: 26px; left: 2px; bottom: 2px; background-color: #10FF10; } /* Rounded statebuttons */ input.round + .statebutton { border-radius: 34px; } input.round + .statebutton:before { border-radius: 50%; } PK!},=spkcspider/apps/spider/static/spider_base/travelprotection.js document.addEventListener("DOMContentLoaded", function(){ let self_protection = document.getElementById("id_self_protection"); let token_wrapper = document.getElementById("id_token_arg_wrapper"); let new_pw_wrapper = document.getElementById("id_new_pw_wrapper"); let new_pw2_wrapper = document.getElementById("id_new_pw2_wrapper"); if(self_protection.value != "pw"){ new_pw_wrapper.style.display = "none"; new_pw2_wrapper.style.display = "none"; } if(self_protection.value != "token"){ token_wrapper.style.display = "none"; } self_protection.addEventListener("change", function (event){ if(event.target.value == "pw"){ new_pw_wrapper.style.display = ""; new_pw2_wrapper.style.display = ""; } else { new_pw_wrapper.style.display = "none"; new_pw2_wrapper.style.display = "none"; } if(event.target.value == "token"){ token_wrapper.style.display = ""; }else { token_wrapper.style.display = "none"; } }) }) PK!ؕYAOO<spkcspider/apps/spider/static/spider_base/trumbowygWidget.js document.addEventListener("DOMContentLoaded", function(){ $('.TrumbowygTarget').trumbowyg({ imageWidthModalEdit: true, resetCss:true, minimalLinks: true, urlProtocol: true, lang: document.documentElement.lang || "en", btnsDef: { // Create a new dropdown insert: { dropdown: ['insertImage', 'base64', 'insertAudio'], ico: 'insertImage' }, justify: { dropdown: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], ico: 'justifyFull' }, formatting: { dropdown: ['p', 'preformatted', 'blockquote', 'h1', 'h2', 'h3', 'h4'], ico: 'p' }, textmarkup: { dropdown: ['strong', 'em', 'del', 'superscript', 'subscript'], ico: 'strong' }, lists: { dropdown: ['unorderedList', 'orderedList'], ico: 'unorderedList' } }, btns: [ ['viewHTML'], ['historyUndo', 'historyRedo'], ['formatting', 'textmarkup', 'foreColor', 'backColor', 'removeformat'], ['emoji', 'link', 'insert'], ['justify'], ['table','horizontalRule'], ['lists', 'lineheight'], ['fullscreen'] ], plugins: { table: { rows: 12, columns: 10 }, } }); }); PK!3 3 +spkcspider/apps/spider/static/spidercss.css.spkc-content{ max-width:980px; margin-right:10px; width:auto; } @media (min-width:990px){ .spkc-content{ width:100vw; margin-left:5px; margin-right:auto; } } @media (min-width:1200px){ .spkc-content{ width:100vw; margin: auto; } } @media (min-width:1400px){ .spkc-content{ width:100vw; max-width:1200px; } } .spkc-form input[type=text] { padding:8px; display:block; border:none; border-bottom:1px solid #ccc; width:100%; } .spkc-form textarea { resize: none; padding:8px; display:block; border:none; border-bottom:1px solid #ccc; width:100%; } .spkc-variant-text{ height:100%; width:100%; position:absolute; left:0; top:0; } .spkc-variant-errcolor{ /* dark red */ color:#fff!important; background-color:#660000!important; } .spkc-entity-button-text{ font-size:17px!important; width:100%; position:absolute; left:0; top:0; vertical-align: middle; text-align:center; } .bodyflex { display: flex; min-height: 100vh; flex-direction: column; } header.cond-top-fix{ width:100%; z-index:99; } main.cond-top-fix{ margin-top:20px; } main.footer-fix{ margin-bottom:10px; flex: 1; } @media (min-width:601px){ .spkc-entity-button{ width:25px; } @media (min-height:300px){ header.cond-top-fix{ top:0; position:fixed; } main.cond-top-fix{ margin-top:45px; } } } .protection-wrapping{ padding: 4px 6px 4px 0px; } .color-fresh-contentvariant{ /* pale green more green */ color:#000; background-color:#d9ffb3; } .color-used-contentvariant{ /* pale green more blue */ color:#000; background-color:#ddffdd; } .color-used-and-unique-contentvariant{ /* orange */ color:#000; background-color:#ffe6b3; } .color-front-0{ /* pale blue */ color:#000; background-color:#ddffff; } .color-back-0{ /* blue */ color:#fff; background-color:#2196F3; } .color-front-1, .color-front-2, .color-front-3, .color-front-4{ /* pale blue2 */ color:#000; background-color:#ccf5ff; } .color-back-1, .color-back-2, .color-back-3, .color-back-4{ /* blue2 */ color:#fff; background-color:#005266; } .color-front-5{ /* pale green */ color:#000; background-color:#ddffdd; } .color-back-5{ /* green */ color:#fff; background-color:#4CAF50; } .color-front-6, .color-front-7, .color-front-8{ /* pale green */ color:#000; background-color:#d9ffb3; } .color-back-6, .color-back-7, .color-back-8{ /* green */ color:#fff; background-color:#264d00; } .color-front-9{ /* pale violet */ color:#000; background-color:#ffccff; } .color-back-9{ /* dark violet */ color:#fff; background-color:#660066; } .color-front-10{ /* pale yellow */ color:#000; background-color:#ffffcc; } .color-back-10{ /* yellow */ color:#000; background-color:#ffeb3b; } PK!I1*7spkcspider/apps/spider/templates/flatpages/default.html{% extends "spider_base/nouc_base.html" %} {% block title %}{{ flatpage.title }}{% endblock %} {% block content %}

{{ flatpage.title }}

{{ flatpage.content|safe }}
{% endblock %} PK!!8 # #Hspkcspider/apps/spider/templates/spider_base/assignedcontent_access.html{% extends "spider_base/uc_base.html" %} {% load i18n static spider_base spider_rdf %} {% block title %}{% if scope == "add" %}{% blocktrans trimmed with name=object %} Create Content: {{name}} {% endblocktrans %}{% elif scope == "update" %}{% blocktrans trimmed with name=object %} Update Content: {{name}} {% endblocktrans %}{% else %}{% blocktrans trimmed with name=object %} Content: {{name}} {% endblocktrans %}{% endif %}{% endblock %} {% block public_meta %} {{block.super}} {% if scope != "add" %} {% endif %} {% endblock %} {% block extrahead %} {{block.super}} {% if form %} {{form.media|safe}} {% endif %} {% if content %} {{content.1|safe}} {% endif %} {% endblock %} {% block main_classes %}{{block.super}}{% if scope == "add" %} w3-animate-bottom{% endif %}{% endblock %} {% block main_attributes %}{{block.super}} typeof="spkc:Content"{% endblock %} {% block content %} {% if scope != "add" and feature_type in object.ctype.ctype %} {% else %} {% endif %} {% for f in active_features %} {% for action in f.feature_urls %} {% endfor %} {% endfor %} {% for ability in abilities %} {% endfor %}
{% if request.is_special_user or uc.public %} {# index is redirected in view #} {% trans 'Index' %} {% endif %} {% if scope != "add" and request.is_special_user %}
{% if scope != "view" %} {% trans "View" %} {% endif %} {% if scope != "update" %} {% trans "Update" %} {% endif %}
{% endif %} {% if scope != "add" %} {% trans 'Link/Export' %} {% endif %}
{% trans "Strength:" %} {{uc.strength}}

{% if scope == "add" %} {% trans object.name as local_name %} {% blocktrans trimmed %} Create Content {% endblocktrans %}: {{local_name}} {% elif scope == "update" %} {% trans object.ctype.name as local_name %} {% blocktrans trimmed %} Update {% endblocktrans %} "{{object}}" ({{local_name}}) {% else %} {% trans object.ctype.name as local_name %} {{object}} ({{local_name}}) {% endif %}

{% if object %} {% endif %} {% if request.token_expires %}
{% include 'spider_base/partials/token_lifetime.html' with rtime=request.token_expires %}
{% endif %}
{% if scope == "view" %} {% if uc.public or request.is_special_user %} {% if object.previous_object or object.next_object %}
{% if object.previous_object %} {% trans "previous" %} {% endif %} {% if object.next_object %} {% trans "Next" %} {% endif %}
{% endif %} {% endif %} {% endif %} {% if active_features.exists %}

{% trans 'Active Component Features' %}:

{% for f in active_features %} {{f}}{% if not forloop.last %}, {% endif %} {% endfor %}
{% endif %} {% block render_content %} {{content.0|safe}} {% endblock %} {% endblock %} {% block outercontent %} {% if request.is_owner %} {% url 'spider_base:ucontent-access' token=object.token access='export' as exportlink %} {% endif %} {% if scope != "add" %} {# remotelink generated in view #} {% include "spider_base/partials/modalpresenter.html" %} {% endif %} {% endblock %} PK!֡YPspkcspider/apps/spider/templates/spider_base/assignedcontent_confirm_delete.html{% extends "spider_base/uc_base.html" %} {% load i18n %} {% block title %}{% blocktrans trimmed %} Delete Content {% endblocktrans %}{% endblock %} {% block content %}
{# index is redirected in view #} {% trans 'Back' %}

{% blocktrans trimmed %} Delete Content {% endblocktrans %}: {{object.content}}

{% blocktrans trimmed with name=object %} Warning this deletes the content.
Go back if you don't want to this and you didn't hit delete.
You can abort the deletion process with reset if it is shown.
It is always shown except no deletion is in progress. {% endblocktrans %}
{% if not remaining or remaining == 0 %}
{% csrf_token %}
{% endif %} {% if remaining %}
{% csrf_token %}
{% endif %}
{% endblock %} PK! Fspkcspider/apps/spider/templates/spider_base/assignedcontent_form.html{% extends "spider_base/assignedcontent_access.html" %} {% load i18n spider_rdf %} {% block render_content %} {% include "spider_base/partials/form_errors.html" with form=form %}
{% csrf_token %}

{% trans 'Content Settings' %}

{% include "spider_base/partials/base_form.html" with form=form %}
{% if content %}

{% if scope == "add" %}{{object}}{% else %}{{object.ctype}}{% endif %}

{{content.0|safe}}
{% endif %}
{% if scope == "update" and raw_update_type in object.ctype.ctype %} {% trans 'Update Raw Content' %} {% endif %} {% if scope != "add" %} {% trans "View" %} {% endif %}
{% for i in extra_outer_forms %} {% endfor %} {% endblock %} {% block outercontent %} {{block.super}} {# FIXME: fix not availabe VariantType #} {% if scope == "update" and raw_update_type in object.ctype.ctype %} {% url 'spider_base:ucontent-access' token=object.token access='raw_update' as raw_updatelink %} {% endif %} {% endblock %} PK!22Fspkcspider/apps/spider/templates/spider_base/assignedcontent_list.html{% extends "spider_base/uc_base.html" %} {% load i18n static spider_base spider_paging spider_rdf %} {% block title %}{% if uc.public %}{% blocktrans trimmed %} Content of: {% endblocktrans %} {{uc}} ({{uc.username}}){% else %}{% blocktrans trimmed %} Content List {% endblocktrans %}{% endif %}{% endblock %} {% block public_meta %} {{block.super}} {% endblock %} {% block extrahead %} {{block.super}} {% if DEBUG %} {% else %} {% endif %} {% endblock %} {% block content %}
{% if request.user == uc.user %} {% trans 'Own' %} {% endif %} {% trans 'Public' %} {% if request.user == uc.user %} {% trans 'Update' %} {% endif %} {% if request.is_staff and uc.user != request.user %} {% trans 'User' %} {% endif %} {% trans 'Link/Export' %}
{% trans "Strength:" %} {{uc.strength}}

{% if request.is_special_user or uc.public %} {% blocktrans trimmed %} Content of: {% endblocktrans %} {{uc}} ({{uc.user}}) {% else %} {% blocktrans trimmed %} Content: {% endblocktrans %} {% endif %}

{% for f in active_features %} {% for action in f.feature_urls %} {% endfor %} {% endfor %} {% if uc.description %}
{{uc.description|linebreaks}}
{% endif %} {% if request.token_expires %}
{% include 'spider_base/partials/token_lifetime.html' with rtime=request.token_expires %}
{% endif %}
{% list_parameters "search" as cur_search %} {% if request.is_owner and not cur_search %}
{% trans 'Add new content' %}
{% include "spider_base/partials/content_variants_box.html" %}
{% endif %} {% if active_features.exists %}

{% trans 'Active Component Features' %}:

{% for f in active_features %} {{f}}{% if not forloop.last %}, {% endif %} {% endfor %}
{% endif %}
{% for content in object_list %} {% empty %} {% endfor %}
{{ content.name }}({{ content.ctype }}) {% if uc.public or request.is_special_user %} {% blocktrans trimmed with time=content.modified|timesince %} Modified: {{time}} ago {% endblocktrans %} {% endif %}
{% if request.is_special_user %}
{% endif %}
{% with content.localized_description as description %} {% if not description|is_not_or_space %} {{description|escape|linebreaksbr}} {% else %} {% trans 'No description' %} {% endif %} {% endwith %}
{% trans 'No User Content available.' %}
{% list_parameters "search" as cur_search %} {% if request.is_special_user and has_unlisted and "_unlisted" not in cur_search %} {% blocktrans trimmed %} This Component contains unlisted content, use "_unlisted" in search to see and edit them {% endblocktrans %} {% endif %} {% include 'spider_base/partials/list_footer.html' %}
{% endblock %} {% block outercontent %} {% if request.is_owner %} {% url 'spider_base:ucontent-export' token=uc.token as exportlink %} {% with exportlink=exportlink|add:"?"|add:spider_GET.urlencode %} {% include "spider_base/partials/modalpresenter.html" %} {% endwith %} {% else %} {% include "spider_base/partials/modalpresenter.html" %} {% endif %} {% endblock %} PK!U  6spkcspider/apps/spider/templates/spider_base/base.html{% load i18n %} {% load static spider_base %} {% load flatpages %} {% block title %}{% endblock %} {% if is_public_view %} {% block public_meta %}{% endblock %} {% else %} {% endif %} {% block favicon %} {% endblock %} {# CSS for all pages #} {% block base_styles %} {% endblock %} {# CSS for page #} {% block styles %}{% endblock %} {# form scripts and foo #} {% block extrahead %}{% endblock %} {# use sub div instead body because of select2 uses wrong colors elsewise #}
{# Deco content, e.g.base.html #} {% block layout %} {% block headerbar %}
{% include "spider_base/partials/headerbar.html"%}
{% endblock headerbar %}
{% block messages %} {% if messages %}
    {% for message in messages %}
  • {{ message|capfirst }}
  • {% endfor %}
{% endif %} {% endblock messages %} {# Main content #} {% block content %}{% endblock %}
{% block global_footer %} {% concat_string '/' LANGUAGE_CODE '/gfooter/' as global_footer_localized %} {% get_flatpages global_footer_localized for request.user as footer_pages %} {% if not footer_pages %} {% get_flatpages '/gfooter/' for request.user as footer_pages %} {% endif %} {% if footer_pages %} {% endif %} {% endblock %} {% endblock %} {# Scripts #} {% block scripts %} {% endblock %} {# e.g. modals #} {% block outercontent %}{% endblock %}
PK!;spkcspider/apps/spider/templates/spider_base/edit_form.html{% load spider_rdf %} {% include "spider_base/partials/form_errors.html" with form=form %} <{% if not inner_form %}form action="{{ request.get_full_path }}" enctype="{{enctype}}" method="post"{% else %}div{% endif %}> {% for rdftype in add_spkc_types %} {% endfor %} {% if not inner_form %}{% csrf_token %}{% endif %}

{{legend|safe}}

{% include "spider_base/partials/base_form.html" with form=form %}
{% if not inner_form %} {% endif %} <{% if not inner_form %}/form{% else %}/div{% endif %}> PK!7Dspkcspider/apps/spider/templates/spider_base/forms/widgets/info.html
{% if widget.value != None %}{{ widget.value|stringformat:'s' }}{% endif %}
PK!m9r  Kspkcspider/apps/spider/templates/spider_base/forms/widgets/statebutton.html PK!%@||Ospkcspider/apps/spider/templates/spider_base/forms/widgets/subsectionstart.html
{% if label %}

{{ label }}

{% endif %} PK!T Nspkcspider/apps/spider/templates/spider_base/forms/widgets/subsectionstop.html
PK!#"Nspkcspider/apps/spider/templates/spider_base/forms/widgets/wrapped_select.html PK!UpPspkcspider/apps/spider/templates/spider_base/forms/widgets/wrapped_textarea.html PK!!Mspkcspider/apps/spider/templates/spider_base/forms/widgets/wrapper_attrs.html{% for name, value in widget.wrapper_attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %} PK!8) ) 6spkcspider/apps/spider/templates/spider_base/home.html{% extends "spider_base/nouc_base.html" %} {% load i18n static spider_rdf spider_base %} {% load flatpages %} {% block title %}{% blocktrans trimmed %} Home {% endblocktrans %}{% endblock %} {% block public_meta %} {{block.super}} {% endblock %} {% block extrahead %} {{block.super}} {% if DEBUG %} {% else %} {% endif %} {% endblock %} {% block main_classes %}{{block.super}} w3-card-4{% endblock %} {% block content %} {% block frontblock %}
{% concat_string '/' LANGUAGE_CODE '/home/heading/' as heading_pages_localized %} {% get_flatpages heading_pages_localized for request.user as heading_pages %} {% if not heading_pages %} {% get_flatpages '/home/heading/' for request.user as heading_pages %} {% endif %} {% for page in heading_pages %}
{{page.content|safe}}
{% empty%}

{% blocktrans trimmed %} Welcome to spkcspider {% endblocktrans %}

{% endfor %} {% concat_string '/' LANGUAGE_CODE '/home/main/' as main_pages_localized %} {% get_flatpages main_pages_localized for request.user as main_pages %} {% if not main_pages %} {% get_flatpages '/home/main/' for request.user as main_pages %} {% endif %} {% for page in main_pages %}

{{page.title}}


{{page.content|safe}}
{% endfor %}
{% endblock %} {% block featured_components %} {% if object_list|length > 0 %}

{% trans "Featured:" %}

{% include "spider_base/usercomponent_list_fragment.html" %}
{% url 'spider_base:ucomponent-listpublic' as searchpath %} {% include 'spider_base/partials/list_footer.html' with searchpath=searchpath %}
{% endif %} {% endblock featured_components %} {% endblock %} PK!E;spkcspider/apps/spider/templates/spider_base/nouc_base.html{% extends "spider_base/base.html" %} {% load spider_rdf %} {% block main_classes %}{{block.super}} spkc-content w3-card-4 w3-sand{% endblock %} {% block body_class %}{{block.super}} w3-light-grey{% endblock %} {% block main_attributes %}{{block.super}} prefix="spkc: {% spkc_namespace %} xsd: http://www.w3.org/2001/XMLSchema# rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#" resource="{{hostpart}}{{ request.path }}"{% endblock %} PK!sm Dspkcspider/apps/spider/templates/spider_base/partials/base_form.html{% load i18n widget_tweaks spider_rdf %} {% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %}
    {% for field in form.visible_fields %} {% if field.field.widget.input_type == "start_pseudo" %}
  • {% render_field field %}
      {% elif field.field.widget.input_type == "stop_pseudo" %}
    {% render_field field %}
  • {% else %}
  • {# cell row #}
    {% hashable_literalize field as hashable %}
    {% if field.field.widget.allow_multiple_selected %} {% else %} {% literalize field as LiteralData %} {% if LiteralData|is_uriref %} {% else %} {% endif %} {% endif %} {% block form_widget %} {% if field|widget_type == "checkboxselectmultiple" %} {% render_field field class+="w3-ul" %} {% elif field|widget_type == "HTMLWidget" %} {% render_field field class+="w3-white" style="padding: 6px 8px;" %} {% else %} {% render_field field %} {% endif %} {% endblock form_widget %}
    {% if field.help_text %}
    {{ field.help_text|safe }}
    {% endif %}
    {% if field.errors %}
      {% for error in field.errors %}
    • {{ error }}
    • {% endfor %}
    {% endif %} {% endif %}
  • {% empty %}{{form_empty_message|safe}}{% endfor %}
PK!wm Ispkcspider/apps/spider/templates/spider_base/partials/base_view_form.html{% load i18n widget_tweaks spider_rdf %}
    {% for field in form.visible_fields %} {% if field.field.widget.input_type == "start_pseudo" %}
  • {% render_field field %}
      {% elif field.field.widget.input_type == "stop_pseudo" %}
    {% render_field field %}
  • {% else %}
  • {% hashable_literalize field as hashable %}
    {{ field.label_tag }}
    {% if field.field.widget.allow_multiple_selected %}
      {% for val in field.value %} {% literalize val field as LiteralData %} {% if LiteralData != 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'|uriref %} {% if LiteralData|is_uriref %}
    • {{val|default_if_none:""}}
    • {% elif LiteralData.datatype == 'http://www.w3.org/2001/XMLSchema#anyURI'|uriref %}
    • {{val|default_if_none:""}}
    • {% else %}
    • {{val|default_if_none:""}}
    • {% endif %} {% endif %} {% empty %} {% endfor %}
    {% else %} {% literalize field as LiteralData %} {% if LiteralData|is_uriref %} {{field.value|default_if_none:""}} {% elif LiteralData.datatype == 'http://www.w3.org/2001/XMLSchema#anyURI'|uriref %} {{field.value|default_if_none:""}} {% else %} {% literalize field as LiteralData %} {{field.value|default_if_none:""}} {% endif %} {% endif %}
  • {% endif %} {% empty %}{{form_empty_message|safe}}{% endfor %}
PK!Ospkcspider/apps/spider/templates/spider_base/partials/content_variants_box.html{% load i18n static %}
{% for variant in content_variants %} {% if uc.strength < variant.strength %} {{variant}} {% else %} {{variant}} {% endif %} {% empty %} {% trans 'No Content Variants available.' %} {% endfor %}
PK!zBBFspkcspider/apps/spider/templates/spider_base/partials/form_errors.html{% load i18n %} {% block errors %} {% if form.errors or form.non_field_errors %}

{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}

{% if form.non_field_errors %}
    {% for error in form.non_field_errors %}
  • {{ error }}
  • {% endfor %}
{% endif %}
{% endif %} {% endblock errors %} PK!O!==Dspkcspider/apps/spider/templates/spider_base/partials/headerbar.html{% load i18n %} {% load static spider_base %} {% url 'auth:login' as loginpath %} {% if SETTINGS.OPEN_FOR_REGISTRATION %} {% url 'auth:signup' as registerpath %} {% endif %}
{% if request.user.is_authenticated %} {% endif %}
{% include "spider_base/partials/language_box.html" with attrs=" style=\"width:150px\"" %}
{% if request.user.is_authenticated %}
{% block user_actions %} {% trans 'Profile' %} {% trans 'Security' %} {% if request.user.is_superuser or request.user.is_staff %} {% trans 'Admin' %} {% endif %} {% trans 'Logout' %} {% endblock user_actions %}
{% else %} {% if SETTINGS.OPEN_FOR_REGISTRATION %} {% trans 'Register' %} {% endif %} {% if request.path != loginpath %} {% trans 'Login' %} {% endif %} {% endif %}
{% include "spider_base/partials/language_box.html" %}
PK!jGspkcspider/apps/spider/templates/spider_base/partials/language_box.html{% load i18n %} {% load static spider_base %} {% get_current_language as LANGUAGE_CODE %} {% language "en" %} {% endlanguage %} PK!o Fspkcspider/apps/spider/templates/spider_base/partials/list_footer.html{% load i18n spider_paging %} {% list_parameters "search" as cur_search %} {% get_searchpath as searchpath %}
{% csrf_token %}
{# for clearing search #} {% if cur_search %} {% endif %}
{% blocktrans trimmed %} Use ! to negate. Use !! to start a search tag with !. Press Enter to confirm tag. The button with the magnifying glass on the right starts the search.
Use _ to start strict search. Escape with __.
Use !_ to start negated strict search. {% endblocktrans %} {% if request.is_special_user %}
{% blocktrans trimmed %} Use _unlisted to list/search "unlisted" marked content. {% endblocktrans %}{% endif %}
{% if page_obj.paginator.num_pages > 1 %}
{% csrf_token %} {% if page_obj.has_previous %} {% endif %} {% blocktrans trimmed with page=page_obj.number num_pages=page_obj.paginator.num_pages %} Page {{ page }} of {{ num_pages }}. {% endblocktrans %} {% if page_obj.has_next %} {% endif %}
{% endif %} PK!K Ispkcspider/apps/spider/templates/spider_base/partials/modalpresenter.html{% load i18n %} {# z-index important for trumbowyg, set higher if not enough #}
×

{% trans "QR/Export-Mode" %}


PK!x' Ispkcspider/apps/spider/templates/spider_base/partials/token_lifetime.html{% load i18n spider_base basic_math %} {% trans 'Remaining lifetime of token' %}: {% expires_delta rtime as delta %} {% blocktrans trimmed with days=delta.days hours=delta.seconds|divide:"3600" minutes=delta.seconds|remainder:"3600"|divide:"60" seconds=delta.seconds|remainder:"60" %} Days: {{days}} {{hours}}h:{{minutes}}m:{{seconds}}s {% endblocktrans %} PK!o66Vspkcspider/apps/spider/templates/spider_base/protections/authtoken_confirm_delete.html{% extends "spider_base/protections/base.html" %} {% load i18n %} {% block title %}{% blocktrans trimmed %} Delete AuthToken {% endblocktrans %}{% endblock %} {% block content %}
{# index is redirected in view #} {% trans 'Back' %}

{% blocktrans trimmed %} Delete AuthToken {% endblocktrans %}: {{object}}

{% blocktrans trimmed with name=object %} Warning: this deletes this token and all depending persisted data.
Go back if you don't want to do this.
{% endblocktrans %}
{% csrf_token %}
{% endblock %} PK!%%Bspkcspider/apps/spider/templates/spider_base/protections/base.html{% extends "spider_base/base.html" %} {% load spider_rdf%} {% block public_meta %} {{block.super}} {% if scope == "list" %} {% else %} {% endif %} {% endblock %} {% block context_actions %} {{block.super}} {% endblock context_actions %} {% block main_classes %}{{block.super}} spkc-content w3-card-4 color-front-{{uc.strength}}{% endblock %} {% block body_class %}{{block.super}} color-back-{{uc.strength}}{% endblock %} {% block main_attributes %}{{block.super}} prefix="spkc: {% spkc_namespace %} xsd: http://www.w3.org/2001/XMLSchema# rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#" resource="{{hostpart}}{{ request.path }}"{% endblock %} PK!x44Mspkcspider/apps/spider/templates/spider_base/protections/protection_form.html{% include "spider_base/partials/base_form.html" %} PK!OAAMspkcspider/apps/spider/templates/spider_base/protections/protection_item.html{% load spider_rdf spider_protections %}

{{prot.1.auth_localize_name}}

{% render_protection prot %}
PK!A``Ispkcspider/apps/spider/templates/spider_base/protections/protections.html{% extends "spider_base/protections/base.html" %} {% load spider_protections i18n spider_rdf %} {% block content %} {% extract_protections "protections" as protections %}
{% csrf_token %}

{% trans 'Protections' %}

{% for prot in protections %} {% include "spider_base/protections/protection_item.html" with prot=prot %} {% endfor %}
{% endblock %} PK!Gspkcspider/apps/spider/templates/spider_base/protections/referring.html{% extends "spider_base/protections/base.html" %} {% load i18n static spider_paging %} {% block extrahead %} {{block.super}} {% if DEBUG %} {% else %} {% endif %} {% endblock %} {% block content %}
{% csrf_token %}

{% trans 'Allow' %}:

{% if not referrer.isascii %} {% endif %} {% if "payment" in intentions %} {% endif %} {% if "live" in intentions %} {% endif %} {% if "persist" in intentions %} {% endif %} {% if "login" in intentions %} {% endif %}
{{referrer}}
{% trans 'Warning: this referrer may conceal his identity with unicode' %}
{% trans 'Warning: this referrer wants to initiate payments and/or create contracts' %}
{% trans 'This referrer applies the filter live instead of using a snapshot of ids' %}
{% trans 'This referrer wants to persist data' %}
{% trans 'Login into: ' %}{{referrer}}

{% trans 'Access to' %}:

{% for intention in intentions %} {% endfor %} {% if scope == "list" %} {% if uc.public %} {% endif %} {% for content in object_list %} {% endfor %} {% else %} {% endif %} {% for f in uc.features.all %} {% endfor %}
{% trans 'Scope' %} {% trans 'Name' %}
{% trans 'Global' %} {% if intention == "sl" %} serverless (sl) {% else %} {{intention}} {% endif %}
{% trans 'Component' %} {% trans 'Description' %}
{% trans 'Component' %} {% trans 'Name' %} ({{uc}})
{% trans 'Content' %} {{content}}
{% trans 'Content' %} {{object}}
{% trans 'Feature' %} {{f}}

{% trans 'Filter' %}

{% blocktrans trimmed %} Use ! to negate. Use !! to start a filter tag with !. Press Enter to confirm tag.
Use _ to start strict filter tag. Escape with __.
Use !_ to start negated strict filter tag. {% endblocktrans %}
{% endblock %} {% block outercontent %} {% endblock %} PK!  9spkcspider/apps/spider/templates/spider_base/uc_base.html{% extends "spider_base/base.html" %} {% load spider_rdf %} {% block public_meta %} {{block.super}} {% if uc.featured or uc.user.is_superuser or uc.user.is_staff %} {% else %} {% endif %} {% endblock %} {% block context_actions %} {{block.super}} {% endblock context_actions %} {% block main_classes %}{{block.super}} spkc-content w3-card-4 color-front-{{uc.strength}}{% endblock %} {% block body_class %}{{block.super}} color-back-{{uc.strength}}{% endblock %} {% block main_attributes %}{{block.super}} prefix="spkc: {% spkc_namespace %} xsd: http://www.w3.org/2001/XMLSchema# rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#" resource="{{hostpart}}{{ request.path }}"{% endblock %} PK!]UNspkcspider/apps/spider/templates/spider_base/usercomponent_confirm_delete.html{% extends "spider_base/uc_base.html" %} {% load i18n %} {% block title %}{% blocktrans trimmed with name=object.name %} Delete: {{name}} {% endblocktrans %}{% endblock %} {% block content %}
{# index is redirected in view #} {% trans 'Back' %}

{% blocktrans trimmed %} Delete User Component: {% endblocktrans %} {{object.name}}

{% blocktrans trimmed with name=object %} Warning this deletes the component and all depending content.
Go back if you don't want to this and you didn't hit delete.
You can abort the deletion process with reset if it is shown.
It is always shown except no deletion is in progress.
{% endblocktrans %}
{% for content in object.contents.all %} {% endfor %}
{% trans 'Deleted Content' %}
{{content}}
{% if not remaining or remaining == 0 %}
{% csrf_token %}
{% endif %} {% if remaining %}
{% csrf_token %}
{% endif %}
{% endblock %} PK!r**Dspkcspider/apps/spider/templates/spider_base/usercomponent_form.html{% extends "spider_base/base.html" %} {# inherits NOT from nouc_base #} {% load i18n static spider_rdf spider_base %} {% block title %}{% if object %}{% if object.is_index %}{% blocktrans trimmed %} Change Login Protection{% endblocktrans %}{% else %}{% blocktrans trimmed %} Change Component {% endblocktrans %}{% endif %}{% else %}{% blocktrans trimmed %} Create Component {% endblocktrans %}{% endif %}{% endblock %} {% block extrahead %} {{block.super}} {{form.media|safe}} {% endblock %} {# public_meta not required because it will never be used #} {% block main_classes %}{{block.super}} {% if not object %}w3-sand{% else %}w3-card-4 color-front-{{object.strength}}{% endif %} spkc-content{% endblock %} {% block body_class %}{{block.super}} {% if not object %}w3-light-grey{% else %}color-back-{{object.strength}}{% endif %}{% endblock %} {% block content %}
{% if object %} {% trans "Contents" %} {% endif %} {% trans 'Own' %} {% if object %} {% trans 'Link/Export' %} {% if not object.is_index %} {% endif %}
{% trans "Strength" %}: {{object.strength}}
{% endif %}

{% if object and form.instance.is_index %} {% trans 'Change Login Protection' %} {% elif object %} {% blocktrans trimmed %} Change Component {% endblocktrans %}: {{form.instance}} {% else %} {% trans 'Create new Component' %} {% endif %}

{% if object %} {% endif %} {% include "spider_base/partials/form_errors.html" with form=form %}
{% csrf_token %} {% if object %}

{% trans 'Add new content' %}

{% include "spider_base/partials/content_variants_box.html" with open=True uc=object %}
{% endif %}

{% if form.instance.id and form.instance.is_index %} {% trans 'Login Protection' %} {% else %} {% trans 'User Component' %} {% endif %}

{% if object %}
{% for token in object.authtokens.all %} {% token_expires object token as expire_date %} {% endfor %}

{% trans 'Active Tokens' %}

{% trans 'Token' %} {% trans 'Valid till / Referrer' %} {% trans 'Delete' %}
{{token}} {% if token.persist >= 0 %}
{{token.referrer}} {% else %} {{expire_date|date:"D, d/m/Y"}}{% if token.referrer %}
{{token.referrer}}{% endif %} {% endif %}
{# onchange would be also triggered by reset, so use click instead #}
{% endif %} {% include "spider_base/partials/base_form.html" with form=form %}
{% if form.instance.id %} {% endif %}

{% trans 'Protections' %}

{% for prot in form.protections %}

{{prot}}

{% include "spider_base/partials/base_form.html" with form=prot %}
{% empty%}
{% trans 'No protections: create component first' %}
{% endfor %}
{% if object %} {% trans "Contents" %} {% endif %}
{% endblock %} {% block outercontent %} {% if object %} {% url 'spider_base:ucontent-export' token=object.token as exportlink %} {% include "spider_base/partials/modalpresenter.html" %} {% endif %} {% endblock %} PK!9Dspkcspider/apps/spider/templates/spider_base/usercomponent_list.html{% extends "spider_base/nouc_base.html" %} {% load i18n static spider_rdf %} {% block title %}{% url 'spider_base:ucomponent-listpublic' as listpublic %}{% if listpublic == request.path %}{% blocktrans trimmed %} Public Components {% endblocktrans %}{% elif request.is_owner %}{% blocktrans trimmed %} Own Components {% endblocktrans %}{% else %}{% blocktrans trimmed with name=username %} Components of {{name}} {% endblocktrans %} {% endif %}{% endblock %} {% block public_meta %} {{block.super}} {% endblock %} {% block extrahead %} {{block.super}} {% if DEBUG %} {% else %} {% endif %} {% endblock %} {% block content %} {% url 'spider_base:ucomponent-listpublic' as listpublic %}
{% if listpublic != request.path %} {% trans 'Public' %} {% endif %} {% trans 'Home' %} {% if listpublic == request.path %} {% else %} {% endif %} {% if request.user.is_authenticated %} {% if request.is_owner %} {% trans 'Add New Component' %} {% endif %} {% if listpublic == request.path %} {% trans 'Own' %} {% else %} {% trans 'Export' %} {% endif %} {% endif %}

{% if listpublic == request.path %}{% blocktrans trimmed %} Public Components {% endblocktrans %}{% elif request.is_owner %}{% blocktrans trimmed %} Own Components {% endblocktrans %}{% else %}{% blocktrans trimmed %} Component List of {% endblocktrans %} {{username}} {% endif %}

{% include "spider_base/usercomponent_list_fragment.html" %}
{% include 'spider_base/partials/list_footer.html' %}
{% endblock %} PK!Y""Mspkcspider/apps/spider/templates/spider_base/usercomponent_list_fragment.html{% load i18n static spider_base spider_rdf %} {% if table_heading %} {% endif %} {% for uc in object_list %} {% empty %} {% endfor %}
{{table_heading}}
{% if uc.is_index and request.is_owner %} {% trans 'Private Component' %} {% else %} {{ uc.name }}{% if not request.user == uc.user %} ({{uc.user}}){% endif %} {% endif %} {% blocktrans trimmed with time=uc.modified|timesince %} Modified: {{time}} ago {% endblocktrans %}
{% if request.user == uc.user %}
{% endif %}
{% if not uc.description|is_not_or_space %} {{uc.description|escape|linebreaksbr}} {% else %} {% trans 'No description' %} {% endif %}
{% trans 'No Components available.' %}
PK!3L[[;spkcspider/apps/spider/templates/spider_base/view_form.html{% load spider_rdf %}
{% for rdftype in add_spkc_types %} {% endfor %}

{{legend|safe}}

{% include "spider_base/partials/base_view_form.html" with form=form %}
PK!/spkcspider/apps/spider/templatetags/__init__.pyPK!l]1spkcspider/apps/spider/templatetags/basic_math.pyfrom django import template register = template.Library() @register.filter(name='divide') def divide(value, arg): return value//int(arg) @register.filter(name='remainder') def remainder(value, arg): return value % int(arg) PK!t6< < 2spkcspider/apps/spider/templatetags/spider_base.pyfrom django import template from django.urls import reverse from django.utils import timezone from django.urls.exceptions import NoReverseMatch register = template.Library() @register.filter() def is_not_or_space(value): if not value: return True return isinstance(value, str) and value.isspace() @register.simple_tag(takes_context=True) def current_url(context): try: return reverse( "{}:{}".format( context["request"].resolver_match.namespace, context["request"].resolver_match.url_name, ), args=context["request"].resolver_match.args, kwargs=context["request"].resolver_match.kwargs ) except NoReverseMatch: return reverse( context["request"].resolver_match.url_name, args=context["request"].resolver_match.args, kwargs=context["request"].resolver_match.kwargs ) @register.simple_tag(takes_context=True) def list_own_content(context): ucname = "index" if context["request"].session.get("is_fake", False): ucname = "fake_index" if context["request"].user.is_authenticated: return context["request"].user.usercomponent_set.get( name=ucname ).get_absolute_url() return "" @register.simple_tag(takes_context=True) def update_component(context, name): ucname = name if ucname == "index" and context["request"].session.get("is_fake", False): ucname = "fake_index" if context["request"].user.is_authenticated: uc = context["request"].user.usercomponent_set.only("token").get( name=ucname ) return reverse("spider_base:ucomponent-update", kwargs={ "token": uc.token }) return "" @register.simple_tag(takes_context=True) def reverse_get(context, name, **kwargs): """ Works only if hostpart and spider_GET is available """ return "{}{}?{}".format( context["hostpart"], reverse(name, kwargs=kwargs), context["spider_GET"].urlencode() ) @register.simple_tag() def concat_string(*args): return "".join(args) @register.simple_tag() def expires_delta(expires): return expires-timezone.now() @register.simple_tag() def token_expires(usercomponent, token): return token.created + usercomponent.token_duration PK!̸w4spkcspider/apps/spider/templatetags/spider_paging.pyfrom django import template register = template.Library() @register.simple_tag(takes_context=True) def list_parameters(context, name, *args): ret = set(context["request"].GET.getlist(name)) ret.update(context["request"].POST.getlist(name)) for i in args: ret.update(i) return ret @register.simple_tag(takes_context=True) def get_searchpath(context, page=None, pagename="page"): if "searchpath" in context: path = context["searchpath"] else: path = context["request"].path san_get = "" if "spider_GET" in context: san_get = context["spider_GET"].urlencode() if page: if san_get: "{}&{}={}".format( san_get, pagename, page ) else: san_get = "{}={}".format(pagename, page) if path[-1] == "?": return "{}{}".format( path, san_get ) return "{}?{}".format( path, san_get ) PK!9spkcspider/apps/spider/templatetags/spider_protections.pyfrom django import template from django import forms from django.template.loader import render_to_string register = template.Library() @register.simple_tag(takes_context=True) def render_protection(context, protectiontup): result, protection = protectiontup protection = protection.installed_class ctx = {} ctx["parent_ctx"] = context ctx["data"] = result ctx["protection"] = protection if callable(getattr(protection, "render", None)): return protection.render(ctx) if isinstance(ctx["data"], forms.Form): ctx["form"] = ctx["data"] elif hasattr(protection, "auth_form"): ctx["form"] = protection.auth_form(**ctx["data"]) template_name = getattr(protection, "template_name") if not template_name: template_name = "spider_base/protections/protection_form.html" return render_to_string( template_name, context=ctx, request=context["request"] ) @register.simple_tag(takes_context=True) def extract_protections(context, extract_name="protections"): if hasattr(context.get("request", None), extract_name): if getattr(context["request"], extract_name) is not True: return getattr(context["request"], extract_name) return [] PK!d_Z1spkcspider/apps/spider/templatetags/spider_rdf.py__all__ = ["literalize", "uriref", "spkc_namespace"] from django import template from django.forms import BoundField, Field from rdflib import Literal from rdflib.namespace import XSD, RDF, URIRef from ..constants import spkcgraph from ..helpers import merge_get_url register = template.Library() @register.filter() def uriref(path): return URIRef(path) @register.filter() def is_uriref(value): return isinstance(value, URIRef) @register.simple_tag(takes_context=True) def action_view(context): token = getattr(context["request"], "auth_token", None) if token: token = token.token url2 = merge_get_url( context["hostpart"] + context["request"].path, token=token ) return Literal(url2, datatype=XSD.anyURI) @register.simple_tag() def literalize(ob, datatype=None, use_uriref=None): if isinstance(ob, BoundField): if not datatype: datatype = getattr(ob.field, "spkc_datatype", None) if use_uriref is None: use_uriref = getattr(ob.field, "spkc_use_uriref", None) ob = ob.value() elif isinstance(datatype, BoundField): if use_uriref is None: use_uriref = getattr(datatype.field, "spkc_use_uriref", None) datatype = getattr(datatype.field, "spkc_datatype", None) elif isinstance(datatype, Field): if use_uriref is None: use_uriref = getattr(datatype, "spkc_use_uriref", None) datatype = getattr(datatype, "spkc_datatype", None) if ob is None: return RDF.nil if hasattr(ob, "get_absolute_url"): if not datatype: datatype = spkcgraph["hashableURI"] if use_uriref is None: use_uriref = True ob = ob.get_absolute_url() elif isinstance(ob, str) and not datatype: datatype = XSD.string if use_uriref: return URIRef(ob) return Literal(ob, datatype=datatype) @register.simple_tag() def hashable_literalize(field): if isinstance(field, BoundField): return literalize(getattr(field.field, "hashable", False)) else: return literalize(getattr(field, "hashable", False)) @register.simple_tag() def spkc_namespace(sub=None): if sub: return spkcgraph[sub] return spkcgraph PK!x  spkcspider/apps/spider/urls.pyfrom django.urls import path from django.contrib.auth.decorators import login_required from .views import ( ComponentIndex, ComponentPublicIndex, ComponentCreate, ComponentUpdate, ComponentDelete ) from .views import ( ContentAdd, ContentIndex, ContentAccess, ContentDelete ) from .views import ( TokenDelete, TokenDeletionRequest, TokenRenewal ) app_name = "spider_base" # token: path: token can simulate path structures (legacy) # components plural: most components url retrieve multiple items # one "component"-url for single retrievals is confusing urlpatterns = [ path( 'components/export/', login_required(ComponentIndex.as_view(scope="export")), name='ucomponent-export' ), path( 'components/slug:user>/export/', login_required(ComponentIndex.as_view(scope="export")), name='ucomponent-export' ), path( 'components/list/', login_required(ComponentIndex.as_view()), name='ucomponent-list' ), path( 'components//list/', login_required(ComponentIndex.as_view()), name='ucomponent-list' ), # path( # 'ucs/create//', # ComponentCreate.as_view(), # name='ucomponent-add' # ), path( 'components/add/', login_required(ComponentCreate.as_view()), name='ucomponent-add' ), path( 'components//update/', login_required(ComponentUpdate.as_view()), name='ucomponent-update' ), path( 'components//delete/', login_required(ComponentDelete.as_view()), name='ucomponent-delete' ), path( 'components//list/', ContentIndex.as_view(), name='ucontent-list' ), path( 'components//export/', ContentIndex.as_view(scope="export"), name='ucontent-export' ), path( 'components//add//', ContentAdd.as_view(), name='ucontent-add' ), path( 'content//delete/', ContentDelete.as_view(), name='ucontent-delete' ), path( 'content///', ContentAccess.as_view(), name='ucontent-access' ), path( 'token//delete/', login_required(TokenDelete.as_view()), name='token-delete' ), path( 'token/delete-request/', TokenDeletionRequest.as_view(), name='token-delete-request' ), path( 'token/renew/', TokenRenewal.as_view(), name='token-renew' ), path( 'components/', ComponentPublicIndex.as_view( is_home=False ), name='ucomponent-listpublic' ), ] PK!*_$spkcspider/apps/spider/validators.py__all__ = ("content_name_validator",) from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ def content_name_validator(value): if not value.isprintable(): raise ValidationError( _("Contains control characters"), code="control_characters" ) if value: if value[0].isspace() or value[1].isspace(): raise ValidationError( _("Contains hidden spaces"), code="hidden_spaces" ) PK!Z߰(spkcspider/apps/spider/views/__init__.pyfrom ._core import * # noqa: F403, F401 from ._components import * # noqa: F403, F401 from ._contents import * # noqa: F403, F401 from ._tokens import * # noqa: F403, F401 PK!U rmEmE+spkcspider/apps/spider/views/_components.py __all__ = ( "ComponentIndex", "ComponentPublicIndex", "ComponentCreate", "ComponentUpdate", "ComponentDelete" ) from django.views.generic.list import ListView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.shortcuts import get_object_or_404, redirect from django.db import models from django.http import HttpResponse from django.conf import settings from django.urls import reverse from django.contrib import messages from django.utils.translation import gettext from rdflib import Graph, Literal, URIRef, XSD from ._core import UserTestMixin, UCTestMixin, EntityDeletionMixin from ..constants import spkcgraph, VariantType from ..forms import UserComponentForm from ..queryfilters import filter_contents, filter_components from ..models import ( UserComponent, TravelProtection, AssignedContent ) from ..helpers import merge_get_url from ..serializing import paginate_stream, serialize_stream class ComponentIndexBase(ListView): scope = "list" def get_context_data(self, **kwargs): kwargs["scope"] = self.scope return super().get_context_data(**kwargs) def get_queryset_components(self, use_contents=True): order = None if "search" in self.request.POST: searchlist = self.request.POST.getlist("search") else: searchlist = self.request.GET.getlist("search") filter_unlisted = not ( self.request.is_special_user and "_unlisted" in searchlist ) and self.scope != "export" filter_q, counter = filter_components( searchlist, filter_unlisted, use_contents ) if self.request.GET.get("protection", "") == "false": filter_q &= models.Q(required_passes=0) if self.scope != "export" and "raw" not in self.request.GET: order = self.get_ordering(counter > 0) ret = self.model.objects.prefetch_related( "contents" ).filter(filter_q).distinct() if order: ret = ret.order_by(*order) return ret def get_queryset_contents(self): if "search" in self.request.POST: searchlist = self.request.POST.getlist("search") else: searchlist = self.request.GET.getlist("search") filter_unlisted = not ( self.request.is_special_user and "_unlisted" in searchlist ) and self.scope != "export" filter_q, counter = filter_contents( searchlist, filter_unlisted ) if self.request.GET.get("protection", "") == "false": filter_q &= models.Q(required_passes=0) return AssignedContent.objects.select_related( "usercomponent" ).filter(filter_q, strengt__lte=self.source_strength) def get_queryset(self): if self.request.GET.get("raw") == "embed": return self.get_queryset_components(False).filter( models.Q(contents__isnull=True) | models.Q(strength__gt=self.source_strength) ) else: return self.get_queryset_components() def get_paginate_by(self, queryset): if self.scope == "export" or "raw" in self.request.GET: # are later paginated and ordered return None return settings.SPIDER_OBJECTS_PER_PAGE def render_to_response(self, context): if self.scope != "export" and "raw" not in self.request.GET: return super().render_to_response(context) embed = ( self.scope == "export" or self.request.GET.get("raw", "") == "embed" ) session_dict = { "request": self.request, "context": context, "scope": self.scope, "expires": None, "hostpart": context["hostpart"], "uc_namespace": spkcgraph["components"], "sourceref": URIRef(context["hostpart"] + self.request.path) } g = Graph() g.namespace_manager.bind("spkc", spkcgraph, replace=True) if embed: # embed empty components per_page = getattr( settings, "SPIDER_SERIALIZED_PER_PAGE", settings.SPIDER_OBJECTS_PER_PAGE ) // 2 p = [ paginate_stream( self.get_queryset_contents(), per_page, settings.SPIDER_MAX_EMBED_DEPTH ), paginate_stream( context["object_list"], # empty components per_page, settings.SPIDER_MAX_EMBED_DEPTH ) ] else: p = [paginate_stream( context["object_list"], getattr( settings, "SPIDER_SERIALIZED_PER_PAGE", settings.SPIDER_OBJECTS_PER_PAGE ), settings.SPIDER_MAX_EMBED_DEPTH )] page = 1 try: page = int(self.request.GET.get("page", "1")) except Exception: pass if page <= 1: g.add(( session_dict["sourceref"], spkcgraph["scope"], Literal(context["scope"], datatype=XSD.string) )) g.add(( session_dict["sourceref"], spkcgraph["strength"], Literal(self.source_strength) )) token = getattr(session_dict["request"], "auth_token", None) if token: token = token.token url2 = merge_get_url(str(session_dict["sourceref"]), token=token) g.add( ( session_dict["sourceref"], spkcgraph["action:view"], Literal(url2, datatype=XSD.anyURI) ) ) serialize_stream( g, p, session_dict, page=page, embed=embed, restrict_embed=(self.source_strength == 10), restrict_inclusion=(self.source_strength == 10) ) ret = HttpResponse( g.serialize(format="turtle"), content_type="text/turtle;charset=utf-8" ) ret["Access-Control-Allow-Origin"] = "*" return ret class ComponentPublicIndex(ComponentIndexBase): model = UserComponent is_home = False source_strength = 0 preserved_GET_parameters = set(["protection"]) def dispatch(self, request, *args, **kwargs): self.request.is_owner = False self.request.is_special_user = False self.request.is_staff = False self.request.auth_token = None return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs["is_public_view"] = True kwargs["hostpart"] = "{}://{}".format( self.request.scheme, self.request.get_host() ) GET = self.request.GET.copy() # parameters preserved in search for key in list(GET.keys()): if key not in self.preserved_GET_parameters: GET.pop(key, None) kwargs["spider_GET"] = GET return super().get_context_data(**kwargs) def get_queryset_components(self): query = super().get_queryset_components() q = models.Q(public=True) if self.is_home: q &= models.Q(featured=True) return query.filter(q) def get_queryset_contents(self): query = super().get_queryset_contents() q = models.Q(usercomponent__strength=0) if self.is_home: q &= models.Q(usercomponent__featured=True) return query.filter(q) def get_ordering(self, issearching=False): if not issearching: return ("-strength", "-modified",) else: return ("-strength", "name", "user__username") class ComponentIndex(UCTestMixin, ComponentIndexBase): model = UserComponent source_strength = 10 user = None def dispatch(self, request, *args, **kwargs): self.user = self.get_user() return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def get_ordering(self, issearching=False): if self.scope == "export": return None if issearching: # MUST use strength here, elsewise travel mode can be exposed return ("-strength", "name",) return ("-modified",) def get_context_data(self, **kwargs): kwargs["collection_user"] = self.user kwargs["username"] = getattr(self.user, self.user.USERNAME_FIELD) kwargs["scope"] = self.scope kwargs["is_public_view"] = False return super().get_context_data(**kwargs) def test_func(self): staffperm = {"spider_base.view_usercomponent"} if self.scope == "export": staffperm.add("spider_base.view_assignedcontent") if self.has_special_access( user_by_login=True, user_by_token=False, staff=staffperm, superuser=True ): return True return False def get_queryset_components(self): query = super().get_queryset_components() q = models.Q() # doesn't matter if it is same user, lazy travel = TravelProtection.objects.get_active() if self.request.session.get("is_fake", False): # remove access to index q &= ~models.Q(name="index") q &= ~models.Q( travel_protected__in=travel ) else: # remove all travel protected components if not admin or is_fake if not self.request.is_staff: q &= ~models.Q( travel_protected__in=travel ) # and remove access to fake index q &= ~models.Q(name="fake_index") return query.filter( q, user=self.user ) def get_queryset_contents(self): query = super().get_queryset_contents() q = models.Q() # doesn't matter if it is same user, lazy travel = TravelProtection.objects.get_active() if self.request.session.get("is_fake", False): # remove access to index q &= ~models.Q(name="usercomponent__index") # q &= ~models.Q( # travel_protected__in=travel # ) q &= ~models.Q( usercomponent__travel_protected__in=travel ) else: # remove all travel protected components if not admin or is_fake if not self.request.is_staff: # q &= ~models.Q( # travel_protected__in=travel # ) q &= ~models.Q( usercomponent__travel_protected__in=travel ) # and remove access to fake index q &= ~models.Q(name="fake_index") return query.filter( q, user=self.user ) def get_usercomponent(self): ucname = "index" if self.request.session.get("is_fake", False): ucname = "fake_index" return get_object_or_404( UserComponent, user=self.user, name=ucname ) class ComponentCreate(UserTestMixin, CreateView): model = UserComponent form_class = UserComponentForm def form_valid(self, form): _ = gettext messages.success( self.request, _('Component created.') ) return super().form_valid(form) def get_success_url(self): return reverse( "spider_base:ucomponent-update", kwargs={ "token": self.object.token } ) def get_usercomponent(self): ucname = "index" if self.request.session.get("is_fake", False): ucname = "fake_index" return get_object_or_404( UserComponent, user=self.get_user(), name=ucname ) def get_form_kwargs(self): ret = super().get_form_kwargs() ret["instance"] = self.model(user=self.get_user()) ret['request'] = self.request return ret class ComponentUpdate(UserTestMixin, UpdateView): model = UserComponent form_class = UserComponentForm def dispatch(self, request, *args, **kwargs): self.object = self.get_object() self.usercomponent = self.object return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return self.render_to_response(self.get_context_data()) def post(self, request, *args, **kwargs): form = self.get_form() if form.is_valid(): return self.form_valid(form) else: return self.form_invalid(form) def test_func(self): if self.has_special_access( user_by_login=True ): # for create_admin_token self.request.auth_token = self.create_admin_token() return True return False def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["content_variants"] = \ self.usercomponent.user_info.allowed_content.exclude( ctype__contains=VariantType.component_feature.value ).exclude( models.Q( ctype__contains=VariantType.component_feature.value ) | models.Q( ctype__contains=VariantType.content_feature.value ) | models.Q(ctype__contains=VariantType.unlisted.value) ) context["content_variants_used"] = \ self.usercomponent.user_info.allowed_content.filter( assignedcontent__usercomponent=self.usercomponent ).exclude( models.Q( ctype__contains=VariantType.component_feature.value ) | models.Q( ctype__contains=VariantType.content_feature.value ) | models.Q(ctype__contains=VariantType.unlisted.value) ) context["remotelink"] = context["spider_GET"].copy() context["remotelink"] = "{}{}?{}".format( context["hostpart"], reverse("spider_base:ucontent-list", kwargs={ "token": self.usercomponent.token }), context["remotelink"].urlencode() ) # this is always available context["auth_token"] = self.request.auth_token.token return context def get_object(self, queryset=None): if not queryset: queryset = self.get_queryset() return get_object_or_404( queryset.prefetch_related( "protections", ), token=self.kwargs["token"] ) def get_form_kwargs(self): ret = super().get_form_kwargs() ret['request'] = self.request return ret def get_form_success_kwargs(self): """Return the keyword arguments for instantiating the form.""" return { 'initial': self.get_initial(), 'prefix': self.get_prefix(), 'instance': self.object, 'request': self.request } def form_valid(self, form): _ = gettext self.object = form.save() persist = 0 if self.object.primary_anchor: persist = self.object.primary_anchor.id self.object.authtokens.filter(persist__gte=0).update( persist=persist ) if self.kwargs["token"] != self.object.token: return redirect( "spider_base:ucomponent-update", token=self.object.token ) messages.success(self.request, _('Component updated.')) return self.render_to_response( self.get_context_data( form=self.get_form_class()(**self.get_form_success_kwargs()) ) ) class ComponentDelete(EntityDeletionMixin, DeleteView): model = UserComponent fields = [] object = None def form_valid(self, form): _ = gettext messages.error( self.request, _('Component deleted.') ) return super().form_valid(form) def dispatch(self, request, *args, **kwargs): self.object = self.get_object() self.usercomponent = self.object self.user = self.get_user() return super().dispatch(request, *args, **kwargs) def get_success_url(self): username = getattr(self.user, self.user.USERNAME_FIELD) return reverse( "spider_base:ucomponent-list", kwargs={ "user": username } ) def get_context_data(self, **kwargs): kwargs["uc"] = self.usercomponent return super().get_context_data(**kwargs) def delete(self, request, *args, **kwargs): if self.object.is_index: return self.handle_no_permission() return super().delete(request, *args, **kwargs) def get(self, request, *args, **kwargs): if self.object.is_index: return self.handle_no_permission() return super().get(request, *args, **kwargs) def get_usercomponent(self): return self.object def get_object(self, queryset=None): if not queryset: queryset = self.get_queryset() return get_object_or_404( queryset, token=self.kwargs["token"] ) PK!-'[\[\)spkcspider/apps/spider/views/_contents.py""" Content Views """ __all__ = ( "ContentIndex", "ContentAdd", "ContentAccess", "ContentDelete" ) from datetime import timedelta from django.views.generic.edit import DeleteView, UpdateView, CreateView from django.views.generic.list import ListView from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.http.response import HttpResponseBase, HttpResponse from django.db import models from django.conf import settings from django.core.exceptions import PermissionDenied from django.http import Http404 from django.contrib import messages from django.utils.translation import gettext from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from next_prev import next_in_order, prev_in_order from rdflib import Graph, Literal, URIRef, XSD from ._core import UCTestMixin, EntityDeletionMixin, ReferrerMixin from ..models import ( AssignedContent, ContentVariant, UserComponent ) from ..forms import UserContentForm from ..helpers import get_settings_func, add_property, merge_get_url from ..queryfilters import filter_contents from ..constants import spkcgraph, VariantType from ..serializing import paginate_stream, serialize_stream _forbidden_scopes = frozenset(["add", "list", "raw", "delete", "anchor"]) class ContentBase(UCTestMixin): model = AssignedContent scope = None object = None # use token of content object instead no_token_usercomponent = True def dispatch(self, request, *args, **kwargs): try: return super().dispatch(request, *args, **kwargs) except Http404: return get_settings_func( "RATELIMIT_FUNC", "spkcspider.apps.spider.functions.rate_limit_default" )(self, request) def get_template_names(self): if self.scope in ("add", "update"): return ['spider_base/assignedcontent_form.html'] elif self.scope == "list": return ['spider_base/assignedcontent_list.html'] else: return ['spider_base/assignedcontent_access.html'] def get_ordering(self, issearching=False): if self.scope != "list": # export: also serializer, other scopes: only one object, overhead return None # ordering will happen in serializer if "raw" in self.request.GET: return None return ("-priority", "-modified") def get_context_data(self, **kwargs): kwargs["request"] = self.request kwargs["scope"] = self.scope kwargs["uc"] = self.usercomponent kwargs["enctype"] = "multipart/form-data" return super().get_context_data(**kwargs) def get_queryset(self): ret = self.model.objects.all() # skip search if user and single object if self.scope in ("add", "update", "raw_update"): return ret if getattr(self.request, "auth_token", None): idlist = \ self.request.auth_token.extra.get("ids", []) searchlist = \ self.request.auth_token.extra.get("filter", []) else: idlist = [] searchlist = [] if self.scope == "list": if "search" in self.request.POST or "id" in self.request.POST: searchlist += self.request.POST.getlist("search") idlist += self.request.POST.getlist("id") else: searchlist += self.request.GET.getlist("search") idlist += self.request.GET.getlist("id") elif self.scope not in ("add", "update", "raw_update"): searchlist += self.request.GET.getlist("search") # list only unlisted if explicity requested or export or: # if it has high priority (only for special users) # listing prioritized, unlisted content is different to the broader # search filter_unlisted = False if self.request.is_special_user: # all other scopes than list can show here _unlisted # this includes export if self.scope == "list" and "_unlisted" not in searchlist: filter_unlisted = 0 else: filter_unlisted = True filter_q, counter = filter_contents( searchlist, idlist, filter_unlisted ) order = self.get_ordering(counter > 0) # distinct required? ret = ret.filter(filter_q) if order: ret = ret.order_by(*order) return ret class ContentIndex(ReferrerMixin, ContentBase, ListView): model = AssignedContent scope = "list" no_token_usercomponent = False def dispatch_extra(self, request, *args, **kwargs): self.allow_domain_mode = self.usercomponent.allow_domain_mode if "referrer" in self.request.GET: self.object_list = self.get_queryset() return self.handle_referrer() return None def get_queryset(self): return super().get_queryset().filter( usercomponent=self.usercomponent ) def get_usercomponent(self): query = {"token": self.kwargs["token"]} return get_object_or_404( UserComponent.objects.select_related( "user", "user__spider_info", ).prefetch_related("protections"), **query ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.is_owner: # request.user is maybe anonymous context["content_variants"] = \ self.usercomponent.user.spider_info.allowed_content.exclude( models.Q( ctype__contains=VariantType.component_feature.value ) | models.Q( ctype__contains=VariantType.content_feature.value ) | models.Q(ctype__contains=VariantType.unlisted.value) ) context["content_variants_used"] = \ self.usercomponent.user.spider_info.allowed_content.filter( assignedcontent__usercomponent=self.usercomponent ).exclude( models.Q( ctype__contains=VariantType.component_feature.value ) | models.Q( ctype__contains=VariantType.content_feature.value ) | models.Q(ctype__contains=VariantType.unlisted.value) ) context["active_features"] = self.usercomponent.features.all() context["is_public_view"] = self.usercomponent.public context["has_unlisted"] = self.usercomponent.contents.filter( info__contains="\x1eunlisted\x1e" ).exists() context["remotelink"] = context["spider_GET"].copy() context["auth_token"] = None if self.request.auth_token: context["auth_token"] = self.request.auth_token.token context["remotelink"] = "{}{}?{}".format( context["hostpart"], reverse("spider_base:ucontent-list", kwargs={ "token": self.usercomponent.token }), context["remotelink"].urlencode() ) return context def test_func(self): staff_perm = not self.usercomponent.is_index if staff_perm: staff_perm = "spider_base.view_usercomponent" # user token is tested later if self.has_special_access( user_by_login=True, user_by_token=False, staff=staff_perm, superuser=True ): self.request.auth_token = self.create_admin_token() return True # block view on special objects for non user and non superusers if self.usercomponent.is_index: return False # export is only available for user and staff with permission minstrength = 0 if self.scope in ["export"]: minstrength = 4 return self.test_token(minstrength) def get_paginate_by(self, queryset): if self.scope == "export" or "raw" in self.request.GET: return None return settings.SPIDER_OBJECTS_PER_PAGE def render_to_response(self, context): if context["scope"] != "export" and "raw" not in self.request.GET: return super().render_to_response(context) session_dict = { "request": self.request, "context": context, "scope": context["scope"], "uc": self.usercomponent, "hostpart": context["hostpart"], "sourceref": URIRef(context["hostpart"] + self.request.path) } g = Graph() g.namespace_manager.bind("spkc", spkcgraph, replace=True) embed = False if ( context["scope"] == "export" or self.request.GET.get("raw", "") == "embed" ): embed = True if context["object_list"]: p = paginate_stream( context["object_list"], getattr( settings, "SPIDER_SERIALIZED_PER_PAGE", settings.SPIDER_OBJECTS_PER_PAGE ), settings.SPIDER_MAX_EMBED_DEPTH ) else: # no content, pagination works here only this way p = paginate_stream( UserComponent.objects.filter(pk=self.usercomponent.pk), 1, 1 ) page = 1 try: page = int(self.request.GET.get("page", "1")) except Exception: pass if page <= 1: if hasattr(self.request, "token_expires"): session_dict["expires"] = self.request.token_expires.strftime( "%a, %d %b %Y %H:%M:%S %z" ) add_property( g, "token_expires", ob=session_dict["request"], ref=session_dict["sourceref"] ) g.add(( session_dict["sourceref"], spkcgraph["scope"], Literal(context["scope"], datatype=XSD.string) )) g.add(( session_dict["sourceref"], spkcgraph["strength"], Literal(self.usercomponent.strength, datatype=XSD.integer) )) if context["referrer"]: g.add(( session_dict["sourceref"], spkcgraph["referrer"], Literal(context["referrer"], datatype=XSD.anyURI) )) token = getattr(session_dict["request"], "auth_token", None) if token: token = token.token g.add( ( session_dict["sourceref"], spkcgraph["action:view"], Literal( merge_get_url( str(session_dict["sourceref"]), token=token ), datatype=XSD.anyURI ) ) ) if context["token_strength"]: add_property( g, "token_strength", ref=session_dict["sourceref"], literal=context["token_strength"], datatype=XSD.integer ) add_property( g, "intentions", ref=session_dict["sourceref"], literal=context["intentions"], datatype=XSD.string, iterate=True ) serialize_stream( g, p, session_dict, page=page, embed=embed ) ret = HttpResponse( g.serialize(format="turtle"), content_type="text/turtle;charset=utf-8" ) if session_dict.get("expires", None): ret['X-Token-Expires'] = session_dict["expires"] # allow cors requests for raw ret["Access-Control-Allow-Origin"] = "*" return ret class ContentAdd(ContentBase, CreateView): scope = "add" model = ContentVariant def get(self, request, *args, **kwargs): self.object = self.get_object() return self.render_to_response(self.get_context_data()) def post(self, request, *args, **kwargs): self.object = self.get_object() return self.render_to_response(self.get_context_data()) def form_valid(self, form): _ = gettext messages.success( self.request, _('Content created.') ) return super().form_valid(form) def get_queryset(self): # use requesting user as base if he can add this type of content if self.request.user.is_authenticated: return self.request.user.spider_info.allowed_content.exclude( models.Q(ctype__contains=VariantType.component_feature.value) | models.Q(ctype__contains=VariantType.unlisted.value) ) else: return self.usercomponent.user.spider_info.allowed_content.exclude( models.Q(ctype__contains=VariantType.component_feature.value) | models.Q(ctype__contains=VariantType.unlisted.value) ) def test_func(self): # test if user and check if user is allowed to create content if self.has_special_access( user_by_login=True, user_by_token=True, superuser=False ): return True return False def get_context_data(self, **kwargs): kwargs["content_type"] = self.object.installed_class kwargs["form"] = self.get_form() kwargs["active_features"] = self.usercomponent.features.all() return super().get_context_data(**kwargs) def get_form(self, allow_data=True): assigned = self.object.installed_class.static_create( associated_kwargs={ "usercomponent": self.usercomponent, "ctype": self.object } ).associated form_kwargs = { "instance": assigned, "request": self.request, "initial": { "usercomponent": self.usercomponent } } if allow_data and self.request.method in ('POST', 'PUT'): form_kwargs.update({ 'data': self.request.POST, # 'files': self.request.FILES, }) return UserContentForm(**form_kwargs) def get_usercomponent(self): return get_object_or_404( UserComponent.objects.prefetch_related("protections"), token=self.kwargs["token"] ) def get_object(self, queryset=None): if not queryset: queryset = self.get_queryset() qquery = models.Q( name=self.kwargs["type"], strength__lte=self.usercomponent.strength ) return get_object_or_404(queryset, qquery) def render_to_response(self, context): # only true if data if context["form"].is_valid(): ucontent = context["form"].save(commit=False) else: ucontent = context["form"].instance rendered = ucontent.content.access(context) # return response if content returned response if isinstance(rendered, HttpResponseBase): return rendered # show framed output assert(isinstance(rendered, (tuple, list))) context["content"] = rendered # redirect if saving worked if getattr(ucontent, "id", None): assert(ucontent.token) assert(ucontent.usercomponent) return redirect( 'spider_base:ucontent-access', token=ucontent.token, access="update" ) else: assert(not getattr(ucontent.content, "id", None)) return super().render_to_response(context) class ContentAccess(ReferrerMixin, ContentBase, UpdateView): scope = "access" form_class = UserContentForm model = AssignedContent def dispatch_extra(self, request, *args, **kwargs): self.object = self.get_object() self.allow_domain_mode = ( self.usercomponent.allow_domain_mode or self.object.allow_domain_mode ) # done in get_queryset # if getattr(self.request, "auth_token", None): # ids = self.request.auth_token.extra.get("ids", None) # if ids is not None and self.object.id not in ids: # return self.handle_no_permission() if "referrer" in self.request.GET: self.object_list = self.model.objects.filter( pk=self.object.pk ) return self.handle_referrer() return None @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): _scope = kwargs["access"] # special scopes which should be not available as url parameter # raw is also deceptive because view and raw=? = raw scope if _scope in _forbidden_scopes: raise PermissionDenied("Deceptive scopes") self.scope = _scope return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): context = {"form": None} if self.scope == "update": context["form"] = self.get_form() return self.render_to_response(self.get_context_data(**context)) def post(self, request, *args, **kwargs): context = {"form": None} # other than update have no form if self.scope == "update": context["form"] = self.get_form() if context["form"].is_valid(): self.object = context["form"].save(commit=True) # use correct usercomponent self.usercomponent = context["form"].instance.usercomponent return self.render_to_response(self.get_context_data(**context)) def get_context_data(self, **kwargs): kwargs["is_public_view"] = ( self.usercomponent.public and self.scope not in ("add", "update", "raw_update") ) context = super().get_context_data(**kwargs) context["remotelink"] = context["spider_GET"].copy() context["auth_token"] = None if self.request.auth_token: context["auth_token"] = self.request.auth_token.token context["remotelink"] = "{}{}?{}".format( context["hostpart"], reverse("spider_base:ucontent-access", kwargs={ "token": self.object.token, "access": "view" }), context["remotelink"].urlencode() ) if self.scope == "update": context["active_features"] = self.usercomponent.features.all() else: context["active_features"] = ContentVariant.objects.filter( models.Q(feature_for_contents=self.object) | models.Q(feature_for_components=self.usercomponent) ) return context def get_form_success_kwargs(self): """Return the keyword arguments for instantiating the form.""" return { 'initial': self.get_initial(), 'instance': self.object, 'request': self.request, 'prefix': self.get_prefix() } def get_form_kwargs(self): """Return the keyword arguments for instantiating the form.""" ret = super().get_form_kwargs() ret["request"] = self.request return ret def test_func(self): # give user and staff the ability to update Content # except it is index, in this case only the user can update # reason: admins could be tricked into malicious updates # for index the same reason as for add uncritically = self.usercomponent.name != "index" staff_perm = uncritically if staff_perm: staff_perm = "spider_base.view_assignedcontent" if self.scope in {"update", "raw_update"}: staff_perm = "spider_base.update_assignedcontent" # user token is tested later if self.has_special_access( staff=staff_perm, superuser=uncritically, user_by_token=False, user_by_login=True ): self.request.auth_token = self.create_admin_token() return True minstrength = 0 if self.scope in {"update", "raw_update", "export"}: minstrength = 4 return self.test_token(minstrength) def get_usercomponent(self): return get_object_or_404( UserComponent.objects.prefetch_related("protections"), contents__token=self.kwargs["token"] ) def get_object(self, queryset=None): # can bypass idlist and searchlist with own queryset arg if not queryset: queryset = self.get_queryset() # required for next/previous token queryset = queryset.select_related( "usercomponent", "usercomponent__user", "usercomponent__user__spider_info" ).filter(usercomponent=self.usercomponent).order_by("priority", "id") ob = get_object_or_404( queryset, token=self.kwargs["token"] ) ob.previous_object = prev_in_order(ob, queryset) ob.next_object = next_in_order(ob, queryset) return ob def render_to_response(self, context): # context is updated and used outside!! rendered = self.object.content.access(context) if self.scope == "update": # token changed => path has changed if self.object.token != self.kwargs["token"]: return redirect( 'spider_base:ucontent-access', token=self.object.token, access="update" ) if context["form"].is_valid(): context["form"] = self.get_form_class()( **self.get_form_success_kwargs() ) if isinstance(rendered, HttpResponseBase): return rendered context["content"] = rendered return super().render_to_response(context) class ContentDelete(EntityDeletionMixin, DeleteView): model = AssignedContent usercomponent = None def form_valid(self, form): _ = gettext messages.error( self.request, _('Content deleted.') ) return super().form_valid(form) def dispatch(self, request, *args, **kwargs): self.object = self.get_object() self.usercomponent = self.get_usercomponent() return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs["uc"] = self.usercomponent return super().get_context_data(**kwargs) def get_success_url(self): return reverse( "spider_base:ucontent-list", kwargs={ "token": self.usercomponent.token, } ) def get_required_timedelta(self): _time = self.object.content.deletion_period if _time: _time = timedelta(seconds=_time) else: _time = timedelta(seconds=0) return _time def get_usercomponent(self): return self.object.usercomponent def get_object(self, queryset=None): if not queryset: queryset = self.get_queryset() return get_object_or_404( queryset, token=self.kwargs["token"] ) PK!x& ~~%spkcspider/apps/spider/views/_core.py__all__ = ( "UserTestMixin", "UCTestMixin", "EntityDeletionMixin", "ReferrerMixin" ) import hashlib import logging from urllib.parse import quote_plus from datetime import timedelta from django.contrib.auth.mixins import AccessMixin from django.shortcuts import get_object_or_404 from django.contrib.auth import get_user_model, REDIRECT_FIELD_NAME from django.http import ( HttpResponseRedirect, HttpResponseServerError, HttpResponse ) from django.http.response import HttpResponseBase from django.db import transaction from django.db.models import Q from django.utils import timezone from django.conf import settings from django.urls import reverse_lazy from django.utils.translation import gettext try: from ratelimit.core import is_ratelimited, get_usage, user_or_ip except ImportError: from ratelimit.utils import ( is_ratelimited, user_or_ip, get_usage_count as get_usage ) import requests import certifi from ..helpers import merge_get_url, get_settings_func, get_hashob from ..constants import VariantType, TokenCreationError, ProtectionType from ..conf import VALID_INTENTIONS, VALID_SUB_INTENTIONS from ..models import UserComponent, AuthToken, ReferrerObject class UserTestMixin(AccessMixin): preserved_GET_parameters = set(["token", "protection"]) login_url = reverse_lazy(getattr( settings, "LOGIN_URL", "auth:login" )) def dispatch_extra(self, request, *args, **kwargs): return None def dispatch(self, request, *args, **kwargs): _ = gettext self.request.is_owner = getattr(self.request, "is_owner", False) self.request.is_special_user = \ getattr(self.request, "is_special_user", False) self.request.is_staff = getattr(self.request, "is_staff", False) self.request.auth_token = getattr(self.request, "auth_token", None) try: user_test_result = self.test_func() except TokenCreationError: logging.exception("Token creation failed") return HttpResponseServerError( _("Token creation failed, try again") ) if isinstance(user_test_result, HttpResponseBase): return user_test_result elif not user_test_result: return self.handle_no_permission() ret = self.dispatch_extra(request, *args, **kwargs) if ret: return ret return super().dispatch(request, *args, **kwargs) def sanitize_GET(self): GET = self.request.GET.copy() for key in list(GET.keys()): if key not in self.preserved_GET_parameters: GET.pop(key, None) return GET def get_context_data(self, **kwargs): kwargs["raw_update_type"] = VariantType.raw_update.value kwargs["feature_type"] = VariantType.component_feature.value kwargs["hostpart"] = "{}://{}".format( self.request.scheme, self.request.get_host() ) kwargs["spider_GET"] = self.sanitize_GET() return super().get_context_data(**kwargs) # by default only owner with login can access view def test_func(self): if self.has_special_access( user_by_login=True ): return True return False def replace_token(self): GET = self.request.GET.copy() GET["token"] = self.request.auth_token.token return HttpResponseRedirect( redirect_to="?".join((self.request.path, GET.urlencode())) ) def create_token(self, special_user=None, extra=None): d = { "usercomponent": self.usercomponent, "session_key": None, "created_by_special_user": special_user, "extra": {} } if "token" not in self.request.GET: d["session_key"] = self.request.session.session_key token = AuthToken(**d) if extra: token.extra.update(extra) token.save() return token def create_admin_token(self): expire = timezone.now()-self.usercomponent.token_duration # delete old token, so no confusion happen self.usercomponent.authtokens.filter( created__lt=expire ).delete() # delete tokens from old sessions self.usercomponent.authtokens.exclude( session_key=self.request.session.session_key, ).filter(created_by_special_user=self.request.user).delete() # use session_key, causes deletion on logout token = self.usercomponent.authtokens.filter( session_key=self.request.session.session_key ).first() if token: return token return self.create_token( self.request.user, extra={ "strength": 10 } ) def remove_old_tokens(self, expire=None): if not expire: expire = timezone.now()-self.usercomponent.token_duration return self.usercomponent.authtokens.filter( created__lt=expire, persist=-1 ).delete() def test_token(self, minstrength=0, force_token=False): expire = timezone.now()-self.usercomponent.token_duration no_token = not force_token and self.usercomponent.required_passes == 0 ptype = ProtectionType.access_control.value if minstrength >= 4: no_token = False ptype = ProtectionType.authentication.value # delete old token, so no confusion happen self.remove_old_tokens(expire) # only valid tokens here tokenstring = self.request.GET.get("token", None) token = None if tokenstring: # find by tokenstring token = self.usercomponent.authtokens.filter( token=tokenstring ).first() elif self.request.session.session_key: # use session_key token = self.usercomponent.authtokens.filter( session_key=self.request.session.session_key ).first() elif not no_token: # generate session key if it not exist and token is required self.request.session.cycle_key() if token and token.extra.get("prot_strength", 0) >= minstrength: self.request.token_expires = \ token.created+self.usercomponent.token_duration # case will never enter # if not token.session_key and "token" not in self.request.GET: # return self.replace_token() if token.extra.get("prot_strength", 0) >= 4: self.request.is_special_user = True self.request.is_owner = True self.request.auth_token = token return True # if result is impossible and token invalid try to login if minstrength >= 4 and not self.usercomponent.can_auth: # remove token and redirect # login_url can be also on a different host => merge_get_url target = "{}?{}={}".format( self.get_login_url(), REDIRECT_FIELD_NAME, quote_plus( merge_get_url( self.request.get_full_path(), token=None ) ) ) return HttpResponseRedirect(redirect_to=target) protection_codes = None if "protection" in self.request.GET: protection_codes = self.request.GET.getlist("protection") # execute protections for side effects even no_token self.request.protections = self.usercomponent.auth( request=self.request, scope=self.scope, protection_codes=protection_codes, ptype=ptype ) if ( type(self.request.protections) is int and # because: False==0 self.request.protections >= minstrength ): # generate no token if not required if no_token: return True token = self.create_token( extra={ "strength": self.usercomponent.strength, "prot_strength": self.request.protections } ) if token.extra["prot_strength"] >= 4: self.request.is_special_user = True self.request.is_owner = True self.request.token_expires = \ token.created+self.usercomponent.token_duration self.request.auth_token = token if "token" in self.request.GET: return self.replace_token() return True return False def has_special_access( self, user_by_login=True, user_by_token=False, staff=False, superuser=False ): if not hasattr(self, "usercomponent"): self.usercomponent = self.get_usercomponent() if user_by_login and self.request.user == self.usercomponent.user: self.request.is_owner = True self.request.is_special_user = True return True if user_by_token and self.test_token(4) is True: return True # remove user special state if is_fake if self.request.session.get("is_fake", False): return False if superuser and self.request.user.is_superuser: self.request.is_special_user = True self.request.is_staff = True return True if staff and self.request.user.is_staff: if type(staff) is bool: self.request.is_special_user = True self.request.is_staff = True return True if not isinstance(staff, (tuple, list, set)): staff = (staff,) if not all( map(self.request.user.has_perm, staff) ): return False self.request.is_special_user = True self.request.is_staff = True return True return False def get_user(self): """ Get user from user field or request """ if ( "user" not in self.kwargs and self.request.user.is_authenticated ): return self.request.user model = get_user_model() margs = {model.USERNAME_FIELD: None} margs[model.USERNAME_FIELD] = self.kwargs.get("user", None) return get_object_or_404( model.objects.select_related("spider_info"), **margs ) def get_usercomponent(self): query = { "token": self.kwargs["token"] } return get_object_or_404( UserComponent.objects.prefetch_related( "authtokens", "protections" ), **query ) def handle_no_permission(self): # in case no protections are used (e.g. add content) p = getattr(self.request, "protections", False) if not bool(p): # return 403 return super().handle_no_permission() # should be never true here assert(p is not True) context = { "spider_GET": self.sanitize_GET(), "LOGIN_URL": self.get_login_url(), "scope": getattr(self, "scope", None), "uc": self.usercomponent, "object": getattr(self, "object", None), "is_public_view": self.usercomponent.public } return self.response_class( request=self.request, template=self.get_noperm_template_names(), # render with own context; get_context_data may breaks stuff or # disclose informations context=context, using=self.template_engine, content_type=self.content_type ) def get_noperm_template_names(self): return "spider_base/protections/protections.html" class UCTestMixin(UserTestMixin): usercomponent = None def dispatch(self, request, *args, **kwargs): self.usercomponent = self.get_usercomponent() return super(UCTestMixin, self).dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): # for protections & contents return self.get(request, *args, **kwargs) def put(self, request, *args, **kwargs): # for protections & contents return self.get(request, *args, **kwargs) class ReferrerMixin(object): allow_domain_mode = False def get_context_data(self, **kwargs): kwargs["token_strength"] = None # will be overwritten in referring path so there is no interference kwargs["referrer"] = None kwargs["intentions"] = set() if self.request.auth_token: kwargs["referrer"] = self.request.auth_token.referrer kwargs["token_strength"] = self.request.auth_token.extra.get( "strength", None ) kwargs["intentions"].update(self.request.auth_token.extra.get( "intentions", [] )) return super().get_context_data(**kwargs) def test_token(self, minstrength, force_token=False): if "intention" in self.request.GET or "referrer" in self.request.GET: # validate early, before auth intentions = set(self.request.GET.getlist("intention")) if not VALID_INTENTIONS.issuperset( intentions ): return HttpResponse( "invalid intentions", status=400 ) if "domain" in intentions: if not self.clean_domain_upgrade( {"intentions": intentions}, False ): return HttpResponse( "invalid domain upgrade", status=400 ) # requires token force_token = True else: # maximal one main intention if len(intentions.difference(VALID_SUB_INTENTIONS)) > 1: return HttpResponse( "invalid intentions", status=400 ) minstrength = 4 return super().test_token(minstrength, force_token) def refer_with_post(self, context, token): # application/x-www-form-urlencoded is best here, # for beeing compatible to most webservers # client side rdf is no problem # NOTE: csrf must be disabled or use csrf token from GET, # here is no way to know the token value h = hashlib.sha256(context["referrer"].encode("utf8")).hexdigest() def h_fun(*a): return h # rate limit on errors if is_ratelimited( request=self.request, fn=self.refer_with_post, key=h_fun, rate=settings.SPIDER_DOMAIN_ERROR_RATE, increment=False ): return HttpResponseRedirect( redirect_to=merge_get_url( context["referrer"], status="post_failed", error="error_rate_limit" ) ) try: d = { "token": token.token, "hash_algorithm": settings.SPIDER_HASH_ALGORITHM.name, "renew": "false" } if context["payload"]: d["payload"] = context["payload"] ret = requests.post( context["referrer"], data=d, timeout=settings.SPIDER_REQUESTS_TIMEOUT, headers={ "Referer": merge_get_url( "%s%s" % ( context["hostpart"], self.request.path ) # sending full url not required anymore, payload # token=None, referrer=None, raw=None, intention=None, # sl=None, payload=None ) }, verify=certifi.where() ) ret.raise_for_status() except requests.exceptions.SSLError as exc: logging.info( "referrer: \"%s\" has a broken ssl configuration", context["referrer"], exc_info=exc ) return HttpResponseRedirect( redirect_to=merge_get_url( context["referrer"], status="post_failed", error="ssl" ) ) except Exception as exc: apply_error_limit = False if isinstance( exc, ( requests.exceptions.ConnectionError, requests.exceptions.Timeout ) ): apply_error_limit = True elif ( isinstance(exc, requests.exceptions.HTTPError) and exc.response.status_code >= 500 ): apply_error_limit = True if apply_error_limit: get_usage( request=self.request, fn=self.refer_with_post, key=h_fun, rate=settings.SPIDER_DOMAIN_ERROR_RATE, increment=True ) logging.info( "post failed: \"%s\" failed", context["referrer"], exc_info=exc ) return HttpResponseRedirect( redirect_to=merge_get_url( context["referrer"], status="post_failed", error="other" ) ) context["post_success"] = True h = get_hashob() h.update(token.token.encode("ascii", "ignore")) return HttpResponseRedirect( redirect_to=merge_get_url( context["referrer"], status="success", hash=h.finalize().hex() ) ) def refer_with_get(self, context, token): return HttpResponseRedirect( redirect_to=merge_get_url( context["referrer"], token=token.token, payload=context["payload"] ) ) def _get_clean_domain_upgrade_key(self, group, request): return "{}:{}".format( self.usercomponent.id, user_or_ip(request) ) def clean_domain_upgrade(self, context, token): if "referrer" not in self.request.GET: return False # domain mode must be used alone if len(context["intentions"]) > 1: return False if not context["intentions"].issubset(VALID_INTENTIONS): return False if not getattr(self.request, "_clean_domain_upgrade_checked", False): if is_ratelimited( request=self.request, fn=self.clean_domain_upgrade, key=self._get_clean_domain_upgrade_key, rate=settings.SPIDER_DOMAIN_UPDATE_RATE, increment=True ): return False setattr(self.request, "_clean_domain_upgrade_checked", True) # False for really no token if token is False: return True if not token or token.created_by_special_user: return False return True def clean_refer_intentions(self, context, token=None): # Only owner can use other intentions than domain if not self.request.is_owner: return False # Second error: invalid intentions # this is the second time the validation will be executed # in case test_token path is used # this is the first time the validation will be executed # in case has_special_access path is used if not context["intentions"].issubset(VALID_INTENTIONS): return False # auth is only for self.requesting component auth if "auth" in context["intentions"]: return False # maximal one main intention if len(context["intentions"].difference(VALID_SUB_INTENTIONS)) > 1: return False # "persist" or default can be serverless other intentions not # this way rogue client based attacks are prevented if "persist" in context["intentions"]: if not self.usercomponent.features.filter( name="Persistence" ).exists(): return False else: if context["is_serverless"] and len(context["intentions"]) != 1: return False if not token: return True ####### with token ######## # noqa: 266E if "persist" in context["intentions"]: # cannot add sl intention if "intentions" in token.extra: if "sl" in context["intentions"].difference( token.extra["intentions"] ): return False # set persist = true, (false=-1) token.persist = 0 # if possible, pin to anchor if self.usercomponent.primary_anchor: token.persist = self.usercomponent.primary_anchor.id else: # check if token was reused if not persisted if token.referrer: return False if "initial_referrer_url" not in token.extra: token.extra["initial_referrer_url"] = "{}://{}{}".format( self.request.scheme, self.request.get_host(), self.request.path ) return True def handle_referrer(self): _ = gettext if ( self.request.user != self.usercomponent.user and not self.request.auth_token ): return HttpResponseRedirect( redirect_to="{}?{}={}".format( self.get_login_url(), REDIRECT_FIELD_NAME, quote_plus( merge_get_url( self.request.get_full_path(), token=None ) ) ) ) context = self.get_context_data() context["intentions"] = set(self.request.GET.getlist("intention")) context["payload"] = self.request.GET.get("payload", None) context["is_serverless"] = "sl" in context["intentions"] context["referrer"] = merge_get_url(self.request.GET["referrer"]) context["old_search"] = [] context["object_list"] = self.object_list if not get_settings_func( "SPIDER_URL_VALIDATOR", "spkcspider.apps.spider.functions.validate_url_default" )(context["referrer"], self): return HttpResponse( status=400, content=_('Insecure url: %(url)s') % { "url": context["referrer"] } ) delete_auth_token = False action = self.request.POST.get("action", None) if "domain" in context["intentions"]: # domain mode only possible for token without user token = self.request.auth_token if not self.allow_domain_mode: return HttpResponse( status=400, content='domain mode disallowed' ) if not self.clean_domain_upgrade(context, token): return HttpResponse( status=400, content='Invalid token' ) token.create_auth_token() token.referrer = ReferrerObject.objects.get_or_create( url=context["referrer"] )[0] token.extra["intentions"] = list(context["intentions"]) try: token.save() except TokenCreationError: logging.exception("Token creation failed") return HttpResponseServerError( "Token creation failed, try again" ) context["post_success"] = False ret = self.refer_with_post(context, token) if not context["post_success"]: token.delete() return ret elif action == "confirm": token = None hasoldtoken = False # if persist try to find old token if "persist" in context["intentions"]: persistfind = Q(persist=0, usercomponent=self.usercomponent) persistfind |= Q( persist__in=self.usercomponent.contents.filter( info__contains="\x1eanchor\x1e" ).values_list("id", flat=True) ) token = AuthToken.objects.filter( persistfind, referrer__url=context["referrer"] ).first() if token: hasoldtoken = True # migrate usercomponent token.usercomponent = self.usercomponent # create only new token when admin token and not persisted token if self.usercomponent.user == self.request.user: # boost strength if user is owner # token is not auth_token if token: token.extra["strength"] = 10 # self.request new token token.create_auth_token() else: token = AuthToken( usercomponent=self.usercomponent, extra={ "strength": 10 } ) else: # either reuse persistent token with auth token tokenstring # or just reuse auth token if token: # slate auth token for destruction delete_auth_token = True # steal token value token.token = self.request.auth_token.token else: # repurpose token # NOTE: one token, one referrer token = self.request.auth_token # set to zero as prot_strength can elevate perms token.extra["prot_strength"] = 0 token.extra["intentions"] = list(context["intentions"]) if not self.clean_refer_intentions(context, token): return HttpResponseRedirect( redirect_to=merge_get_url( context["referrer"], error="intentions_incorrect" ) ) token.extra["filter"] = self.request.POST.getlist("search") if "live" in context["intentions"]: token.extra.pop("ids", None) else: token.extra["ids"] = list( self.object_list.values_list("id", flat=True) ) token.referrer = ReferrerObject.objects.get_or_create( url=context["referrer"] )[0] # after cleanup, save try: with transaction.atomic(): # must be done here, elsewise other token can (unlikely) # take token as it is free for a short time, better be safe if delete_auth_token: # delete old token self.request.auth_token.delete() token.save() except TokenCreationError: logging.exception("Token creation failed") return HttpResponseServerError( _("Token creation failed, try again") ) if context["is_serverless"]: context["post_success"] = True ret = self.refer_with_get(context, token) else: context["post_success"] = False ret = self.refer_with_post(context, token) if not context["post_success"] and not hasoldtoken: token.delete() return ret elif action == "cancel": return HttpResponseRedirect( redirect_to=merge_get_url( context["referrer"], status="canceled", payload=context["payload"] ) ) else: token = None oldtoken = None # use later reused token early if self.usercomponent.user != self.request.user: token = self.request.auth_token # don't re-add search parameters, only initialize if ( self.request.method != "POST" and "persist" in context["intentions"] ): persistfind = Q(persist=0, usercomponent=self.usercomponent) persistfind |= Q( persist__in=self.usercomponent.contents.filter( info__contains="\x1eanchor\x1e" ).values_list("id", flat=True) ) oldtoken = AuthToken.objects.filter( persistfind, referrer__url=context["referrer"], ).first() if oldtoken: context["old_search"] = oldtoken.extra.get("search", []) if not self.clean_refer_intentions(context, token): return HttpResponse( status=400, content=_('Error: intentions incorrect') ) return self.response_class( request=self.request, template=self.get_referrer_template_names(), context=context, using=self.template_engine, content_type=self.content_type ) def get_referrer_template_names(self): return "spider_base/protections/referring.html" class EntityDeletionMixin(UserTestMixin): object = None http_method_names = ['get', 'post', 'delete'] def get_context_data(self, **kwargs): _time = self.get_required_timedelta() if _time and self.object.deletion_requested: now = timezone.now() if self.object.deletion_requested + _time >= now: kwargs["remaining"] = timedelta(seconds=0) else: kwargs["remaining"] = self.object.deletion_requested+_time-now return super().get_context_data(**kwargs) def get_required_timedelta(self): _time = self.object.deletion_period if _time: _time = timedelta(seconds=_time) else: _time = timedelta(seconds=0) return _time def options(self, request, *args, **kwargs): ret = super().options() ret["Access-Control-Allow-Origin"] = self.request.get_host() ret["Access-Control-Allow-Methods"] = "POST, GET, DELETE, OPTIONS" return ret def delete(self, request, *args, **kwargs): _time = self.get_required_timedelta() if _time: now = timezone.now() if self.object.deletion_requested: if self.object.deletion_requested+_time >= now: return self.get(request, *args, **kwargs) else: self.object.deletion_requested = now self.object.save() return self.get(request, *args, **kwargs) self.object.delete() return HttpResponseRedirect(self.get_success_url()) def post(self, request, *args, **kwargs): # because forms are screwed (delete not possible) # UPDATE: delete works if allowed in CORS if request.POST.get("action") == "reset": return self.reset(request, *args, **kwargs) elif request.POST.get("action") == "delete": return self.delete(request, *args, **kwargs) return super().get(request, *args, **kwargs) def reset(self, request, *args, **kwargs): self.object.deletion_requested = None self.object.save(update_fields=["deletion_requested"]) return HttpResponseRedirect(self.get_success_url()) PK!i'spkcspider/apps/spider/views/_tokens.py__all__ = ( "TokenDelete", "TokenDeletionRequest", "TokenRenewal" ) import logging from django.shortcuts import get_object_or_404 from django.views.generic.edit import DeleteView from django.http import ( Http404, HttpResponseServerError, JsonResponse, HttpResponseRedirect, HttpResponse ) from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from django.views import View import requests import certifi from ._core import UCTestMixin from ..models import AuthToken from ..helpers import get_settings_func from ..constants import TokenCreationError class TokenDelete(UCTestMixin, DeleteView): def get_object(self): return None def delete(self, request, *args, **kwargs): self.remove_old_tokens() query = AuthToken.objects.filter( usercomponent=self.usercomponent, id__in=self.request.POST.getlist("tokens") ) # replace active admin token if query.filter( created_by_special_user=self.request.user ).exists(): self.request.auth_token = self.create_token( self.request.user, extra={ "strength": 10 } ) query.delete() del query return self.get(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.delete(self, request, *args, **kwargs) def get(self, request, *args, **kwargs): self.remove_old_tokens() response = { "tokens": [ { "expires": None if i.persist >= 0 else ( i.created + self.usercomponent.token_duration ).strftime("%a, %d %b %Y %H:%M:%S %z"), "referrer": i.referrer if i.referrer else "", "name": str(i), "id": i.id } for i in AuthToken.objects.filter( usercomponent=self.usercomponent ) ], "admin": AuthToken.objects.filter( usercomponent=self.usercomponent, created_by_special_user=self.request.user ).first() } if response["admin"]: # don't censor, required in modal presenter response["admin"] = response["admin"].token return JsonResponse(response) class TokenDeletionRequest(UCTestMixin, DeleteView): model = AuthToken template_name = "spider_base/protections/authtoken_confirm_delete.html" def dispatch(self, request, *args, **kwargs): self.object = self.get_object() try: return super().dispatch(request, *args, **kwargs) except Http404: return get_settings_func( "RATELIMIT_FUNC", "spkcspider.apps.spider.functions.rate_limit_default" )(self, request) def test_func(self): if self.has_special_access( user_by_token=True, user_by_login=True, superuser=True, staff="spider_base.delete_authtoken" ): return True return self.test_token() def get_usercomponent(self): return self.object.usercomponent def get_object(self, queryset=None): if not queryset: queryset = self.get_queryset() return get_object_or_404( queryset, token=self.request.GET.get("delete", ""), persist__gte=0 ) def delete(self, request, *args, **kwargs): self.object.delete() return HttpResponseRedirect( redirect_to=self.object.referrer ) def post(self, request, *args, **kwargs): return self.delete(request, *args, **kwargs) class TokenRenewal(UCTestMixin, View): model = AuthToken @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): try: return super().dispatch(request, *args, **kwargs) except Http404: return get_settings_func( "RATELIMIT_FUNC", "spkcspider.apps.spider.functions.rate_limit_default" )(self, request) def get_usercomponent(self): token = self.request.POST.get("token", None) if not token: raise Http404() self.request.auth_token = get_object_or_404( AuthToken, token=token, persist__gte=0, referrer__isnull=False ) if ( not self.request.auth_token.referrer or "persist" not in self.request.auth_token.extra.get( "intentions", [] ) ): raise Http404() return self.request.auth_token.usercomponent def test_func(self): return True def options(self, request, *args, **kwargs): ret = super().options() ret["Access-Control-Allow-Origin"] = \ self.request.auth_token.referrer.host ret["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS" return ret def update_with_post(self): # application/x-www-form-urlencoded is best here, # for beeing compatible to most webservers # client side rdf is no problem # NOTE: csrf must be disabled or use csrf token from GET, # here is no way to know the token value try: d = { "token": self.request.auth_token.token, "renew": "true" } if "payload" in self.request.POST: d["payload"] = self.request.POST["payload"] ret = requests.post( self.request.auth_token.referrer, data=d, headers={ "Referer": "%s://%s" % ( self.request.scheme, self.request.path ) }, timeout=settings.SPIDER_REQUESTS_TIMEOUT, verify=certifi.where() ) ret.raise_for_status() except requests.exceptions.SSLError as exc: logging.info( "referrer: \"%s\" has a broken ssl configuration", self.request.auth_token.referrer, exc_info=exc ) return False except Exception as exc: logging.info( "post failed: \"%s\" failed", self.request.auth_token.referrer, exc_info=exc ) return False return True def post(self, request, *args, **kwargs): self.request.auth_token.create_token() success = True if "sl" in self.request.extra.get("intentions", []): # only the original referer can access this success = ( self.request["Referer"] == self.request.auth_token.referrer ) else: # if not serverless: # you cannot steal a token because it is bound to a domain # damage can be only done in persisted data success = self.update_with_post() if success: try: self.request.auth_token.save() except TokenCreationError: success = False if not success: logging.exception("Token creation failed") return HttpResponseServerError( "Token update failed, try again" ) if "sl" in self.request.extra.get("intentions", []): ret = HttpResponse( self.request.auth_token.token.encode( "ascii" ), content_type="text/plain" ) ret["Access-Control-Allow-Origin"] = \ self.request.auth_token.referrer else: ret = HttpResponse(status_code=200) # no sl: simplifies update logic for thirdparty web servers # sl: safety (but anyway checked by referer check) ret["Access-Control-Allow-Origin"] = \ self.request.auth_token.referrer.host return ret PK!W#/#/!spkcspider/apps/spider/widgets.py__all__ = [ "OpenChoiceWidget", "StateButtonWidget", "HTMLWidget", "SubSectionStartWidget", "SubSectionStopWidget", "ListWidget" ] from django.forms import widgets from django.conf import settings from django.utils.translation import get_language, gettext_lazy as _ _extra = '' if settings.DEBUG else '.min' class StateButtonWidget(widgets.CheckboxInput): template_name = 'spider_base/forms/widgets/statebutton.html' # ugly as hell class Media: css = { 'all': [ 'spider_base/statebutton.css' ] } class ListWidget(widgets.SelectMultiple): template_name = 'spider_base/forms/widgets/wrapped_select.html' allow_multiple_selected = True class Media: js = [ 'node_modules/@json-editor/json-editor/dist/jsoneditor%s.js' % _extra, # noqa:E501 'spider_base/ListEditorWidget.js' ] def __init__( self, *, attrs=None, wrapper_attrs=None, format_type="text", item_label=_("item"), **kwargs ): if not attrs: attrs = {"class": ""} if not wrapper_attrs: wrapper_attrs = {} attrs.setdefault("class", "") attrs["class"] += " SpiderListTarget" attrs["format_type"] = format_type # don't access them as they are lazy evaluated attrs["item_label"] = item_label self.wrapper_attrs = wrapper_attrs.copy() super().__init__(attrs=attrs, **kwargs) def __deepcopy__(self, memo): obj = super().__deepcopy__(memo) obj.wrapper_attrs = self.wrapper_attrs.copy() return obj def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) context['widget']['wrapper_attrs'] = self.wrapper_attrs context['widget']['wrapper_attrs']["id"] = "{}_inner_wrapper".format( context['widget']['attrs']["id"] ) return context def optgroups(self, name, value, attrs=None): """Return a list of optgroups for this widget.""" groups = [] for index, option_value in enumerate(value or []): if option_value is None: option_value = '' # selected = True groups.append(( None, [self.create_option( name, option_value, option_value, True, index, subindex=None, attrs=attrs, )], index )) return groups class HTMLWidget(widgets.Input): template_name = 'spider_base/forms/widgets/info.html' input_type = "html_widget" def __init__(self, *, template_name=None, **kwargs): if template_name: self.template_name = template_name super().__init__(**kwargs) def use_required_attribute(self, initial): return False def format_value(self, value): return None def value_omitted_from_data(self, data, files, name): return None class SubSectionStartWidget(HTMLWidget): input_type = "start_pseudo" template_name = 'spider_base/forms/widgets/subsectionstart.html' label = None def __init__(self, *, label=None, **kwargs): self.label = label super().__init__(**kwargs) def get_context(self, name, value, attrs): ret = super().get_context(name, value, attrs) ret["label"] = self.label return ret class SubSectionStopWidget(HTMLWidget): input_type = "stop_pseudo" template_name = 'spider_base/forms/widgets/subsectionstop.html' class Select2Widget(widgets.Select): class Media: js = [ 'node_modules/jquery/dist/jquery%s.js' % _extra, 'node_modules/select2/dist/js/select2%s.js' % _extra, 'spider_base/Select2Widget.js' ] css = { 'all': [ 'node_modules/select2/dist/css/select2%s.css' % _extra ] } def __init__(self, allow_multiple_selected, *, attrs=None, **kwargs): self.allow_multiple_selected = allow_multiple_selected if not attrs: attrs = {"class": ""} attrs.setdefault("class", "") attrs["class"] += " Select2WidgetTarget" super().__init__(attrs=attrs, **kwargs) class OpenChoiceWidget(widgets.Select): class Media: css = { 'all': [ 'node_modules/select2/dist/css/select2.min.css' ] } js = [ 'node_modules/jquery/dist/jquery%s.js' % _extra, 'node_modules/select2/dist/js/select2%s.js' % _extra, 'spider_base/OpenChoiceWidget.js' ] def __init__(self, allow_multiple_selected, *, attrs=None, **kwargs): self.allow_multiple_selected = allow_multiple_selected if not attrs: attrs = {"class": ""} attrs.setdefault("class", "") attrs["class"] += " OpenChoiceTarget" super().__init__(attrs=attrs, **kwargs) def optgroups(self, name, value, attrs=None): """Return a list of optgroups for this widget.""" groups = [] groups2 = [] has_selected = False choices = list(self.choices) value_set = set(value) # optimization if isinstance(value, set): value = value_set lastindex = 0 for index, (option_value, option_label) in enumerate(choices): if option_value is None: option_value = '' subgroup = [] if isinstance(option_label, (list, tuple)): group_name = option_value subindex = 0 choices = option_label else: group_name = None subindex = None choices = [(option_value, option_label)] groups.append((group_name, subgroup, index)) for subvalue, sublabel in choices: selected = False if str(subvalue) in value_set: value_set.remove(str(subvalue)) selected = True selected = ( selected and (not has_selected or self.allow_multiple_selected) ) has_selected |= selected subgroup.append(self.create_option( name, subvalue, sublabel, selected, index, subindex=subindex, attrs=attrs, )) if subindex is not None: subindex += 1 lastindex = index for index, option_value in enumerate(value, lastindex): if option_value not in value_set: continue if option_value is None: option_value = '' subgroup = [self.create_option( name, option_value, option_value, (not has_selected or self.allow_multiple_selected), index, subindex=None, attrs=attrs, )] groups2.append((None, subgroup, index)) return groups2 + groups class TrumbowygWidget(widgets.Textarea): template_name = 'spider_base/forms/widgets/wrapped_textarea.html' _media = widgets.Media( css={ 'all': [ 'node_modules/trumbowyg/dist/ui/trumbowyg.min.css', 'node_modules/trumbowyg/dist/plugins/emoji/ui/trumbowyg.emoji.css', # noqa: E501 'node_modules/trumbowyg/dist/plugins/colors/ui/trumbowyg.colors.css', # noqa: E501 # 'node_modules/trumbowyg/dist/plugins/history/ui/trumbowyg.history.css' # noqa: E501 # 'node_modules/jquery-resizable/resizable.css', ] }, js=[ 'node_modules/jquery/dist/jquery%s.js' % _extra, # 'node_modules/jquery-resizable/resizable.js', 'node_modules/trumbowyg/dist/trumbowyg%s.js' % _extra, 'node_modules/trumbowyg/dist/plugins/emoji/trumbowyg.emoji.min.js', 'node_modules/trumbowyg/dist/plugins/preformatted/trumbowyg.preformatted.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/insertaudio/trumbowyg.insertaudio.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/pasteimage/trumbowyg.pasteimage.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/lineheight/trumbowyg.lineheight.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/base64/trumbowyg.base64.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/history/trumbowyg.history.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/colors/trumbowyg.colors.min.js', # noqa: E501 # 'node_modules/trumbowyg/dist/plugins/resizimg/trumbowyg.resizimg.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/table/trumbowyg.table.min.js', 'spider_base/trumbowygWidget.js', ] ) @property def media(self): if not self.is_localized: return self._media lang = get_language() if not lang or lang.startswith("en"): return self._media lang = lang.replace("-", "_") return widgets.Media( css={ 'all': [ 'node_modules/trumbowyg/dist/ui/trumbowyg.min.css', 'node_modules/trumbowyg/dist/plugins/emoji/ui/trumbowyg.emoji.css', # noqa: E501 'node_modules/trumbowyg/dist/plugins/colors/ui/trumbowyg.colors.css', # noqa: E501 # 'node_modules/trumbowyg/dist/plugins/history/ui/trumbowyg.history.css' # noqa: E501 # 'node_modules/jquery-resizable/resizable.css', ] }, js=[ 'node_modules/jquery/dist/jquery%s.js' % _extra, # 'node_modules/jquery-resizable/resizable.js', 'node_modules/trumbowyg/dist/trumbowyg%s.js' % _extra, 'node_modules/trumbowyg/dist/plugins/emoji/trumbowyg.emoji.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/preformatted/trumbowyg.preformatted.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/insertaudio/trumbowyg.insertaudio.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/pasteimage/trumbowyg.pasteimage.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/lineheight/trumbowyg.lineheight.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/base64/trumbowyg.base64.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/history/trumbowyg.history.min.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/colors/trumbowyg.colors.min.js', # noqa: E501 # 'node_modules/trumbowyg/dist/plugins/resizimg/trumbowyg.resizimg.js', # noqa: E501 'node_modules/trumbowyg/dist/plugins/table/trumbowyg.table.min.js', # noqa: E501 'spider_base/trumbowygWidget.js', 'node_modules/trumbowyg/dist/langs/{}.min.js'.format( lang ) ] ) def __init__(self, *, attrs=None, wrapper_attrs=None, **kwargs): if not attrs: attrs = {"class": ""} if not wrapper_attrs: wrapper_attrs = {"class": ""} attrs.setdefault("class", "") wrapper_attrs.setdefault("class", "") attrs["class"] += " TrumbowygTarget" wrapper_attrs["class"] += " TrumbowygTargetWrapper" self.wrapper_attrs = wrapper_attrs.copy() super().__init__(attrs=attrs, **kwargs) def __deepcopy__(self, memo): obj = super().__deepcopy__(memo) obj.wrapper_attrs = self.wrapper_attrs.copy() return obj def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) context['widget']['wrapper_attrs'] = self.wrapper_attrs return context PK!EQQ+spkcspider/apps/spider_accounts/__init__.pydefault_app_config = 'spkcspider.apps.spider_accounts.apps.SpiderAccountsConfig' PK!x(spkcspider/apps/spider_accounts/admin.pyfrom django.contrib import admin from django.contrib.auth import admin as user_admin from django.utils.translation import gettext_lazy as _ from .models import SpiderUser # Register your models here. @admin.register(SpiderUser) class UserAdmin(user_admin.UserAdmin): # exclude = ["first_name", "last_name"] list_display = ('username', 'email', 'is_active', 'is_staff') list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups') search_fields = ('username', 'email') fieldsets = ( (None, {'fields': ('username', 'password')}), (_('Personal info'), {'fields': ('email',)}), (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'quota_local', 'quota_remote', 'groups', 'user_permissions')}), (_('Important dates'), {'fields': ('last_login', 'date_joined')}), ) def has_module_permission(self, request): return True def has_change_permission(self, request, obj=None): if obj: if not request.user.is_active: return False if request.user.is_superuser or request.user == obj: return True # only superuser can alter superusers if obj.is_superuser: return False return super().has_change_permission(request, obj) def has_delete_permission(self, request, obj=None): if not request.user.is_active: return False if obj and obj != request.user: if request.user.is_superuser: return True # only superuser can delete superusers if obj.is_superuser: return False return super().has_delete_permission(request, obj) def get_readonly_fields(self, request, obj=None): fields = [] if not request.user.is_superuser: fields.append("is_superuser") fields.append("last_login") fields.append("date_joined") if not self.has_change_permission(request, obj): fields.append("groups") fields.append("permissions") fields.append("is_staff") return fields PK!XR'spkcspider/apps/spider_accounts/apps.py__all__ = ["SpiderAccountsConfig"] from django.apps import AppConfig class SpiderAccountsConfig(AppConfig): name = 'spkcspider.apps.spider_accounts' label = 'spider_accounts' verbose_name = 'Spkcspider User Model' spider_url_path = "accounts/" PK!R1 (spkcspider/apps/spider_accounts/forms.pyfrom django import forms from django.contrib.auth.forms import UserCreationForm, UserChangeForm from django.contrib.auth import get_user_model from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ from django.urls import reverse from django.conf import settings if getattr(settings, "USE_CAPTCHAS", False): from captcha.fields import CaptchaField else: CaptchaField = None class SignupForm(UserCreationForm): # TODO: for a better spammer protection, use useragent for seeding # and add/rename, move random fields # disguise hidden fields use_required_attribute = False # real: username, fake for simple spammers name = forms.CharField( label="", max_length=100, required=False, widget=forms.HiddenInput() ) # fake for simple spammers email = forms.EmailField( label="", max_length=100, required=False, widget=forms.HiddenInput() ) # email, real liame = forms.EmailField( label=_('Email Address'), max_length=100, required=False, help_text=_("(optional)") ) class Meta: model = get_user_model() fields = ['username', 'password1', 'password2', 'name', 'liame', 'email'] def is_valid(self): """ Returns True if the form has no errors. Otherwise, False. If errors are being ignored, returns False. """ if self.data['email'] or self.data['name']: return False return self.is_bound and not self.errors def clean(self): # clean up email hack super(SignupForm, self).clean() self.cleaned_data["email"] = self.cleaned_data["liame"] del self.cleaned_data["liame"] del self.cleaned_data["name"] if CaptchaField: # add captcha, disguised name SignupForm.declared_fields[settings.SPIDER_CAPTCHA_FIELD_NAME] = \ CaptchaField(label=_("Captcha")) SignupForm.base_fields[settings.SPIDER_CAPTCHA_FIELD_NAME] = \ SignupForm.declared_fields[settings.SPIDER_CAPTCHA_FIELD_NAME] class UserUpdateForm(UserChangeForm): class Meta(UserChangeForm.Meta): model = get_user_model() fields = model.SAFE_FIELDS def __init__(self, *args, **kwargs): _ = gettext super(UserChangeForm, self).__init__(*args, **kwargs) self.fields['password'].help_text = \ self.fields['password'].help_text.format( reverse('auth:password_change') ) f = self.fields.get('user_permissions') if f is not None: f.queryset = f.queryset.select_related('content_type') self.fields['email'].help_text += _("Optional") PK!1j j :spkcspider/apps/spider_accounts/migrations/0001_initial.py# Generated by Django 2.0.5 on 2018-05-27 13:44 import django.contrib.auth.models import django.contrib.auth.validators from django.db import migrations, models import django.utils.timezone class Migration(migrations.Migration): initial = True dependencies = [ ('auth', '0009_alter_user_last_name_max_length'), ] operations = [ migrations.CreateModel( name='SpiderUser', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], options={ 'verbose_name': 'user', 'verbose_name_plural': 'users', 'abstract': False, 'swappable': 'AUTH_USER_MODEL', }, managers=[ ('objects', django.contrib.auth.models.UserManager()), ], ), ] PK!kfEspkcspider/apps/spider_accounts/migrations/0002_auto_20180810_1433.py# Generated by Django 2.1 on 2018-08-10 14:33 from django.db import migrations import spkcspider.apps.spider_accounts.models class Migration(migrations.Migration): dependencies = [ ('spider_accounts', '0001_initial'), ] operations = [ migrations.AlterModelManagers( name='spideruser', managers=[ ('objects', spkcspider.apps.spider_accounts.models.UserManager()), ], ), ] PK!4gCspkcspider/apps/spider_accounts/migrations/0003_spideruser_quota.py# Generated by Django 2.1.1 on 2018-09-22 22:11 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_accounts', '0002_auto_20180810_1433'), ] operations = [ migrations.AddField( model_name='spideruser', name='quota', field=models.PositiveIntegerField(blank=True, default=None, help_text='Quota in Bytes, null to use standard', null=True), ), ] PK!xG@Espkcspider/apps/spider_accounts/migrations/0004_auto_20190109_1505.py# Generated by Django 2.1.5 on 2019-01-09 15:05 from django.db import migrations, models import spkcspider.apps.spider_accounts.models class Migration(migrations.Migration): dependencies = [ ('spider_accounts', '0003_spideruser_quota'), ] operations = [ migrations.RemoveField( model_name='spideruser', name='quota', ), migrations.AddField( model_name='spideruser', name='quota_local', field=models.PositiveIntegerField(blank=True, default=spkcspider.apps.spider_accounts.models.default_quota_spider_user_local, help_text='Quota in Bytes, null for no limit', null=True), ), migrations.AddField( model_name='spideruser', name='quota_remote', field=models.PositiveIntegerField(blank=True, default=spkcspider.apps.spider_accounts.models.default_quota_spider_user_remote, help_text='Quota in Bytes, null for no limit', null=True), ), ] PK!7]]Rspkcspider/apps/spider_accounts/migrations/0005_spideruser_quota_usercomponents.py# Generated by Django 2.2 on 2019-04-17 19:28 from django.db import migrations, models import spkcspider.apps.spider_accounts.models class Migration(migrations.Migration): dependencies = [ ('spider_accounts', '0004_auto_20190109_1505'), ] operations = [ migrations.AddField( model_name='spideruser', name='quota_usercomponents', field=models.PositiveIntegerField(blank=True, default=spkcspider.apps.spider_accounts.models.default_quota_spider_user_components, help_text='Quota in units, null for no limit', null=True), ), ] PK!6spkcspider/apps/spider_accounts/migrations/__init__.pyPK!MDy~~)spkcspider/apps/spider_accounts/models.py from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import UserManager as CoreUserManager class UserManager(CoreUserManager): def create_superuser(self, username, password, email=None, **extra_fields): # fix users with optional email super().create_superuser(username, email, password, **extra_fields) # Create your models here. def default_quota_spider_user_local(): return getattr(settings, "SPIDER_USER_QUOTA_LOCAL", None) def default_quota_spider_user_remote(): return getattr(settings, "SPIDER_USER_QUOTA_REMOTE", None) def default_quota_spider_user_components(): return getattr(settings, "SPIDER_USER_QUOTA_USERCOMPONENTS", None) class SpiderUser(AbstractUser): """ A reference User Implementation suitable for spkcspider """ REQUIRED_FIELDS = [] SAFE_FIELDS = ['email', 'password'] if getattr(settings, 'ALLOW_USERNAME_CHANGE', False): SAFE_FIELDS.insert(0, 'username') # optional quota quota_local = models.PositiveIntegerField( null=True, blank=True, default=default_quota_spider_user_local, help_text=_("Quota in Bytes, null for no limit") ) quota_remote = models.PositiveIntegerField( null=True, blank=True, default=default_quota_spider_user_remote, help_text=_("Quota in Bytes, null for no limit") ) quota_usercomponents = models.PositiveIntegerField( null=True, blank=True, default=default_quota_spider_user_components, help_text=_("Quota in units, null for no limit") ) # first_name,last_name are used in the UserForm so don't remove them # unless you want spending your time adapting the adminform objects = UserManager() class Meta(AbstractUser.Meta): swappable = 'AUTH_USER_MODEL' PK!^VR**@spkcspider/apps/spider_accounts/templates/registration/base.html{% extends "spider_base/base.html" %} {% block robots %}none{% endblock %} {% block extrahead %} {{block.super}}{% if form %} {{form.media|safe}}{% endif %} {% endblock extrahead %} {% block main_classes %}{{block.super}} w3-padding spkc-content w3-card-4 w3-pale-red{% endblock main_classes %} PK!_`Fspkcspider/apps/spider_accounts/templates/registration/logged_out.html{% extends "registration/base.html" %} {% load i18n %} {% block extrahead %} {{block.super}} {% endblock %} {% block content %}

{% trans "Logout successful." %}

{% trans 'Click if not redirected' %} {% endblock %} {# form errors should not appear here #} {% block errors %}{% endblock errors%} PK!EAspkcspider/apps/spider_accounts/templates/registration/login.html{% extends "registration/base.html" %} {% load i18n static spider_protections spider_rdf %} {% extract_protections "protections" as protections %} {% block main_attributes %}{{block.super}} prefix="spkc: {% spkc_namespace %} xsd: http://www.w3.org/2001/XMLSchema#" resource="{{hostpart}}{{ request.path }}"{% endblock %} {% block content %}

Login

{% csrf_token %}
{% include "spider_base/partials/base_form.html" with form=form %}

{% trans 'Authenticate with' %}

{% extract_protections "protections" as protections %} {% for prot in protections %} {% include "spider_base/protections/protection_item.html" with prot=prot %} {% endfor %}

{% if SETTINGS.OPEN_FOR_REGISTRATION %} {% trans 'No account? Signup?' %} {% endif %}
{% endblock %} PK!ZPspkcspider/apps/spider_accounts/templates/registration/password_change_done.html{% extends "registration/base.html" %} {% load i18n %} {% block extrahead %} {{block.super}} {% endblock %} {% block title %}{{ title }}{% endblock %} {% block content %}

{% trans 'Your password was changed.' %}

{% trans 'Click if not redirected' %} {% endblock %} {# form errors should not appear here #} {% block errors %}{% endblock errors%} PK!jjPspkcspider/apps/spider_accounts/templates/registration/password_change_form.html{% extends "registration/base.html" %} {% load i18n static %} {% block title %}{{ title }}{% endblock %} {% block content %}
{% csrf_token %}

{% trans 'Change Password' %}

{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}

{% include "spider_base/partials/base_form.html" with form=form %}
{% endblock %} PK!l/v  Cspkcspider/apps/spider_accounts/templates/registration/profile.html{% extends "registration/base.html" %} {% load i18n static %} {% block content %} {% trans 'Update User Profile' as legend %} {% trans 'Update' as confirm %} {% include "spider_base/edit_form.html" with form=form legend=legend confirm=confirm %} {% endblock %} PK!1B@Bspkcspider/apps/spider_accounts/templates/registration/signup.html{% extends "registration/base.html" %} {% load i18n static %} {% block main_classes %}{{block.super}} w3-animate-bottom{% endblock %} {% block content %}
{% csrf_token %} {# don't annotate (correctly) for making it harder for bots #}

{% trans 'Sign up' %}

{% include "spider_base/partials/base_form.html" with form=form %}

{% trans 'Already an account? Login?' %}
{% endblock %} PK!1WBspkcspider/apps/spider_accounts/templates/registration/thanks.html{% extends "registration/base.html" %} {% load i18n %} {% block extrahead %} {{block.super}} {% endblock %} {% block content %}

{% blocktrans with name=site_name trimmed=True %} Signup successful. Thank you for using {{name}} {% endblocktrans %}

{% trans 'Click if not redirected' %} {% endblock %} {# form errors should not appear here #} {% block errors %}{% endblock errors%} PK!^+'spkcspider/apps/spider_accounts/urls.py from django.conf import settings from django.urls import path, reverse_lazy from django.contrib.auth.views import ( PasswordChangeView, PasswordChangeDoneView ) from django.views.generic.base import RedirectView, TemplateView from .views import SignupView, UserUpdateView, UserLoginView, UserLogoutView app_name = "auth" # no recovery, only authentication urlpatterns = [ path('login/', UserLoginView.as_view(), name='login'), path('logout/', UserLogoutView.as_view(), name='logout'), path( 'password_change/', PasswordChangeView.as_view( success_url=reverse_lazy("auth:password_change_done") ), name='password_change' ), path( 'password_change/done/', PasswordChangeDoneView.as_view(), name='password_change_done' ), path( 'thanks/', TemplateView.as_view(template_name='registration/thanks.html'), name="signup_thanks" ), path('profile/', UserUpdateView.as_view(), name="profile"), path( '', RedirectView.as_view( url=reverse_lazy('auth:profile'), permanent=True ) ), ] if getattr(settings, "OPEN_FOR_REGISTRATION", False): urlpatterns.append( path('signup/', SignupView.as_view(), name="signup") ) PK!<'w (spkcspider/apps/spider_accounts/views.py__all__ = ( "SignupView", "UserLoginView", "UserLogoutView", "UserUpdateView" ) from django.http import HttpResponseRedirect from django.contrib.auth import login, authenticate from django.contrib.auth.views import LoginView, LogoutView from django.views.generic.edit import FormView, UpdateView from django.contrib.auth.mixins import LoginRequiredMixin from django.views.decorators.cache import never_cache from django.utils.decorators import method_decorator from django.urls import reverse_lazy from django.contrib.auth import get_user_model from django.conf import settings from .forms import SignupForm, UserUpdateForm from spkcspider.apps.spider.forms import SpiderAuthForm # Create your views here. class SignupView(FormView): template_name = 'registration/signup.html' form_class = SignupForm success_url = reverse_lazy("auth:signup_thanks") def form_valid(self, form): form.save() username = form.cleaned_data.get('username') raw_password = form.cleaned_data.get('password1') user = authenticate(username=username, password=raw_password) login(self.request, user) return HttpResponseRedirect(self.get_success_url()) def get_success_url(self): if "next" in self.request.GET: return self.request.GET["next"] return self.success_url class UserLoginView(LoginView): form_class = SpiderAuthForm def get(self, request, *args, **kwargs): if request.user.is_authenticated: return HttpResponseRedirect(self.get_success_url()) return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): return super().post(request, *args, **kwargs) def form_valid(self, form): """Security check complete. Log the user in.""" # backend for loging in, select the first backend = settings.AUTHENTICATION_BACKENDS[0] login( self.request, form.get_user(), backend ) return HttpResponseRedirect(self.get_success_url()) def form_invalid(self, form): form.reset_protections() return super().form_invalid(form) class UserLogoutView(LogoutView): @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): ret = super().dispatch(request, *args, **kwargs) ret["Clear-Site-Data"] = "*" return ret class UserUpdateView(LoginRequiredMixin, UpdateView): success_url = reverse_lazy('auth:profile') template_name = 'registration/profile.html' form_class = UserUpdateForm def get_object(self, queryset=None): obj = get_user_model().objects.get(id=self.request.user.pk) return obj PK!=MM)spkcspider/apps/spider_filets/__init__.pydefault_app_config = 'spkcspider.apps.spider_filets.apps.SpiderFiletsConfig' PK!??&spkcspider/apps/spider_filets/admin.pyfrom django.contrib import admin # Register your models here. PK!ՅCC%spkcspider/apps/spider_filets/apps.py__all__ = ["SpiderFiletsConfig"] from django.apps import AppConfig from django.db.models.signals import post_delete def DeleteFilesCb(sender, instance, **kwargs): if instance.file: instance.file.delete(False) class SpiderFiletsConfig(AppConfig): name = 'spkcspider.apps.spider_filets' label = 'spider_filets' verbose_name = 'spkcspider File and Text content' def ready(self): from .models import FileFilet post_delete.connect( DeleteFilesCb, sender=FileFilet, dispatch_uid="delete_files_filet" ) PK![%spkcspider/apps/spider_filets/conf.py__all__ = ( "DEFAULT_LICENSE_FILE", "DEFAULT_LICENSE_TEXT", "LICENSE_CHOICES" ) from django.conf import settings LICENSE_CHOICES = getattr( settings, "SPIDER_LICENSE_CHOICES", {} ) DEFAULT_LICENSE_FILE = getattr( settings, "SPIDER_DEFAULT_LICENSE_FILE", lambda uc, user: "other" ) if not callable(DEFAULT_LICENSE_FILE): _DEFAULT_LICENSE_FILE = DEFAULT_LICENSE_FILE def DEFAULT_LICENSE_FILE(uc, user): return _DEFAULT_LICENSE_FILE DEFAULT_LICENSE_TEXT = getattr( settings, "SPIDER_DEFAULT_LICENSE_TEXT", DEFAULT_LICENSE_FILE ) if not callable(DEFAULT_LICENSE_TEXT): _DEFAULT_LICENSE_TEXT = DEFAULT_LICENSE_TEXT def DEFAULT_LICENSE_TEXT(uc, user): return _DEFAULT_LICENSE_TEXT PK!|&spkcspider/apps/spider_filets/forms.py__all__ = ["FileForm", "TextForm", "RawTextForm"] from django.db import models from django.conf import settings from django.utils.translation import gettext_lazy as _ from django import forms from spkcspider.apps.spider.helpers import get_settings_func from spkcspider.apps.spider.fields import SanitizedHtmlField from .models import FileFilet, TextFilet from .conf import ( DEFAULT_LICENSE_FILE, DEFAULT_LICENSE_TEXT, LICENSE_CHOICES ) from .widgets import LicenseChooserWidget from spkcspider.apps.spider.fields import ( OpenChoiceField, MultipleOpenChoiceField ) from spkcspider.apps.spider.widgets import ListWidget, Select2Widget _extra = '' if settings.DEBUG else '.min' def check_attrs_func(tag, name, value): # currently no objections return True def _extract_choice(item): return (item[0], item[1].get("name", item[0])) class FileForm(forms.ModelForm): license_name = OpenChoiceField( label=_("License"), help_text=_("Select license"), choices=map(_extract_choice, LICENSE_CHOICES.items()), widget=LicenseChooserWidget( licenses=LICENSE_CHOICES ) ) sources = MultipleOpenChoiceField( required=False, initial=False, widget=ListWidget( item_label=_("Source") ) ) class Meta: model = FileFilet fields = ['file', 'license_name', 'license_url', 'sources'] widgets = { "license_url": forms.URLInput( attrs={ "style": "width:100%" } ) } class Media: js = [ 'spider_filets/licensechooser.js' ] def __init__(self, request, uc=None, initial=None, **kwargs): if initial is None: initial = {} if not getattr(kwargs.get("instance", None), "id", None): initial.setdefault( "license_name", DEFAULT_LICENSE_FILE(uc, request.user) ) super().__init__(initial=initial, **kwargs) setattr(self.fields['file'], "hashable", True) # sources should not be hashed as they don't affect result setattr(self.fields['sources'], "hashable", False) setattr(self.fields['license_name'], "hashable", True) setattr(self.fields['license_url'], "hashable", True) if request.user.is_superuser: # no upload limit pass elif request.user.is_staff: self.fields["file"].max_length = getattr( settings, "MAX_FILE_SIZE_STAFF", None ) else: self.fields["file"].max_length = getattr( settings, "MAX_FILE_SIZE", None ) if request.is_owner: # self.user = request.user return self.fields["file"].editable = False self.fields["name"].editable = False def clean(self): ret = super().clean() if "file" not in ret: return ret # has to raise ValidationError get_settings_func( "UPLOAD_FILTER_FUNC", "spkcspider.apps.spider.functions.allow_all_filter" )(ret["file"]) return ret class TextForm(forms.ModelForm): text = SanitizedHtmlField(localize=True) license_name = OpenChoiceField( label=_("License"), help_text=_("Select license"), choices=map(_extract_choice, LICENSE_CHOICES.items()), widget=LicenseChooserWidget(licenses=LICENSE_CHOICES) ) sources = MultipleOpenChoiceField( required=False, initial=False, widget=ListWidget( item_label=_("Source") ) ) class Meta: model = TextFilet fields = [ 'text', 'push', 'editable_from', 'license_name', 'license_url', 'sources' ] widgets = { "editable_from": Select2Widget( allow_multiple_selected=True, attrs={ "style": "min-width: 150px; width:100%" } ), "license_url": forms.URLInput( attrs={ "style": "width:100%" } ) } class Media: js = [ 'spider_filets/licensechooser.js', 'spider_filets/description_helper.js' ] def __init__(self, request, source, scope, initial=None, **kwargs): if initial is None: initial = {} if not getattr(kwargs.get("instance", None), "id", None): initial.setdefault( "license_name", DEFAULT_LICENSE_TEXT(source, request.user) ) super().__init__(initial=initial, **kwargs) if scope in ("add", "update"): self.fields["editable_from"].help_text = \ _( "Allow editing from selected components. " "Requires protection strength >=%s." ) % settings.MIN_STRENGTH_EVELATION query = models.Q(pk=self.instance.associated.usercomponent.pk) if scope == "update": query |= models.Q( contents__references=self.instance.associated ) query &= models.Q(strength__gte=settings.MIN_STRENGTH_EVELATION) query &= models.Q(strength__lt=9) self.fields["editable_from"].queryset = \ self.fields["editable_from"].queryset.filter(query).distinct() return del self.fields["editable_from"] del self.fields["push"] self.fields["license_name"].editable = False self.fields["license_url"].editable = False allow_edit = scope == "update_guest" self.fields["text"].editable = allow_edit # sources stay enabled self.fields["sources"].editable = allow_edit class RawTextForm(forms.ModelForm): name = forms.CharField() sources = MultipleOpenChoiceField( required=False, initial=False ) class Meta: model = TextFilet fields = ['text', 'license_name', 'license_url', 'sources'] def __init__(self, request, source=None, scope=None, **kwargs): super().__init__(**kwargs) self.fields['name'].initial = self.instance.associated.name setattr(self.fields['name'], "hashable", True) setattr(self.fields['text'], "hashable", True) # sources should not be hashed as they don't affect result setattr(self.fields['sources'], "hashable", False) setattr(self.fields['license_name'], "hashable", True) setattr(self.fields['license_url'], "hashable", True) PK!}Q=spkcspider/apps/spider_filets/locale/de/LC_MESSAGES/django.po# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-03 00:50+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: spkcspider/apps/spider_filets/forms.py:113 #, python-format msgid "" "Allow editing from selected components. Requires protection strength >=%s." msgstr "" #: spkcspider/apps/spider_filets/models.py:123 msgid "Upload File" msgstr "" #: spkcspider/apps/spider_filets/models.py:128 msgid "Update File" msgstr "" #: spkcspider/apps/spider_filets/models.py:152 msgid "Allow editing from selected components." msgstr "" #: spkcspider/apps/spider_filets/models.py:159 msgid "" "How many words from start should be used for search, seo, search machine " "preview? (tags are stripped)" msgstr "" #: spkcspider/apps/spider_filets/models.py:221 #, python-format msgid "Update \"%s\" (guest)" msgstr "" #: spkcspider/apps/spider_filets/templates/spider_filets/file.html:3 msgid "Download" msgstr "" #: spkcspider/apps/spider_filets/templates/spider_filets/file_form.html:2 msgid "If the file is too big, the connection is resetted" msgstr "" #: spkcspider/apps/spider_filets/templates/spider_filets/text.html:7 msgid "Update" msgstr "" PK!z!8spkcspider/apps/spider_filets/migrations/0001_initial.py# Generated by Django 2.1 on 2018-08-10 00:44 from django.db import migrations, models import spkcspider.apps.spider_filets.models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='FileFilet', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('add', models.DateTimeField(auto_now_add=True)), ('modified', models.DateTimeField(auto_now=True)), ('name', models.CharField(max_length=255)), ('file', models.FileField(upload_to=spkcspider.apps.spider_filets.models.get_file_path)), ], options={ 'abstract': False, 'default_permissions': [], }, ), migrations.CreateModel( name='TextFilet', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('name', models.CharField(max_length=255)), ('text', models.TextField(default='')), ], options={ 'abstract': False, 'default_permissions': [], }, ), ] PK!@~Jspkcspider/apps/spider_filets/migrations/0002_textfilet_non_public_edit.py# Generated by Django 2.1 on 2018-08-10 14:33 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0001_initial'), ] operations = [ migrations.AddField( model_name='textfilet', name='non_public_edit', field=models.BooleanField(default=False, help_text='Allow others to edit text file if not public'), ), ] PK!Cspkcspider/apps/spider_filets/migrations/0003_auto_20180820_1409.py# Generated by Django 2.1 on 2018-08-20 14:09 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_base', '0001_initial'), ('spider_filets', '0002_textfilet_non_public_edit'), ] operations = [ migrations.RemoveField( model_name='textfilet', name='non_public_edit', ), migrations.AddField( model_name='textfilet', name='editable_from', field=models.ManyToManyField(help_text='Allow editing from selected componentsby privileged users.', related_name='_textfilet_editable_from_+', to='spider_base.UserComponent'), ), ] PK! Cspkcspider/apps/spider_filets/migrations/0004_auto_20180821_0021.py# Generated by Django 2.1 on 2018-08-21 00:21 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0003_auto_20180820_1409'), ] operations = [ migrations.AlterField( model_name='textfilet', name='editable_from', field=models.ManyToManyField(blank=True, help_text='Allow editing from selected components.', related_name='_textfilet_editable_from_+', to='spider_base.UserComponent'), ), ] PK!0ϧCspkcspider/apps/spider_filets/migrations/0005_auto_20180919_1329.py# Generated by Django 2.1.1 on 2018-09-19 13:29 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0004_auto_20180821_0021'), ] operations = [ migrations.RemoveField( model_name='filefilet', name='add', ), migrations.RemoveField( model_name='filefilet', name='modified', ), ] PK!3ŬHspkcspider/apps/spider_filets/migrations/0006_textfilet_preview_words.py# Generated by Django 2.1.2 on 2018-10-07 18:31 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0005_auto_20180919_1329'), ] operations = [ migrations.AddField( model_name='textfilet', name='preview_words', field=models.PositiveIntegerField(default=10, help_text='How many words should be used for preview?'), ), ] PK!Cspkcspider/apps/spider_filets/migrations/0007_auto_20181016_2002.py# Generated by Django 2.1.2 on 2018-10-16 20:02 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0006_textfilet_preview_words'), ] operations = [ migrations.AlterField( model_name='textfilet', name='preview_words', field=models.PositiveIntegerField(default=0, help_text='How many words from start should be used for search, seo, search machine preview? (tags are stripped)'), ), ] PK!Q83Cspkcspider/apps/spider_filets/migrations/0008_auto_20181121_0749.py# Generated by Django 2.1.3 on 2018-11-21 07:49 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0007_auto_20181016_2002'), ] operations = [ migrations.AlterField( model_name='textfilet', name='text', field=models.TextField(blank=True, default=''), ), ] PK!d?spkcspider/apps/spider_filets/migrations/0009_textfilet_push.py# Generated by Django 2.1.5 on 2019-01-09 19:27 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0008_auto_20181121_0749'), ] operations = [ migrations.AddField( model_name='textfilet', name='push', field=models.BooleanField(blank=True, default=False, help_text='Improve ranking of this content.'), ), ] PK!#  Cspkcspider/apps/spider_filets/migrations/0010_auto_20190403_0417.py# Generated by Django 2.2 on 2019-04-03 04:17 from django.db import migrations, models import jsonfield.fields class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0009_textfilet_push'), ] operations = [ migrations.AddField( model_name='filefilet', name='license', field=models.TextField(default='', blank=True), ), migrations.AddField( model_name='filefilet', name='license_name', field=models.CharField(default='other', max_length=255), ), migrations.AddField( model_name='filefilet', name='sources', field=jsonfield.fields.JSONField(default=list, blank=True), ), migrations.AddField( model_name='textfilet', name='license', field=models.TextField(default='', blank=True), ), migrations.AddField( model_name='textfilet', name='license_name', field=models.CharField(default='other', max_length=255), ), migrations.AddField( model_name='textfilet', name='sources', field=jsonfield.fields.JSONField(default=list, blank=True), ), ] PK!Y:&&Cspkcspider/apps/spider_filets/migrations/0011_auto_20190405_0045.py# Generated by Django 2.2 on 2019-04-05 00:45 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0010_auto_20190403_0417'), ] operations = [ migrations.RemoveField( model_name='filefilet', name='name', ), migrations.RemoveField( model_name='textfilet', name='name', ), migrations.RemoveField( model_name='textfilet', name='preview_words', ), ] PK!d//Cspkcspider/apps/spider_filets/migrations/0012_auto_20190419_0800.py# Generated by Django 2.2 on 2019-04-19 08:00 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_filets', '0011_auto_20190405_0045'), ] operations = [ migrations.RemoveField( model_name='filefilet', name='license', ), migrations.RemoveField( model_name='textfilet', name='license', ), migrations.AddField( model_name='filefilet', name='license_url', field=models.URLField(blank=True, max_length=400, null=True), ), migrations.AddField( model_name='textfilet', name='license_url', field=models.URLField(blank=True, max_length=400, null=True), ), ] PK!4spkcspider/apps/spider_filets/migrations/__init__.pyPK!!!'spkcspider/apps/spider_filets/models.py import logging import posixpath from django.utils.html import escape from django.db import models from django.urls import reverse from django.conf import settings from django.template.loader import render_to_string from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext from django.http import HttpResponseRedirect from django.core.files.storage import default_storage from jsonfield import JSONField from ranged_response import RangedFileResponse from spkcspider.apps.spider.contents import BaseContent, add_content from spkcspider.apps.spider.conf import ( image_extensions, media_extensions ) from spkcspider.apps.spider.helpers import ( create_b64_token, prepare_description ) from .conf import ( LICENSE_CHOICES ) logger = logging.getLogger(__name__) # Create your models here. def get_file_path(instance, filename): ret = getattr(settings, "FILE_FILET_DIR", "file_filet") size = getattr(settings, "FILE_FILET_SALT_SIZE", 45) # try 100 times to find free filename # but should not take more than 1 try # IMPORTANT: strip . to prevent creation of htaccess files or similar for _i in range(0, 100): ret_path = default_storage.generate_filename( posixpath.join( ret, str(instance.associated.usercomponent.user.pk), create_b64_token(size), filename.lstrip(".") ) ) if not default_storage.exists(ret_path): break else: raise Exception("Unlikely event: no free filename") return ret_path class ContentWithLicense(BaseContent): license_name = models.CharField( max_length=255, null=False, default="other" ) license_url = models.URLField(max_length=400, blank=True, null=True) sources = JSONField(default=list, blank=True) license_name_translation_list = LICENSE_CHOICES class Meta(BaseContent.Meta): abstract = True @property def full_license_name(self): return self.license_name_translation_list.get( self.license_name, {} ).get("name", self.license_name) def get_size(self): s = super().get_size() s += len(str(self.sources)) return s @add_content class FileFilet(ContentWithLicense): expose_name = True expose_description = True appearances = [{"name": "File"}] file = models.FileField(upload_to=get_file_path, null=False, blank=False) def get_template_name(self, scope): if scope in ["add", "update"]: return 'spider_filets/file_form.html' return 'spider_base/view_form.html' def get_size(self): return self.file.size + super().get_size() def get_content_name(self): # in case no name is set return posixpath.basename(self.file.name) def get_info(self): ret = super().get_info() return "%sname=%s\x1e" % (ret, self.associated.name) def get_form(self, scope): from .forms import FileForm return FileForm def get_abilities(self, context): return set(("download",)) def get_form_kwargs(self, **kwargs): ret = super().get_form_kwargs(**kwargs) ret["request"] = kwargs["request"] ret["uc"] = self.associated.usercomponent return ret def render_form(self, **kwargs): kwargs["enctype"] = "multipart/form-data" return super().render_form(**kwargs) def access_view(self, **kwargs): kwargs["object"] = self kwargs["associated"] = self.associated kwargs["type"] = None split = self.associated.name.rsplit(".", 1) if len(split) == 2: extension = split[1].lower() if extension in image_extensions: kwargs["type"] = "image" elif extension in media_extensions: kwargs["type"] = "media" if getattr(settings, "FILE_DIRECT_DOWNLOAD", False): kwargs["download"] = self.file.url else: kwargs["download"] = "{}?{}".format( reverse( 'spider_base:ucontent-access', kwargs={ "token": self.associated.token, "access": 'download' } ), kwargs["spider_GET"].urlencode() ) return ( render_to_string( "spider_filets/file.html", request=kwargs["request"], context=kwargs ), "" ) def access_download(self, **kwargs): if getattr(settings, "FILE_DIRECT_DOWNLOAD", False): response = HttpResponseRedirect( self.file.url, ) else: response = RangedFileResponse( kwargs["request"], self.file.file, content_type='application/octet-stream' ) name = self.associated.name if "." not in name: # use ending of saved file ext = self.file.name.rsplit(".", 1) if len(ext) > 1: name = "%s.%s" % (name, ext[1]) # name is sanitized to not contain \n, and other ugly control chars response['Content-Disposition'] = \ 'attachment; filename="%s"' % posixpath.basename(name.replace( r'"', r'\"' )) response["Access-Control-Allow-Origin"] = "*" return response def access_add(self, **kwargs): _ = gettext kwargs["legend"] = escape(_("Upload File")) return super().access_add(**kwargs) def access_update(self, **kwargs): _ = gettext kwargs["legend"] = escape(_("Update File")) return super().access_update(**kwargs) def save(self, *args, **kw): if self.pk is not None: orig = FileFilet.objects.get(pk=self.pk) if orig.file != self.file: orig.file.delete(False) super().save(*args, **kw) @add_content class TextFilet(ContentWithLicense): expose_name = "force" expose_description = True appearances = [{"name": "Text"}] editable_from = models.ManyToManyField( "spider_base.UserComponent", related_name="+", help_text=_("Allow editing from selected components."), blank=True ) push = models.BooleanField( blank=True, default=False, help_text=_("Improve ranking of this content.") ) text = models.TextField(default="", blank=True) def get_priority(self): # push to top if self.push: return 1 return 0 def get_template_name(self, scope): # view update form if scope == "update_guest": return 'spider_base/edit_form.html' elif scope == "view": return 'spider_base/text.html' return super().get_template_name(scope) def get_content_description(self): # use javascript instead # currently dead code return " ".join( prepare_description( self.text, 51 )[:50] ) def get_info(self): ret = super().get_info() return "%sname=%s\x1e" % ( ret, self.associated.name ) def get_size(self): return len(self.text.encode("utf8")) + super().get_size() def get_form(self, scope): if scope in ("raw", "export", "list"): from .forms import RawTextForm as f else: from .forms import TextForm as f return f def get_form_kwargs(self, **kwargs): ret = super().get_form_kwargs(**kwargs) ret["request"] = kwargs["request"] ret["source"] = kwargs.get("source", self.associated.usercomponent) ret["scope"] = kwargs["scope"] return ret def get_abilities(self, context): _abilities = set() source = context.get("source", self.associated.usercomponent) if self.id and self.editable_from.filter( pk=source.pk ).exists(): _abilities.add("update_guest") return _abilities def access_update_guest(self, **kwargs): kwargs["legend"] = \ escape(_("Update \"%s\" (guest)") % self.__str__()) kwargs["inner_form"] = False return self.access_update(**kwargs) def access_view(self, **kwargs): kwargs["object"] = self kwargs["content"] = self.associated return ( render_to_string( "spider_filets/text.html", request=kwargs["request"], context=kwargs ), "" ) PK!rttHspkcspider/apps/spider_filets/static/spider_filets/description_helper.js document.addEventListener("DOMContentLoaded", function(){ let source_element = $("#id_text"); let dest_element = document.getElementById("id_content_control-description"); let modify_dest = false; /* last char will be replaced by …*/ let max_length = dest_element.attributes.maxlength.value; let val = source_element.html().replace(/<\/?[^>]+(>|$)/g, " ").replace(/\ +/g, " ").trim().substr(0,max_length); if (dest_element.value == "" || dest_element.value.substr(0,max_length)==val){ modify_dest = true; dest_element.value = val; } dest_element.addEventListener("input", function(event){ val = source_element.html().replace(/<\/?[^>]+(>|$)/g, " ").replace(/\ +/g, " ").trim().substr(0,max_length); /* || dest_element.value.substr(0,500)==val*/ if(event.target.value==""){ modify_dest = true; dest_element.value = val } else { modify_dest = false; } }); source_element.on("tbwchange", function(event){ if (modify_dest){ dest_element.value = event.target.value.replace(/<\/?[^>]+(>|$)/g, " ").replace(/\ +/g, " ").trim().substr(0,max_length); } }); }); PK!(~mmDspkcspider/apps/spider_filets/static/spider_filets/licensechooser.js document.addEventListener("DOMContentLoaded", function(){ let el = $("#id_license_name"); let license_urls = {} try{ license_urls = JSON.parse(el.attr("license_urls")); } catch(e){ console.log(e); } let target = document.getElementById("id_license_url"); if(el.val() in license_urls){ try{ target.value = license_urls[el.val()]; } catch(e){ console.log(e); } } el.on("change", function(event){ if(event.target.value in license_urls){ try{ target.value = license_urls[event.target.value]; } catch(e){ console.log(e); } } }) }) PK!?spkcspider/apps/spider_filets/templates/spider_filets/file.html{% load i18n %}
{% if type == "image" %} {{object.name}} {% elif type == "media" %} {% else %} {% endif %} {% include "spider_filets/license_info.html" %}
{% trans "Download" %}
PK!quDspkcspider/apps/spider_filets/templates/spider_filets/file_form.html{% load i18n %}
{% trans "If the file is too big, the connection is resetted" %}
{% include "spider_base/partials/base_form.html" %} PK!9Gspkcspider/apps/spider_filets/templates/spider_filets/license_info.html{% load i18n static %}

{% trans 'License' %}: {{object.full_license_name}}


{{object.full_license_name}}: {% if object.license_url %}{{object.license_url}}{% else %}–{% endif %}
{% if object.sources %}

{% trans 'Sources' %}

    {% for val in object.sources %}
  • {{val}}
  • {% empty %} {% endfor %}
{% endif %}
PK!?spkcspider/apps/spider_filets/templates/spider_filets/text.html{% load i18n %}
{{object.text|safe}}
{% include "spider_filets/license_info.html" %} {% if "update_guest" in abilities %} {% endif %} PK!A훑(spkcspider/apps/spider_filets/widgets.py__all__ = [ "LicenseChooserWidget" ] import json # from django.forms import widgets from django.conf import settings # from django.utils.translation import gettext_lazy as _ from spkcspider.apps.spider.widgets import OpenChoiceWidget _extra = '' if settings.DEBUG else '.min' class LicenseChooserWidget(OpenChoiceWidget): licenses = None def __init__(self, licenses, allow_multiple_selected=False, **kwargs): self.licenses = licenses super().__init__( allow_multiple_selected=allow_multiple_selected, **kwargs ) def build_attrs(self, base_attrs, extra_attrs=None): """ add license_urls to attrs.""" ret = super().build_attrs(base_attrs, extra_attrs) d = dict(map(lambda x: (x[0], x[1]["url"]), self.licenses.items())) ret["license_urls"] = json.dumps(d) # ret.setdefault("style", "color:black;") return ret PK!0II'spkcspider/apps/spider_keys/__init__.pydefault_app_config = 'spkcspider.apps.spider_keys.apps.SpiderKeysConfig' PK!??$spkcspider/apps/spider_keys/admin.pyfrom django.contrib import admin # Register your models here. PK!!˟  #spkcspider/apps/spider_keys/apps.py__all__ = ["SpiderKeysConfig"] from django.apps import AppConfig class SpiderKeysConfig(AppConfig): name = 'spkcspider.apps.spider_keys' label = 'spider_keys' spider_url_path = 'spiderkeys/' verbose_name = 'spkcspider keys, identifiers and anchors' PK!>$spkcspider/apps/spider_keys/forms.py__all__ = ["KeyForm", "AnchorServerForm", "AnchorKeyForm"] # "AnchorGovForm" import binascii from django import forms from django.db import models from django.conf import settings from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext from cryptography import exceptions from cryptography.x509 import load_pem_x509_certificate from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.backends import default_backend from spkcspider.apps.spider.fields import MultipleOpenChoiceField from spkcspider.apps.spider.widgets import ListWidget from .models import PublicKey, AnchorServer, AnchorKey class KeyForm(forms.ModelForm): class Meta: model = PublicKey fields = ['key'] def __init__(self, **kwargs): super().__init__(**kwargs) setattr(self.fields['key'], "hashable", True) def clean_key(self): _ = gettext data = self.cleaned_data['key'].strip() if data == "": raise forms.ValidationError( _('Empty Key'), code="empty" ) if "PRIVATE" in data.upper(): raise forms.ValidationError( _('Private Key') ) return data class AnchorServerForm(forms.ModelForm): identifier = forms.CharField(disabled=True) anchor_type = forms.CharField(disabled=True, initial="url") setattr(identifier, "hashable", True) scope = "" old_urls = MultipleOpenChoiceField( widget=ListWidget( format_type="url", item_label=_("Url to superseded anchor") ), required=False ) class Meta: model = AnchorServer fields = ["new_url", "old_urls"] def __init__(self, scope, **kwargs): self.scope = scope super().__init__(**kwargs) if self.scope == "add": del self.fields["identifier"] del self.fields["anchor_type"] del self.fields["new_url"] def clean_old_urls(self): values = self.cleaned_data["old_urls"] if not isinstance(values, list): raise forms.ValidationError( _("Invalid format"), code='invalid_format' ) return values def clean(self): _ = gettext ret = super().clean() if ret.get("new_url", None) and ret.get("old_urls", []): raise forms.ValidationError( _( "Specify either replacement url or " "urls to superseding anchors" ), code='invalid_choices' ) return ret class AnchorKeyForm(forms.ModelForm): identifier = forms.CharField(disabled=True) anchor_type = forms.CharField(disabled=True, initial="signature") scope = "" class Meta: model = AnchorKey fields = ['key', 'signature'] field_order = ['identifier', 'signature', 'key'] def __init__(self, scope, **kwargs): self.scope = scope super().__init__(**kwargs) if self.scope == "add": del self.fields["identifier"] del self.fields["signature"] del self.fields["anchor_type"] # self.fields["signature"].disabled = True # self.fields["signature"].required = False if self.scope in ("add", "update"): self.fields["key"].queryset = self.fields["key"].queryset.filter( models.Q(key__contains="-----BEGIN CERTIFICATE-----") | models.Q(key__contains="-----BEGIN PUBLIC KEY-----") ) elif self.scope in ("raw", "list", "view"): self.fields["key"] = forms.CharField( initial=self.instance.key.key, widget=forms.TextArea ) setattr(self.fields['key'], "hashable", True) def clean(self): _ = gettext ret = super().clean() try: if "-----BEGIN CERTIFICATE-----" in self.cleaned_data["key"].key: pubkey = load_pem_x509_certificate( ret["key"].key.encode("utf-8"), default_backend() ).public_key() else: pubkey = serialization.load_pem_public_key( ret["key"].key.encode("utf-8"), default_backend() ) except exceptions.UnsupportedAlgorithm: self.add_error("key", forms.ValidationError( _("key not usable for signing"), code="unusable_key" )) if self.scope == "add": ret["signature"] = "" return ret chosen_hash = settings.SPIDER_HASH_ALGORITHM try: pubkey.verify( binascii.unhexlify(ret["signature"]), ret["identifier"].encode("utf-8"), padding.PSS( mgf=padding.MGF1(chosen_hash), salt_length=padding.PSS.MAX_LENGTH ), chosen_hash ) except exceptions.InvalidSignature: self.add_error("signature", forms.ValidationError( _("signature incorrect"), code="incorrect_signature" )) except (binascii.Error, KeyError, ValueError): self.add_error("signature", forms.ValidationError( _("signature malformed or missing"), code="malformed_signature" )) return ret # class AnchorGovForm(forms.ModelForm): # identifier = forms.CharField(disabled=True, widget=forms.HiddenInput()) # anchor_type = forms.CharField(disabled=True, initial="gov") # scope = "" # # class Meta: # model = AnchorGov # fields = ['idtype', 'token'] # # def __init__(self, scope, **kwargs): # self.scope = scope # super().__init__(**kwargs) # # def clean(self): # self.cleaned_data["type"] = "gov" # self.cleaned_data["identifier"] = self.instance.get_identifier() # self.cleaned_data["format"] = "gov_{verifier}_{token}" # if self.scope not in ["add", "update"]: # self.cleaned_data["verifier"] = ID_VERIFIERS[self.idtype] # del self.cleaned_data["idtype"] # return self.cleaned_data PK!u ~~;spkcspider/apps/spider_keys/locale/de/LC_MESSAGES/django.po# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-03 00:50+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: spkcspider/apps/spider_keys/forms.py:29 msgid "Empty Key" msgstr "" #: spkcspider/apps/spider_keys/forms.py:34 #: spkcspider/apps/spider_keys/models.py:37 msgid "Private Key" msgstr "" #: spkcspider/apps/spider_keys/forms.py:83 msgid "key not usable for signing" msgstr "" #: spkcspider/apps/spider_keys/forms.py:107 msgid "signature incorrect" msgstr "" #: spkcspider/apps/spider_keys/models.py:25 msgid "Signature of Identifier" msgstr "" #: spkcspider/apps/spider_keys/models.py:27 msgid "\"Public Key\"-Content" msgstr "" #: spkcspider/apps/spider_keys/models.py:39 msgid "Not trimmed" msgstr "" #: spkcspider/apps/spider_keys/models.py:41 msgid "Not a key" msgstr "" #: spkcspider/apps/spider_keys/models.py:57 msgctxt "content name" msgid "Public Key" msgstr "" PK!+6spkcspider/apps/spider_keys/migrations/0001_initial.py# Generated by Django 2.1 on 2018-08-10 00:34 from django.db import migrations, models import spkcspider.apps.spider_keys.models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='PublicKey', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('key', models.TextField(validators=[spkcspider.apps.spider_keys.models.valid_pkey_properties])), ('note', models.TextField(blank=True, default='', max_length=100)), ], options={ 'abstract': False, 'default_permissions': [], }, ), ] PK!f5Espkcspider/apps/spider_keys/migrations/0002_0002_20190426_squashed.py# Generated by Django 2.2 on 2019-04-25 22:07 from django.db import migrations, models import django.db.models.deletion import jsonfield.fields import spkcspider.apps.spider_keys.models class Migration(migrations.Migration): replaces = [('spider_keys', '0002_anchorkey_anchorserver'), ('spider_keys', '0003_auto_20190130_1137'), ('spider_keys', '0004_auto_20190221_2253'), ('spider_keys', '0005_remove_publickey_note'), ('spider_keys', '0006_auto_20190425_2159')] dependencies = [ ('spider_keys', '0001_initial'), ] operations = [ migrations.CreateModel( name='AnchorServer', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('new_url', models.URLField(blank=True, help_text='Url to new anchor (in case this one is superseded)', max_length=400, null=True)), ('old_urls', jsonfield.fields.JSONField(blank=True, default=list, help_text='Superseded anchor urls')), ], options={ 'abstract': False, 'default_permissions': [], }, ), migrations.AlterField( model_name='publickey', name='key', field=models.TextField(help_text='It is recommended to use different keysfor signing and encryption', validators=[spkcspider.apps.spider_keys.models.valid_pkey_properties]), ), migrations.RemoveField( model_name='publickey', name='note', ), migrations.CreateModel( name='AnchorKey', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('signature', models.CharField(help_text='Signature of Identifier (hexadecimal-encoded)', max_length=1024)), ('key', models.OneToOneField(help_text='"Public Key"-Content for signing identifier. It is recommended to use different keys for signing and encryption.', on_delete=django.db.models.deletion.CASCADE, related_name='anchorkey', to='spider_keys.PublicKey')), ], options={ 'abstract': False, 'default_permissions': [], }, ), ] PK!f~SSEspkcspider/apps/spider_keys/migrations/0002_anchorkey_anchorserver.py# Generated by Django 2.1 on 2018-08-24 04:30 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ('spider_keys', '0001_initial'), ] operations = [ migrations.CreateModel( name='AnchorServer', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ], options={ 'abstract': False, 'default_permissions': [], }, ), migrations.CreateModel( name='AnchorKey', fields=[ ('anchorserver_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='spider_keys.AnchorServer')), ('signature', models.CharField(help_text="""Signature of Identifier (hexadecimal-encoded)""", max_length=1024)), ('key', models.ForeignKey(help_text=""""Public Key"-Content""", on_delete=django.db.models.deletion.CASCADE, related_name='+', to='spider_keys.PublicKey')), ], options={ 'abstract': False, 'default_permissions': [], }, bases=('spider_keys.anchorserver',), ), ] PK!aA::Aspkcspider/apps/spider_keys/migrations/0003_auto_20190130_1137.py# Generated by Django 2.1.5 on 2019-01-30 11:37 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ('spider_keys', '0002_anchorkey_anchorserver'), ] operations = [ migrations.AddField( model_name='publickey', name='use_for_encryption', field=models.BooleanField(blank=True, default=True, help_text='Use for encryption not for signing.'), ), migrations.AlterField( model_name='anchorkey', name='key', field=models.OneToOneField(help_text='"Public Key"-Content', limit_choices_to={'use_for_encryption': False}, on_delete=django.db.models.deletion.CASCADE, related_name='anchorkey', to='spider_keys.PublicKey'), ), ] PK!8==Aspkcspider/apps/spider_keys/migrations/0004_auto_20190221_2253.py# Generated by Django 2.1.7 on 2019-02-21 22:53 from django.db import migrations, models import django.db.models.deletion import spkcspider.apps.spider_keys.models class Migration(migrations.Migration): dependencies = [ ('spider_keys', '0003_auto_20190130_1137'), ] operations = [ migrations.RemoveField( model_name='publickey', name='use_for_encryption', ), migrations.AlterField( model_name='anchorkey', name='key', field=models.OneToOneField(help_text='"Public Key"-Content for signing identifier. It is recommended to use different keys for signing and encryption.', on_delete=django.db.models.deletion.CASCADE, related_name='anchorkey', to='spider_keys.PublicKey'), ), migrations.AlterField( model_name='publickey', name='key', field=models.TextField(help_text='It is recommended to use different keysfor signing and encryption', validators=[spkcspider.apps.spider_keys.models.valid_pkey_properties]), ), ] PK!KKDspkcspider/apps/spider_keys/migrations/0005_remove_publickey_note.py# Generated by Django 2.2 on 2019-04-05 00:45 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('spider_keys', '0004_auto_20190221_2253'), ] operations = [ migrations.RemoveField( model_name='publickey', name='note', ), ] PK!x!  Aspkcspider/apps/spider_keys/migrations/0006_auto_20190425_2159.py# Generated by Django 2.2 on 2019-04-25 21:59 from django.db import migrations, models import django.db.models.deletion import jsonfield.fields class Migration(migrations.Migration): dependencies = [ ('spider_keys', '0005_remove_publickey_note'), ] operations = [ migrations.DeleteModel( name='AnchorKey', ), migrations.CreateModel( name='AnchorKey', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('signature', models.CharField(help_text='Signature of Identifier (hexadecimal-encoded)', max_length=1024)), ('key', models.OneToOneField(help_text='"Public Key"-Content for signing identifier. It is recommended to use different keys for signing and encryption.', on_delete=django.db.models.deletion.CASCADE, related_name='anchorkey', to='spider_keys.PublicKey')), ], options={ 'abstract': False, 'default_permissions': [], }, ), migrations.AddField( model_name='anchorserver', name='new_url', field=models.URLField(blank=True, help_text='Url to new anchor (in case this one is superseded)', max_length=400, null=True), ), migrations.AddField( model_name='anchorserver', name='old_urls', field=jsonfield.fields.JSONField(blank=True, default=list, help_text='Superseded anchor urls'), ), ] PK!2spkcspider/apps/spider_keys/migrations/__init__.pyPK! 267"7"%spkcspider/apps/spider_keys/models.py import logging from urllib.parse import urljoin from django.db import models from django.conf import settings from django.urls import reverse from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext from django.core.exceptions import ValidationError from django.template.loader import render_to_string from django.utils.translation import pgettext from django.http import HttpResponseRedirect, JsonResponse from jsonfield import JSONField from spkcspider.apps.spider.helpers import get_hashob from spkcspider.apps.spider.contents import BaseContent, add_content from spkcspider.apps.spider.constants import VariantType from spkcspider.apps.spider.conf import get_anchor_domain logger = logging.getLogger(__name__) # Create your models here. _htest = get_hashob() _htest.update(b"test") _help_text_sig = _("""Signature of Identifier (hexadecimal-encoded)""") _help_text_key = _(""""Public Key"-Content for signing identifier. It is recommended to use different keys for signing and encryption.""") # noqa ID_VERIFIERS = { } def valid_pkey_properties(key): _ = gettext if "PRIVAT" in key.upper(): raise ValidationError(_('Private Key')) if key.strip() != key: raise ValidationError(_('Not trimmed')) if len(key) < 100: raise ValidationError(_('Not a key')) @add_content class PublicKey(BaseContent): expose_name = False expose_description = True appearances = [ {"name": "PublicKey", "ctype": VariantType.unique.value} ] key = models.TextField( editable=True, validators=[valid_pkey_properties], help_text=_( "It is recommended to use different keys" "for signing and encryption" ) ) @classmethod def localize_name(cls, name): return pgettext("content name", "Public Key") def get_size(self): s = super().get_size() s += len(self.key) return s def get_info(self): ret = super().get_info() key = self.get_key_name()[0] h = get_hashob() h.update(key.encode("ascii", "ignore")) return "%shash=%s=%s\x1e" % ( ret, settings.SPIDER_HASH_ALGORITHM.name, h.finalize().hex() ) def get_key_name(self): # PEM key split = self.key.split("\n") if len(split) > 1: h = get_hashob() h.update(self.key.encode("ascii", "ignore")) return (h.finalize().hex(), None) # ssh key split = self.key.rsplit(" ", 1) if len(split) == 2 and "@" in split[1]: return split # other key return (self.key, None) def get_content_name(self): st = self.get_key_name() if st[1]: st = st[1] else: st = "{}...".format(st[0][:10]) if len(self.associated.description) > 0: st = "{}: {}".format(st, self.associated.description[:20]) return "Key: {}".format(st) def get_form(self, scope): from .forms import KeyForm return KeyForm def access_view(self, **kwargs): kwargs["object"] = self kwargs["algo"] = settings.SPIDER_HASH_ALGORITHM.name kwargs["hash"] = self.associated.getlist( "hash:%s" % settings.SPIDER_HASH_ALGORITHM.name, 1 )[0] return ( render_to_string( "spider_keys/key.html", request=kwargs["request"], context=kwargs ), "" ) # Anchors can ONLY be used for server content. # Clients have to be verified seperately # 1 To verify client use this trick: # requesting party redirects user to server, with a return address in GET # the server generates a secret # the user authenticates and redirects to the url plus hash of secret # the server sends secret via post to requesting party # the requesting party can verify that both: client and server are connected # this means: anchors are valid for this client # or 2: generate a token # or 3: use oauth # advantage of 1,2: simple # implementation: if requester in GET, create token and # redirect after auth to requester with hash of token # post token to requester # TODO: make hash algorithm configurable class AnchorBase(BaseContent): expose_name = False expose_description = True class Meta(BaseContent.Meta): abstract = True def get_abilities(self, context): return {"anchor"} def get_priority(self): return -10 def get_form_kwargs(self, **kwargs): ret = super().get_form_kwargs(**kwargs) ret.setdefault("initial", {}) ret["initial"]["identifier"] = self.get_identifier(kwargs["request"]) ret["scope"] = kwargs["scope"] return ret def get_identifier(self, request): """ returns id of content, server """ # security: id can only be faked by own server # this should never happen, except with admin access to server if not self.associated.id: return None ret = urljoin( "{}://{}".format(request.scheme, get_anchor_domain()), reverse( "spider_keys:anchor-permanent", kwargs={"pk": self.associated.id} ) ) return ret @add_content class AnchorServer(AnchorBase): """ identify by server """ appearances = [ { "name": "AnchorServer", "ctype": VariantType.anchor.value, "strength": 0 } ] new_url = models.URLField( max_length=400, blank=True, null=True, help_text=_( "Url to new anchor (in case this one is superseded)" ) ) old_urls = JSONField( default=list, blank=True, help_text=_( "Superseded anchor urls" ) ) def get_size(self): s = super().get_size() s += 400 s += len(str(self.old_urls)) return s def get_form(self, scope): from .forms import AnchorServerForm return AnchorServerForm def get_content_name(self): return "Anchor: {}".format(self.associated.id) def access_anchor(self, **kwargs): if self.new_url: ret = HttpResponseRedirect(location=self.new_url) else: ret = JsonResponse(self.old_urls) ret["Access-Control-Allow-Origin"] = "*" return ret @add_content class AnchorKey(AnchorBase): """ domain name of pc, signed """ expose_name = False expose_description = True appearances = [ { "name": "AnchorKey", "ctype": VariantType.anchor+VariantType.unique, "strength": 0 } ] key = models.OneToOneField( PublicKey, on_delete=models.CASCADE, related_name="anchorkey", help_text=_help_text_key ) signature = models.CharField( max_length=1024, help_text=_help_text_sig, null=False ) def get_size(self): s = super().get_size() s += 1024 return s def get_content_name(self): st = self.key.get_key_name() if st[1]: st = st[1] else: st = "{}...".format(st[0][:10]) if len(self.key.associated.description) > 0: st = "{}: {}".format(st, self.key.associated.description[:20]) return st def get_form(self, scope): from .forms import AnchorKeyForm return AnchorKeyForm def access_anchor(self, **kwargs): ret = JsonResponse([]) ret["Access-Control-Allow-Origin"] = "*" return ret def get_info(self): ret = super().get_info() key = self.key.get_key_name()[0] h = get_hashob() h.update(key.encode("ascii", "ignore")) return "%shash=%s=%s\x1e" % ( ret, settings.SPIDER_HASH_ALGORITHM.name, h.finalize().hex() ) # TODO: implement # @add_content class AnchorLink(AnchorBase): """ Anchor by Organisation, e.g. government, verifier returns token pointing to url """ class Meta(AnchorBase.Meta): abstract = True verified_by = models.URLField(max_length=400) appearances = [ { "name": "AnchorLink", "ctype": VariantType.anchor + VariantType.unique, "strength": 10 } ] def access_anchor(self, **kwargs): ret = JsonResponse(self.verified_by) ret["Access-Control-Allow-Origin"] = "*" return ret def get_form(self, scope): raise NotImplementedError def get_info(self): return "{}verified_by={}\x1e".format( super().get_info(unique=True, unlisted=False), self.verified_by ) PK!(,,:spkcspider/apps/spider_keys/templates/spider_keys/key.html

Key & hash

Hash ({{algo}}) {{hash}}
key {{object.key}}
PK!\JA#spkcspider/apps/spider_keys/urls.pyfrom django.urls import path from .views import PermAnchorView app_name = "spider_keys" urlpatterns = [ path( 'anchor//view/', PermAnchorView.as_view(), name='anchor-permanent' ) ] PK!LA$spkcspider/apps/spider_keys/views.py__all__ = ["PermAnchorView"] from urllib.parse import urljoin from django.http.response import HttpResponseRedirect, HttpResponseBase from django.views.generic.detail import DetailView from spkcspider.apps.spider.conf import get_anchor_domain from spkcspider.apps.spider.models import AssignedContent class PermAnchorView(DetailView): queryset = AssignedContent.objects.filter( info__contains="\x1eanchor\x1e" ) def get(self, request, *args, **kwargs): if request.get_host() != get_anchor_domain(): return HttpResponseRedirect( location=urljoin( get_anchor_domain(), request.get_full_path() ) ) return super().get(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs["scope"] = "anchor" return super().get_context_data(**kwargs) def render_to_response(self, context): ret = self.object.content.access(context) assert(isinstance(ret, HttpResponseBase)) return ret PK!?,II'spkcspider/apps/spider_tags/__init__.pydefault_app_config = 'spkcspider.apps.spider_tags.apps.SpiderTagsConfig' PK!)$spkcspider/apps/spider_tags/admin.pyfrom django.contrib import admin # Register your models here. from .models import ( TagLayout ) from .forms import TagLayoutAdminForm @admin.register(TagLayout) class TagLayoutAdmin(admin.ModelAdmin): form = TagLayoutAdminForm PK!aO#spkcspider/apps/spider_tags/apps.py__all__ = ["SpiderTagsConfig"] from django.apps import AppConfig, apps from .signals import UpdateDefaultLayouts from spkcspider.apps.spider.helpers import extract_app_dicts from spkcspider.apps.spider.signals import update_dynamic class SpiderTagsConfig(AppConfig): name = 'spkcspider.apps.spider_tags' label = 'spider_tags' spider_url_path = 'spidertags/' verbose_name = 'spkcspider tags' def ready(self): from .fields import installed_fields for app in apps.get_app_configs(): installed_fields.update( extract_app_dicts(app, "spider_tag_fields") ) update_dynamic.connect( UpdateDefaultLayouts, dispatch_uid="update_default_layouts" ) PK!_ %spkcspider/apps/spider_tags/fields.py__all__ = ["installed_fields", "generate_fields"] import logging import posixpath from django import forms from django.apps import apps from django.utils.translation import gettext, gettext_lazy from spkcspider.apps.spider.helpers import add_by_field from spkcspider.apps.spider.models import TravelProtection from spkcspider.apps.spider.widgets import ( SubSectionStartWidget, SubSectionStopWidget ) installed_fields = {} safe_default_fields = [ "BooleanField", "CharField", "ChoiceField", "MultipleChoiceField", "DateField", "DateTimeField", "DecimalField", "DurationField", "EmailField", "FilePathField", "FloatField", "GenericIPAddressField", "ModelChoiceField", "ModelMultipleChoiceField", "SlugField", "TimeField", "URLField" ] for i in safe_default_fields: installed_fields[i] = getattr(forms, i) @add_by_field(installed_fields, "__name__") class TextareaField(forms.CharField): widget = forms.Textarea # extra attributes for fields: # limit_to_usercomponent = ": limit field name to associated uc # limit_to_user = ": limit field name to user of associated uc def localized_choices(obj): def func(*, choices=(), **kwargs): choices = map(lambda x: (x[0], gettext(x[1])), choices) return obj(choices=choices, **kwargs) return func installed_fields["LocalizedChoiceField"] = localized_choices(forms.ChoiceField) installed_fields["MultipleLocalizedChoiceField"] = \ localized_choices(forms.MultipleChoiceField) @add_by_field(installed_fields, "__name__") class UserContentRefField(forms.ModelChoiceField): filter_strength_link = "associated_rel__usercomponent__strength__lte" # limit_to_uc: limit to usercomponent, if False to user # True is strongly recommended to prevent info leak gadgets def __init__(self, modelname, limit_to_uc=True, **kwargs): from spkcspider.apps.spider.contents import BaseContent if limit_to_uc: self.filters_usercomponent = ( "associated_rel__usercomponent", "associated_rel__referenced_by__usercomponent" ) else: self.filters_user = ("associated_rel__usercomponent__user",) model = apps.get_model( modelname ) if not issubclass(model, BaseContent): raise Exception("Not a content (inherit from BaseContent)") travel = TravelProtection.objects.get_active() kwargs["queryset"] = model.objects.filter( **kwargs.pop("limit_choices_to", {}) ).exclude( associated_rel__usercomponent__travel_protected__in=travel ) super().__init__(**kwargs) def tagdata_from_value(self, obj): if obj: return obj.pk return None def label_from_instance(self, obj): return str(obj) @add_by_field(installed_fields, "__name__") class MultipleUserContentRefField(forms.ModelMultipleChoiceField): filter_strength_link = "associated_rel__usercomponent__strength__lte" # limit_to_uc: limit to usercomponent, if False to user # True is strongly recommended to prevent info leak gadgets def __init__(self, modelname, limit_to_uc=True, **kwargs): from spkcspider.apps.spider.contents import BaseContent if limit_to_uc: self.filters_usercomponent = ( "associated_rel__usercomponent", "associated_rel__referenced_by__usercomponent" ) else: self.filters_user = ("associated_rel__usercomponent__user",) model = apps.get_model( modelname ) if not issubclass(model, BaseContent): raise Exception("Not a content (inherit from BaseContent)") travel = TravelProtection.objects.get_active() kwargs["queryset"] = model.objects.filter( **kwargs.pop("limit_choices_to", {}) ).exclude( associated_rel__usercomponent__travel_protected__in=travel ) super().__init__(**kwargs) def tagdata_from_value(self, query): return list(query.values_list( 'pk', flat=True )) def label_from_instance(self, obj): return str(obj) @add_by_field(installed_fields, "__name__") class AnchorField(forms.ModelChoiceField): spkc_use_uriref = True use_default_anchor = None filter_strength_link = "usercomponent__strength__lte" # limit_to_uc: limit to usercomponent, if False to user def __init__( self, use_default_anchor=True, limit_to_uc=True, **kwargs ): from spkcspider.apps.spider.models import AssignedContent _ = gettext_lazy if limit_to_uc: self.filters_usercomponent = ("usercomponent",) else: self.filters_user = ("usercomponent__user",) self.use_default_anchor = use_default_anchor if use_default_anchor: kwargs.setdefault("required", False) if use_default_anchor and not kwargs.get("empty_label", None): kwargs["empty_label"] = _("(Use default anchor)") travel = TravelProtection.objects.get_active() # can also use Links to anchor as anchor kwargs["queryset"] = AssignedContent.objects.filter( info__contains="\x1eanchor\x1e", **kwargs.pop("limit_choices_to", {}) ).exclude( usercomponent__travel_protected__in=travel ) super().__init__(**kwargs) def to_python(self, value): if value in self.empty_values: return None try: key = self.to_field_name or 'pk' value = self.queryset.get(**{key: value}).content except (ValueError, TypeError, self.queryset.model.DoesNotExist): raise forms.ValidationError( self.error_messages['invalid_choice'], code='invalid_choice' ) return value def tagdata_from_value(self, obj): if obj: return obj.associated.id return None def label_from_instance(self, obj): return str(obj.content) class StartSub(forms.Field): widget = SubSectionStartWidget hashable = False def __init__(self, **kwargs): super().__init__(**kwargs) self.widget.label = self.label class StopSub(forms.Field): widget = SubSectionStopWidget hashable = False def generate_fields(layout, prefix="", _base=None, _mainprefix=None): if _base is None: _base = [] _mainprefix = prefix for i in layout: item = i.copy() item.setdefault("required", False) try: key, field = item.pop("key", None), item.pop("field", None) except Exception: logging.warning("Invalid item (no dict)", i) continue localize = item.pop("localize", False) nonhashable = item.pop("nonhashable", False) if not item.get("label"): item["label"] = key.replace(_mainprefix, "", 1) if localize: item["label"] = gettext(item["label"]) if "help_text" in item: item["help_text"] = gettext(item["help_text"]) if not key or "/" in key: logging.warning("Invalid item (no key/contains /)", i) continue if isinstance(field, list): new_prefix = posixpath.join(prefix, key) item["required"] = False item["initial"] = None # by prefixing with _ invalidate prefix for tag recognition _base.append(( "_{}_start".format(new_prefix), StartSub(**item) )) generate_fields( field, new_prefix, _base=_base, _mainprefix=_mainprefix ) # by prefixing with _ invalidate prefix for tag recognition _base.append(( "_{}_stop".format(new_prefix), StopSub(**item) )) elif isinstance(field, str): new_field = installed_fields.get(field, None) if not new_field: logging.warning("Invalid field specified: %s", field) else: new_field = new_field(**item) setattr(new_field, "hashable", not nonhashable) _base.append((posixpath.join(prefix, key), new_field)) else: logging.warning("Invalid item", i) return _base PK!W͟++$spkcspider/apps/spider_tags/forms.py__all__ = [ "TagLayoutForm", "TagLayoutAdminForm", "SpiderTagForm", "generate_form", ] import json import posixpath from collections import OrderedDict # from django.utils.translation import gettext_lazy as _ from django.conf import settings from django import forms # from django.apps import apps from django.db.models import Q, QuerySet from django.contrib.contenttypes.models import ContentType from django.core.exceptions import NON_FIELD_ERRORS from django.utils.translation import gettext_lazy as _ from rdflib import XSD from .fields import generate_fields from .models import TagLayout, SpiderTag from spkcspider.apps.spider.fields import MultipleOpenChoiceField, JsonField from spkcspider.apps.spider.widgets import ListWidget, OpenChoiceWidget from spkcspider.apps.spider.helpers import merge_get_url from spkcspider.apps.spider.models import ( AssignedContent, ReferrerObject ) from spkcspider.apps.spider.contents import BaseContent from .widgets import SchemeWidget from .fields import installed_fields # don't spam set objects _empty_set = frozenset() _extra = '' if settings.DEBUG else '.min' class TagLayoutForm(forms.ModelForm): layout = JsonField( widget=SchemeWidget( attrs={ "field_types": json.dumps(list(installed_fields.keys())) } ) ) default_verifiers = MultipleOpenChoiceField( widget=ListWidget( format_type="url", item_label=_("Url to Verifier") ), required=False ) class Meta: model = TagLayout fields = ["name", "unique", "layout", "default_verifiers"] usertag = None def _save_m2m(self): self.instance.usertag = self.usertag self.instance.save() self.instance.usertag.refresh_from_db() return super()._save_m2m() def clean_default_verifiers(self): values = self.cleaned_data["default_verifiers"] if not isinstance(values, list): raise forms.ValidationError( _("Invalid format"), code='invalid_format' ) values = list(map(merge_get_url, values)) return values def save(self, commit=True): self.usertag = self.instance.usertag if commit: self.usertag.save() self._save_m2m() else: self.save_m2m = self._save_m2m return self.usertag class TagLayoutAdminForm(forms.ModelForm): layout = JsonField( widget=SchemeWidget( attrs={ "field_types": json.dumps(list(installed_fields.keys())) } ) ) default_verifiers = MultipleOpenChoiceField( widget=ListWidget( format_type="url", item_label=_("Url to Verifier") ), required=False ) class Meta: model = TagLayout fields = ["name", "unique", "layout", "default_verifiers", "usertag"] class Media: css = { 'all': [ 'node_modules/@fortawesome/fontawesome-free/css/all.min.css' ] } def clean_default_verifiers(self): values = self.cleaned_data["default_verifiers"] if not isinstance(values, list): raise forms.ValidationError( _("Invalid format"), code='invalid_format' ) values = list(map(merge_get_url, values)) return values class SpiderTagForm(forms.ModelForm): updateable_by = MultipleOpenChoiceField( required=False, initial=False, widget=ListWidget( format_type="url" ) ) layout = forms.ModelChoiceField( queryset=TagLayout.objects.none(), to_field_name="name" ) class Meta: model = SpiderTag fields = ["layout", "updateable_by"] def __init__(self, user=None, **kwargs): super().__init__(**kwargs) index = user.usercomponent_set.get(name="index") self.fields["layout"].queryset = TagLayout.objects.filter( Q(usertag__isnull=True) | Q(usertag__associated_rel__usercomponent=index) ).order_by("name") def clean_updateable_by(self): values = self.cleaned_data["updateable_by"] values = set(map(merge_get_url, values)) existing = ReferrerObject.objects.filter( url__in=values ).values_list("url", flat=True) for url in values.difference(existing): # extra validation ReferrerObject.objects.get_or_create( url=url ) return ReferrerObject.objects.filter( url__in=values ) def generate_form(name, layout): _gen_fields = generate_fields(layout, "tag") _temp_field = forms.BooleanField(required=False, initial=False) setattr(_temp_field, "hashable", True) _gen_fields.insert(0, ( "primary", _temp_field )) _temp_field = MultipleOpenChoiceField( required=False, initial=False, widget=OpenChoiceWidget( allow_multiple_selected=True, attrs={ "style": "min-width: 300px; width:100%" } ) ) setattr(_temp_field, "spkc_datatype", XSD.anyURI) _gen_fields.append(("updateable_by", _temp_field)) _temp_field = MultipleOpenChoiceField( required=False, initial=False, widget=ListWidget( format_type="url", item_label=_("Url to Verifier") ) ) setattr(_temp_field, "spkc_datatype", XSD.anyURI) _gen_fields.append(("verified_by", _temp_field)) class _form(forms.BaseForm): __name__ = name declared_fields = OrderedDict(_gen_fields) base_fields = declared_fields # used in models layout_generating_form = True class Meta: error_messages = { NON_FIELD_ERRORS: { 'unique_together': _( 'Primary layout for "%s" exists already' ) % name } } def __init__(self, instance, *, uc=None, initial=None, **kwargs): if not initial: initial = {} self.instance = instance _initial = self.encode_initial(initial) _initial["primary"] = getattr(instance, "primary", False) _initial["verified_by"] = getattr(instance, "verified_by", []) super().__init__( initial=_initial, **kwargs ) for field in self.fields.values(): if hasattr(field, "queryset"): filters = {} q_user = Q() q_uc = Q() # can also contain __lte or so attr = getattr(field, "filter_strength_link", None) if attr: filters[attr] = uc.strength attrs = getattr(field, "filters_user", _empty_set) if attrs: for i in attrs: q_user |= Q(**{i: uc.user}) attrs = getattr(field, "filters_usercomponent", _empty_set) if attrs: for i in attrs: q_uc |= Q(**{i: uc}) field.queryset = field.queryset.filter( q_user, q_uc, **filters ) def clean_updateable_by(self): values = self.cleaned_data["updateable_by"] values = set(map(merge_get_url, values)) existing = ReferrerObject.objects.filter( url__in=values ).values_list("url", flat=True) for url in values.difference(existing): # extra validation ReferrerObject.objects.get_or_create( url=url ) return ReferrerObject.objects.filter( url__in=values ) def clean(self): super().clean() _cached_references = [] for key, value in self.cleaned_data.items(): if key in ("verified_by", "updateable_by", "primary"): continue # e.g. anchors if isinstance(value, AssignedContent): _cached_references.append(value) if issubclass(type(value), BaseContent): _cached_references.append(value.associated) # e.g. anchors if isinstance(value, QuerySet): if issubclass(value.model, AssignedContent): _cached_references += list(value) if issubclass(value.model, BaseContent): _cached_references += list( AssignedContent.objects.filter( object_id__in=value.values_list( "id", flat=True ), content_type=ContentType.objects.get_for_model( value.model ) ) ) self.instance._cached_references = _cached_references self.instance.full_clean() return self.cleaned_data @classmethod def encode_initial(cls, initial, prefix="tag", base=None): if base is None: base = {} for i in initial.items(): if isinstance(i[1], dict): new_prefix = posixpath.join(prefix, i[0]) cls.encode_initial(i[1], prefix=new_prefix, base=base) else: base[posixpath.join(prefix, i[0])] = i[1] return base def encode_data(self, cleaned_data, prefix="tag"): ret = {} for counter, i in enumerate(cleaned_data.items()): if not i[0].startswith(prefix): # unrelated data continue selected_dict = ret splitted = i[0].split("/") # last key is item key, first is "tag" for key in splitted[1:-1]: if key not in selected_dict: selected_dict[key] = {} selected_dict = selected_dict[key] if hasattr(self.fields[i[0]], "tagdata_from_value"): selected_dict[splitted[-1]] = \ self.fields[i[0]].tagdata_from_value(i[1]) else: selected_dict[splitted[-1]] = i[1] return ret def save_m2m(self): pass def save(self, commit=True): self.instance.primary = self.cleaned_data["primary"] # self.instance.verified_by = self.cleaned_data["verified_by"] # self.instance.updateable_by = self.cleaned_data["updateable_by"] self.instance.tagdata = self.encode_data(self.cleaned_data) if commit: self.instance.save() return self.instance return _form PK!s4l!l!&spkcspider/apps/spider_tags/layouts.py__all__ = ("default_layouts", "initialize_layouts") from django.utils.translation import gettext_noop as _ from spkcspider.apps.spider.helpers import extract_app_dicts default_layouts = {} default_layouts["address"] = { "description": _( "An universal address.\n" "Can be used for persons and companies." ), "layout": [ { "key": "name", "localize": True, "label": _("Name"), "field": "CharField", "max_length": 512 }, { "key": "place", "localize": True, "label": _("Street/Place"), "field": "CharField", "max_length": 512 }, { "key": "street_number", "localize": True, "label": _("Street Number"), "field": "CharField", "max_length": 10, "required": False }, { "key": "city", "localize": True, "label": _("City"), "field": "CharField", "max_length": 256 }, { "key": "post_code", "localize": True, "label": _("Post Code"), "field": "CharField", "max_length": 20, "required": False }, { "key": "country_area", "localize": True, "label": _("Country Area"), "field": "CharField", "max_length": 256, "required": False }, { "key": "country_code", "localize": True, "label": _("Country Code"), "field": "CharField", "min_length": 2, "max_length": 3 } ] } default_layouts["person_official"] = { "description": _( "An description of a person.\n" "For e.g. shopping or contracts" ), "layout": [ { "key": "address", "label": _("Address"), "localize": True, "field": "UserContentRefField", "modelname": "spider_tags.SpiderTag", "limit_choices_to": { "layout__name__in": ["address"] }, }, { "key": "title", "label": _("Titles"), "localize": True, "field": "CharField", "max_length": 20, "required": False }, { "key": "first_name", "label": _("First Name"), "localize": True, "field": "CharField", "max_length": 255 }, { "key": "extra_names", "label": _("Extra Name"), "localize": True, "field": "CharField", "max_length": 255, "required": False }, { "key": "last_name", "label": _("Last Name"), "localize": True, "field": "CharField", "max_length": 255, "required": False }, { "key": "iddocs", "label": _("ID Documents"), "localize": True, "field": "MultipleUserContentRefField", "modelname": "spider_filets.FileFilet", "required": False }, { "key": "anchor", "field": "AnchorField", "required": False } ] } default_layouts["work"] = { "description": _( "Basic specifications of a work." ), "layout": [ { "key": "worker", "label": _("Worker"), "localize": True, "field": "UserContentRefField", "modelname": "spider_tags.SpiderTag", "limit_choices_to": { "layout__name__in": ["person_official"] }, }, { "key": "address", "label": _("Address"), "localize": True, "field": "UserContentRefField", "modelname": "spider_tags.SpiderTag", "limit_choices_to": { "layout__name__in": ["address"] }, }, ] } default_layouts["emergency"] = { "description": _( "Emergency information." ), "layout": [ { "key": "address", "label": _("Address"), "localize": True, "field": "UserContentRefField", "modelname": "spider_tags.SpiderTag", "limit_choices_to": { "layout__name__in": ["address"] }, "required": False }, { "key": "contacts", "label": _("Contacts"), "localize": True, "initial": "", "field": "TextareaField", "required": False }, { "key": "bgender", "label": _("Biologic Gender"), "localize": True, "field": "LocalizedChoiceField", "choices": [ ("", ""), ("male", _("male")), ("female", _("female")), ("other", _("other")), ], "required": False }, { "key": "bloodgroup", "label": _("Blood Group"), "localize": True, "field": "ChoiceField", "choices": [ ("", ""), ("A", "A"), ("B", "B"), ("AB", "AB"), ("0", "0"), ], "required": False }, { "key": "rhesus_factor", "label": _("Rhesus Factor"), "localize": True, "field": "ChoiceField", "choices": [ ("", ""), ("rh+", "Rh+"), ("rh-", "Rh-"), ], "required": False }, { "key": "health_problems", "label": _("Health Problems"), "localize": True, "field": "MultipleLocalizedChoiceField", "choices": [ ("heart", _("Heart")), ("diabetes", _("Diabetes")), ("lung", _("Lung")), ("dementia", _("Dementia")), ("social", _("Social")), ("phobia", _("Phobias")), ], "required": False }, { "key": "organ_donations", "label": _("Organ donations in case of death"), "localize": True, "field": "LocalizedChoiceField", "choices": [ ("", ""), ("all", _("All organs")), ("special", _("Special Organs")), ("contacts", _("Contacts can decide")), ("none", _("None")), ], "required": False }, { "key": "organs", "localize": True, "label": _('In case of: "Special Organs", which?'), "field": "TextareaField", "required": False } ] } default_layouts["license"] = { "description": _( "License." ), "layout": [ { "key": "license_name", "label": _("Name"), "localize": True, "field": "CharField", "required": True }, { "key": "license_body", "label": _("License"), "localize": True, "initial": "", "field": "TextareaField", "required": True }, ] } def initialize_layouts(apps=None): if not apps: from django.apps import apps TagLayout = apps.get_model("spider_tags", "TagLayout") layouts = default_layouts.copy() # create union from all layouts for app in apps.get_app_configs(): layouts.update( extract_app_dicts(app, "spider_tag_layouts") ) # iterate over unionized layouts for name, layout_dic in layouts.items(): tag_layout = TagLayout.objects.get_or_create( defaults=layout_dic, name=name )[0] has_changed = False # check attributes of model for layout_attr, value in layout_dic.items(): if getattr(tag_layout, layout_attr) != value: # layout change detected, update and mark for saving setattr(tag_layout, layout_attr, value) has_changed = True if has_changed: tag_layout.save() PK!22;spkcspider/apps/spider_tags/locale/de/LC_MESSAGES/django.po# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-03 00:50+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: spkcspider/apps/spider_tags/forms.py:81 #, python-format msgid "Primary layout for \"%s\" exists already" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:12 msgid "Name" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:19 msgid "Street/Place" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:26 msgid "Street Number" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:34 msgid "City" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:41 msgid "Post Code" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:49 msgid "Country Area" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:57 msgid "Country Code" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:69 msgid "Address" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:79 msgid "Titles" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:87 msgid "First Name" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:94 msgid "Extra Name" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:102 msgid "Last Name" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:110 msgid "ID Documents" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:128 msgid "Adress Object" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:139 msgid "Contacts" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:147 msgid "Biologic Gender" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:152 msgid "male" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:153 msgid "female" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:154 msgid "other" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:160 msgid "Blood Group" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:174 msgid "Rhesus Factor" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:186 msgid "Health Problems" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:190 msgid "Heart" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:191 msgid "Diabetes" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:192 msgid "Lung" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:193 msgid "Dementia" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:194 msgid "Social" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:195 msgid "Phobias" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:201 msgid "Organ donations in case of death" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:206 msgid "All organs" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:207 msgid "Special Organs" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:208 msgid "Contacts can decide" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:209 msgid "None" msgstr "" #: spkcspider/apps/spider_tags/layouts.py:216 msgid "In case of: \"Special Organs\", which?" msgstr "" #: spkcspider/apps/spider_tags/models.py:38 msgid "Layout exists already" msgstr "" #: spkcspider/apps/spider_tags/templates/spider_tags/edit_form.html:5 #: spkcspider/apps/spider_tags/templates/spider_tags/view_form.html:4 msgid "List of verifiers, where the tag was verified" msgstr "" PK!{6spkcspider/apps/spider_tags/migrations/0001_initial.py# Generated by Django 2.1 on 2018-08-11 12:10 from django.db import migrations, models import django.db.models.deletion import jsonfield.fields class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='SpiderTag', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('tagdata', jsonfield.fields.JSONField(default=dict)), ('verified_by', jsonfield.fields.JSONField(default=list)), ('primary', models.BooleanField(default=False)), ], options={ 'abstract': False, 'default_permissions': [], }, ), migrations.CreateModel( name='TagLayout', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.SlugField(max_length=255)), ('layout', jsonfield.fields.JSONField(default=list)), ('default_verifiers', jsonfield.fields.JSONField(default=list)), ], ), migrations.CreateModel( name='UserTagLayout', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ], options={ 'abstract': False, 'default_permissions': [], }, ), migrations.AddField( model_name='taglayout', name='usertag', field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='layout', to='spider_tags.UserTagLayout'), ), migrations.AddField( model_name='spidertag', name='layout', field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='tags', to='spider_tags.TagLayout'), ), migrations.AlterUniqueTogether( name='taglayout', unique_together={('name', 'usertag')}, ), ] PK!nAspkcspider/apps/spider_tags/migrations/0002_auto_20180815_1128.py# Generated by Django 2.1 on 2018-08-15 11:28 from django.db import migrations import jsonfield.fields class Migration(migrations.Migration): dependencies = [ ('spider_tags', '0001_initial'), ] operations = [ migrations.AlterField( model_name='taglayout', name='default_verifiers', field=jsonfield.fields.JSONField(blank=True, default=list), ), ] PK!ƼAspkcspider/apps/spider_tags/migrations/0003_auto_20180815_1205.py# Generated by Django 2.1 on 2018-08-15 12:05 from django.db import migrations, models import jsonfield.fields class Migration(migrations.Migration): dependencies = [ ('spider_tags', '0002_auto_20180815_1128'), ] operations = [ migrations.AlterField( model_name='spidertag', name='primary', field=models.BooleanField(blank=True, default=False), ), migrations.AlterField( model_name='spidertag', name='tagdata', field=jsonfield.fields.JSONField(blank=True, default=dict), ), migrations.AlterField( model_name='spidertag', name='verified_by', field=jsonfield.fields.JSONField(blank=True, default=list), ), ] PK!g#zzAspkcspider/apps/spider_tags/migrations/0004_auto_20190117_1253.py# Generated by Django 2.1.5 on 2019-01-17 12:53 from django.db import migrations, models import jsonfield.fields class Migration(migrations.Migration): dependencies = [ ('spider_tags', '0003_auto_20180815_1205'), ] operations = [ migrations.AddField( model_name='taglayout', name='unique', field=models.BooleanField(blank=True, default=False), ), migrations.AlterField( model_name='taglayout', name='layout', field=jsonfield.fields.JSONField(default=list, help_text='Field list in JSON format'), ), ] PK! Fspkcspider/apps/spider_tags/migrations/0005_spidertag_updateable_by.py# Generated by Django 2.1.5 on 2019-02-11 20:15 from django.db import migrations import jsonfield.fields class Migration(migrations.Migration): dependencies = [ ('spider_tags', '0004_auto_20190117_1253'), ] operations = [ migrations.AddField( model_name='spidertag', name='updateable_by', field=jsonfield.fields.JSONField(blank=True, default=list), ), ] PK!qqAspkcspider/apps/spider_tags/migrations/0006_auto_20190226_1853.py# Generated by Django 2.1.7 on 2019-02-26 18:53 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_base', '0008_auto_20190225_2034'), ('spider_tags', '0005_spidertag_updateable_by'), ] operations = [ migrations.RemoveField( model_name='spidertag', name='updateable_by', ), migrations.AddField( model_name='spidertag', name='updateable_by', field=models.ManyToManyField(blank=True, related_name='tags', to='spider_base.ReferrerObject'), ), ] PK!r /Dspkcspider/apps/spider_tags/migrations/0007_taglayout_description.py# Generated by Django 2.2 on 2019-04-14 14:53 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_tags', '0006_auto_20190226_1853'), ] operations = [ migrations.AddField( model_name='taglayout', name='description', field=models.TextField(blank=True, default=''), ), ] PK!2spkcspider/apps/spider_tags/migrations/__init__.pyPK!Clٌ33%spkcspider/apps/spider_tags/models.py import logging from django.utils.html import escape from django.db import models from django.utils.translation import gettext, gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt from django.core.exceptions import ValidationError from django.contrib.contenttypes.models import ContentType from django.urls import reverse from django.http import HttpResponse from rdflib import URIRef from jsonfield import JSONField from spkcspider.apps.spider.contents import ( BaseContent, add_content, VariantType, ActionUrl ) from spkcspider.apps.spider.helpers import get_settings_func from spkcspider.apps.spider.models import AssignedContent logger = logging.getLogger(__name__) try: from lru import LRU CACHE_FORMS = LRU(256) except Exception: logger.warning("LRU dict failed, use dict instead", exc_info=True) CACHE_FORMS = {} # Create your models here. class TagLayout(models.Model): name = models.SlugField(max_length=255, null=False) # shall it be unique for a component? unique = models.BooleanField(default=False, blank=True) layout = JSONField( default=list, help_text=_("Field list in JSON format") ) default_verifiers = JSONField(default=list, blank=True) usertag = models.OneToOneField( "spider_tags.UserTagLayout", on_delete=models.CASCADE, related_name="layout", null=True, blank=True ) description = models.TextField(default="", blank=True) class Meta(object): unique_together = [ ("name", "usertag") ] def get_description(self): if self.usertag: return self.usertag.associated.description return self.description def full_clean(self, **kwargs): # checked with clean kwargs.setdefault("exclude", []).append("usertag") return super().full_clean(**kwargs) def clean(self): if self.usertag: if TagLayout.objects.filter(usertag=None, name=self.name).exists(): raise ValidationError( _("Layout exists already"), code="unique" ) self.usertag.full_clean(exclude=["layout"]) def get_form(self): compkey = (self.name, self.usertag.pk if self.usertag else None) form = CACHE_FORMS.get(compkey) if not form: from .forms import generate_form form = generate_form("LayoutForm", self.layout) CACHE_FORMS[compkey] = form return form def __repr__(self): if self.usertag: return "" % ( self.name, self.usertag.associated.usercomponent.user ) return "" % self.name def __str__(self): if self.usertag: return "TagLayout: %s:%s" % ( self.name, self.usertag.associated.usercomponent.user ) return "TagLayout: %s" % self.name def save(self, *args, **kwargs): if self.pk: # invalidate forms old = TagLayout.objects.get(pk=self.pk) _id = self.usertag.pk if self.usertag else None try: del CACHE_FORMS[old.name, _id] except KeyError: pass return super().save(*args, **kwargs) @add_content class UserTagLayout(BaseContent): # 10 is required for preventing info leak gadgets via component auth appearances = [ { "name": "TagLayout", "ctype": VariantType.unique.value, "strength": 10 } ] expose_name = False expose_description = True def get_content_name(self): return "%s: %s" % ( self.layout.name, self.associated.usercomponent.username ) def localized_description(self): """ localize and perform other transforms before rendering to user """ return gettext(self.associated.description) def get_size(self): s = super().get_size() s += len(str(self.layout.default_verifiers)) s += len(str(self.layout.layout)) return s def get_template_name(self, scope): if scope == "view": return 'spider_tags/edit_preview_form.html' return super().get_template_name(scope) def get_strength_link(self): # never allow links to this, elsewise with links is an information # disclosure possible return 11 def get_info(self): return "%slayout=%s\x1e" % ( super().get_info(), self.layout.name ) def get_form(self, scope): if scope in {"add", "update"}: from .forms import TagLayoutForm return TagLayoutForm else: return self.layout.get_form() def access_view(self, **kwargs): _ = gettext kwargs.setdefault( "legend", escape(_("Check \"%s\"") % self.__str__()) ) # not visible by default kwargs.setdefault("confirm", _("Check")) # prevent second button kwargs.setdefault("inner_form", False) return super().access_view(**kwargs) def access_add(self, **kwargs): if not hasattr(self, "layout"): self.layout = TagLayout(usertag=self) return super().access_add(**kwargs) def get_form_kwargs(self, **kwargs): kwargs["instance"] = self.layout return super().get_form_kwargs(**kwargs) def access(self, context): if context["scope"] == "view": context["extra_outer_forms"] = ["request_verification_form"] return super().access(context) @add_content class SpiderTag(BaseContent): _cached_references = None tmp_primary_anchor = None appearances = [ { "name": "SpiderTag", "strength": 0, "ctype": VariantType.domain_mode }, { "name": "PushedTag", "strength": 2, "ctype": VariantType.component_feature + VariantType.domain_mode } ] layout = models.ForeignKey( TagLayout, related_name="tags", on_delete=models.PROTECT, ) tagdata = JSONField(default=dict, blank=True) verified_by = JSONField(default=list, blank=True) updateable_by = models.ManyToManyField( "spider_base.ReferrerObject", related_name="tags", blank=True ) primary = models.BooleanField(default=False, blank=True) expose_name = False expose_description = False def __str__(self): if not self.id: return self.localize_name(self.associated.ctype.name) if not self.layout.usertag: return "%s: <%s>: %s" % ( self.localize_name("Tag"), self.layout.name, self.associated.id ) return "%s: <%s: %s>: %s" % ( self.localize_name("Tag"), self.layout.name, self.layout.id, self.associated.id ) @classmethod def feature_urls(cls, name): return [ ActionUrl(reverse("spider_tags:create-pushtag"), "pushtag") ] def get_content_description(self): return self.layout.get_description() def get_content_name(self): if not self.layout.usertag: return self.layout.name return self.layout.usertag.get_content_name() def get_template_name(self, scope): if scope == "update": return 'spider_tags/edit_form.html' if scope == "push_update": return 'spider_base/edit_form.html' return super().get_template_name(scope) def get_size(self): s = super().get_size() s += len(str(self.verified_by)) s += len(str(self.tagdata)) return s def get_strength_link(self): return 0 def get_abilities(self, context): _abilities = set() if ( context["request"].auth_token and context["request"].auth_token.referrer ): if get_settings_func( "SPIDER_TAG_VERIFIER_VALIDATOR", "spkcspider.apps.spider.functions.clean_verifier" )(self, context["request"]): _abilities.add("verify") if self.updateable_by.filter( id=context["request"].auth_token.referrer.id ).exists(): _abilities.add("push_update") return _abilities def map_data(self, name, field, data, graph, context): if ( field.__class__.__name__ == "AnchorField" and field.use_default_anchor ): if data is None: return URIRef(self.get_primary_anchor(graph, context)) return super().map_data(name, field, data, graph, context) @csrf_exempt def access_verify(self, **kwargs): # full url to result verified = kwargs["request"].POST.get("url", "") if "://" not in verified: return HttpResponse("invalid url", status=400) if verified in self.verified_by: return self.access_view(**kwargs) self.verified_by.append(verified) self.clean() self.save() return self.access_view(**kwargs) def access_push_update(self, **kwargs): kwargs["legend"] = escape( _("Update \"%s\" (push)") % self.__str__() ) kwargs["inner_form"] = False return self.access_update(**kwargs) def access(self, context): if context["scope"] not in {"add", "view"}: context["extra_outer_forms"] = ["request_verification_form"] return super().access(context) def get_form(self, scope): from .forms import SpiderTagForm if scope == "add": return SpiderTagForm else: return self.layout.get_form() def get_references(self): if not getattr(self, "layout", None): return [] if self._cached_references: return self._cached_references _cached_references = [] form = self.layout.get_form()( initial=self.tagdata.copy(), instance=self, uc=self.associated.usercomponent ) attached_to_primary_anchor = False for name, field in form.fields.items(): raw_value = form.initial.get(name, None) value = field.to_python(raw_value) # e.g. anchors if isinstance(value, AssignedContent): _cached_references.append(value) if ( field.__class__.__name__ == "AnchorField" and field.use_default_anchor ): if value is None: attached_to_primary_anchor = True if issubclass(type(value), BaseContent): _cached_references.append(value.associated) # e.g. anchors if isinstance(value, models.QuerySet): if issubclass(value.model, AssignedContent): _cached_references += list(value) if issubclass(value.model, BaseContent): _cached_references += list( AssignedContent.objects.filter( object_id__in=value.values_list( "id", flat=True ), content_type=ContentType.objects.get_for_model( value.model ) ) ) if ( attached_to_primary_anchor and self.associated.usercomponent.primary_anchor ): _cached_references.append( self.associated.usercomponent.primary_anchor ) if ( self.associated.attached_to_primary_anchor != attached_to_primary_anchor ): self.associated.attached_to_primary_anchor = \ attached_to_primary_anchor self.associated.save( update_fields=[ "attached_to_primary_anchor" ] ) self._cached_references = _cached_references return self._cached_references def get_form_kwargs(self, instance=None, **kwargs): if kwargs["scope"] == "add": ret = super().get_form_kwargs( instance=instance, **kwargs ) ret["user"] = self.associated.usercomponent.user else: ret = super().get_form_kwargs( **kwargs ) ret["initial"] = self.tagdata.copy() ret["uc"] = self.associated.usercomponent return ret def encode_verifiers(self): return "".join( map( lambda x: "verified_by={}\x1e".format(x), self.verified_by ) ) def get_info(self): return "{}{}tag={}\x1e".format( super().get_info(unique=self.primary, unlisted=False), self.encode_verifiers(), self.layout.name ) PK!9\&spkcspider/apps/spider_tags/signals.py def UpdateDefaultLayouts(sender, **kwargs): # provided apps argument lacks model function support # so use this from django.apps import apps from .layouts import initialize_layouts initialize_layouts(apps) PK!r&!!?spkcspider/apps/spider_tags/static/spider_tags/scheme_editor.js JSONEditor.plugins.select2.enable = true; document.addEventListener("DOMContentLoaded", function(){ let collection = document.getElementsByClassName("SchemeEditorTarget"); for (let counter=0;counter {% endif %} {% include "spider_base/edit_form.html" %} {% if not inner_form %} {% endif %}
{% for verifier in object.content.layout.default_verifiers %} {% endfor %}
{% trans "Verifier for this type" %}:
PK!ZZHspkcspider/apps/spider_tags/templates/spider_tags/edit_preview_form.html{% load i18n %} {% if not inner_form %}
{% endif %} {% include "spider_base/edit_form.html" %} {% if not inner_form %}
{% endif %}
{% for verifier in object.content.layout.default_verifiers %} {% endfor %}
{% trans "Verifier for this type" %}:
PK!v22#spkcspider/apps/spider_tags/urls.pyfrom django.urls import path from .views import PushTagView app_name = "spider_tags" # uc = UserComponent # name = UserComponent.name # UserComponent.name contains unicode => str urlpatterns = [ path( 'pushtag/create/', PushTagView.as_view(), name='create-pushtag' ) ] PK!G$spkcspider/apps/spider_tags/views.py__all__ = ("PushTagView",) from django.conf import settings from django.urls import reverse from django.http import Http404 from django.shortcuts import get_object_or_404 from django.http import JsonResponse, HttpResponseRedirect from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from django.views.generic.edit import FormView from spkcspider.apps.spider.views import UCTestMixin from spkcspider.apps.spider.helpers import get_settings_func from spkcspider.apps.spider.models import ( AuthToken ) from .models import SpiderTag from .forms import SpiderTagForm class PushTagView(UCTestMixin, FormView): model = SpiderTag form_class = SpiderTagForm variant = None object = None @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): try: return super().dispatch(request, *args, **kwargs) except Http404: return get_settings_func( "RATELIMIT_FUNC", "spkcspider.apps.spider.functions.rate_limit_default" )(self, request) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs["instance"] = self.object kwargs["user"] = self.usercomponent.user return kwargs def get_usercomponent(self): self.request.auth_token = get_object_or_404( AuthToken, token=self.request.GET.get("token", None), referrer__isnull=False ) return self.request.auth_token.usercomponent def test_func(self): self.variant = self.usercomponent.features.filter( name="PushedTag" ).first() # can only access feature if activated return bool(self.variant) def options(self, request, *args, **kwargs): ret = super().options() ret["Access-Control-Allow-Origin"] = "*" ret["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS" return ret def post(self, request, *args, **kwargs): # not yet confirmed self.object = self.model.static_create( token_size=getattr(settings, "TOKEN_SIZE", 30), associated_kwargs={ "usercomponent": self.usercomponent, "ctype": self.variant } ) # return super().post(request, *args, **kwargs) form = self.get_form() if form.is_valid(): return self.form_valid(form) else: return self.form_invalid(form) def render_to_response(self, context): ret = JsonResponse( { "layout": [ i.name for i in context["form"].fields["layout"].queryset.all() ] } ) # allow cors requests for accessing data ret["Access-Control-Allow-Origin"] = \ self.request.auth_token.referrer.host return ret def form_valid(self, form): self.object = form.save(commit=False) self.object.clean() self.object.save() form.save_m2m() self.object.updateable_by.add(self.request.auth_token.referrer) assert(self.object.associated.id) assert(self.object.associated.token) ret = HttpResponseRedirect( redirect_to="{}?token={}".format( reverse( "spider_base:ucontent-access", kwargs={ "token": self.object.associated.token, "access": "push_update" } ), self.request.auth_token.token ) ) ret["Access-Control-Allow-Origin"] = \ self.request.auth_token.referrer.host return ret PK!#y&spkcspider/apps/spider_tags/widgets.py__all__ = ["SchemeWidget"] import json from django.forms import widgets from django.conf import settings _extra = '' if settings.DEBUG else '.min' class SchemeWidget(widgets.Textarea): template_name = 'spider_base/forms/widgets/wrapped_textarea.html' class Media: js = [ 'node_modules/jquery/dist/jquery%s.js' % _extra, 'node_modules/select2/dist/js/select2%s.js' % _extra, 'node_modules/@json-editor/json-editor/dist/jsoneditor%s.js' % _extra, # noqa:E501, 'spider_tags/scheme_editor.js' ] css = { 'all': [ 'node_modules/select2/dist/css/select2%s.css' % _extra ] } def __init__(self, *, attrs=None, wrapper_attrs=None, **kwargs): if not attrs: attrs = {"class": ""} if not wrapper_attrs: wrapper_attrs = {} attrs.setdefault("class", "") attrs["class"] += " SchemeEditorTarget" self.wrapper_attrs = wrapper_attrs.copy() super().__init__(attrs=attrs, **kwargs) def __deepcopy__(self, memo): obj = super().__deepcopy__(memo) obj.wrapper_attrs = self.wrapper_attrs.copy() return obj def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) context['widget']['wrapper_attrs'] = self.wrapper_attrs context['widget']['wrapper_attrs']["id"] = "{}_inner_wrapper".format( context['widget']['attrs']["id"] ) return context def format_value(self, value): if not value: return "[]" if isinstance(value, (tuple, list)): value = json.dumps(value) return str(value) def render(self, name, value, attrs=None, renderer=None): if value is None: value = "" if not isinstance(value, str): value = json.dumps(value, ensure_ascii=False, indent=2) return super().render(name, value, attrs, renderer) PK!F":MM)spkcspider/apps/spider_webcfg/__init__.pydefault_app_config = 'spkcspider.apps.spider_webcfg.apps.SpiderWebCfgConfig' PK!&spkcspider/apps/spider_webcfg/admin.pyPK!5;U1%spkcspider/apps/spider_webcfg/apps.py__all__ = ["SpiderWebCfgConfig"] from django.apps import AppConfig class SpiderWebCfgConfig(AppConfig): name = 'spkcspider.apps.spider_webcfg' label = 'spider_webcfg' spider_url_path = 'webcfg/' verbose_name = 'spkcspider WebConfig' PK!0&spkcspider/apps/spider_webcfg/forms.py__all__ = ["WebConfigForm"] from django import forms from .models import WebConfig class WebConfigForm(forms.ModelForm): creation_url = forms.URLField(disabled=True, required=False) class Meta: model = WebConfig fields = ['config'] def __init__(self, *, scope=None, user=None, **kwargs): super().__init__(**kwargs) self.fields["creation_url"].initial = self.instance.creation_url PK!ifP8spkcspider/apps/spider_webcfg/migrations/0001_initial.py# Generated by Django 2.1.5 on 2019-01-15 02:00 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): initial = True dependencies = [ ('spider_base', '0002_20190127_squashed'), ] operations = [ migrations.CreateModel( name='WebConfig', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('creation_url', models.URLField(editable=False)), ('config', models.TextField(blank=True, default='')), ('token', models.ForeignKey(limit_choices_to={'persist': True}, on_delete=django.db.models.deletion.CASCADE, to='spider_base.AuthToken')), ], options={ 'abstract': False, 'default_permissions': [], }, ), ] PK!VCspkcspider/apps/spider_webcfg/migrations/0002_auto_20190120_1252.py# Generated by Django 2.1.5 on 2019-01-20 12:52 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('spider_webcfg', '0001_initial'), ] operations = [ migrations.RemoveField( model_name='webconfig', name='creation_url', ), migrations.RemoveField( model_name='webconfig', name='token', ), ] PK!4spkcspider/apps/spider_webcfg/migrations/__init__.pyPK!('spkcspider/apps/spider_webcfg/models.py__all__ = ["WebConfig"] from django.db import models from django.urls import reverse from spkcspider.apps.spider.constants import VariantType, ActionUrl from spkcspider.apps.spider.contents import BaseContent, add_content # from spkcspider.apps.spider.models.base import BaseInfoModel @add_content class WebConfig(BaseContent): expose_name = False appearances = [ { "name": "WebConfig", "ctype": ( VariantType.unique + VariantType.component_feature + VariantType.persist ), "strength": 0 }, { "name": "TmpConfig", "ctype": ( VariantType.unique + VariantType.component_feature + VariantType.domain_mode ), "strength": 5 } ] config = models.TextField(default="", blank=True) def get_content_name(self): return self.associated.attached_to_token.referrer.url[:255] @classmethod def feature_urls(cls, name): return [ ActionUrl(reverse("spider_webcfg:webconfig-view"), "webcfg") ] def get_size(self): return super().get_size() + len(self.config) def get_priority(self): # low priority return -10 def get_form_kwargs(self, **kwargs): ret = super().get_form_kwargs(**kwargs) ret["scope"] = kwargs["scope"] ret["user"] = kwargs["request"].user return ret def get_form(self, scope): from .forms import WebConfigForm as f return f def get_info(self): ret = super().get_info(unique=True) return "{}url={}\x1e".format( ret, self.associated.attached_to_token.referrer.url ) PK!#))%spkcspider/apps/spider_webcfg/urls.pyfrom django.urls import path from .views import WebConfigView app_name = "spider_webcfg" # uc = UserComponent # name = UserComponent.name # UserComponent.name contains unicode => str urlpatterns = [ path( '', WebConfigView.as_view(), name='webconfig-view' ) ] PK!ْo&spkcspider/apps/spider_webcfg/views.py__all__ = ("WebConfigView",) from django.conf import settings from django.http import Http404 from django.core.exceptions import ValidationError from django.shortcuts import get_object_or_404 from django.http.response import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from django.views import View from spkcspider.apps.spider.views import UCTestMixin from spkcspider.apps.spider.helpers import get_settings_func from spkcspider.apps.spider.models import ( AuthToken, AssignedContent ) from .models import WebConfig _empty_set = frozenset() class WebConfigView(UCTestMixin, View): model = WebConfig require_persist = True @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): try: return super().dispatch(request, *args, **kwargs) except Http404: return get_settings_func( "RATELIMIT_FUNC", "spkcspider.apps.spider.functions.rate_limit_default" )(self, request) def get_usercomponent(self): token = self.request.GET.get("token", None) if not token: raise Http404() self.request.auth_token = get_object_or_404( AuthToken, token=token ) if not self.request.auth_token.referrer: raise Http404() return self.request.auth_token.usercomponent def test_func(self): return True def get_object(self, queryset=None): variant = self.usercomponent.features.filter( name="WebConfig" ).first() if not variant: variant = self.usercomponent.features.filter( name="TmpConfig" ).first() # can only access feature if activated even WebConfig exists already if not variant: raise Http404() if ( variant == "WebConfig" and "persist" not in self.request.auth_token.extra.get( "intentions", _empty_set ) ): raise Http404() ret = AssignedContent.objects.filter( attached_to_token=self.request.auth_token ).first() if ret: return ret.content ret = self.model.static_create( token_size=getattr(settings, "TOKEN_SIZE", 30), associated_kwargs={ "usercomponent": self.usercomponent, "ctype": variant, "attached_to_token": self.request.auth_token } ) ret.clean() ret.save() assert(ret.associated.token) return ret def options(self, request, *args, **kwargs): ret = super().options() self.object = self.get_object() ret["Access-Control-Allow-Origin"] = self.object.token.referrer.host ret["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS" return ret def get(self, request, *args, **kwargs): self.object = self.get_object() return self.render_to_response(self.object.config) def post(self, request, *args, **kwargs): self.object = self.get_object() old_size = self.object.get_size() oldconfig = self.object.config self.object.config = self.request.body.decode( "ascii", "backslashreplace" ) # a full_clean is here not required self.object.clean() try: self.object.update_used_space(old_size-self.object.get_size()) except ValidationError as exc: return HttpResponse( str(exc), status_code=400 ) self.object.save() return self.render_to_response(oldconfig) def render_to_response(self, config): ret = HttpResponse( config.encode( "ascii", "backslashreplace" ), content_type="text/plain" ) ret["X-SPIDER-URL"] = self.request.auth_token.referrer.url ret["X-SPIDER-MODIFIED"] = self.object.associated.modified ret["X-SPIDER-CREATED"] = self.object.associated.created # allow cors requests for accessing data ret["Access-Control-Allow-Origin"] = \ self.request.auth_token.referrer.host return ret PK! JJ$spkcspider/apps/verifier/__init__.pydefault_app_config = 'spkcspider.apps.verifier.apps.SpiderVerifierConfig' PK!'], , !spkcspider/apps/verifier/admin.py import logging from django.utils.timezone import now from django.contrib import admin from django.contrib.auth import get_permission_codename from .models import DataVerificationTag logger = logging.getLogger(__name__) @admin.register(DataVerificationTag) class DataVerificationTagAdmin(admin.ModelAdmin): view_on_site = True list_display = ('hash', "checked", 'verification_state') search_fields = ('hash', "checked", 'verification_state') fields = [ 'created', 'modified', 'checked', 'dvfile', 'verification_state', 'note' ] readonly_fields = ['created', 'modified', "checked"] def get_form(self, request, obj=None, **kwargs): opts = self.opts codename = get_permission_codename('change', opts) if not request.user.has_perm("%s.%s" % (opts.app_label, codename)): self.readonly_fields = [ 'created', 'modified', 'checked', 'dvfile' ] return super().get_form(request, obj, **kwargs) def has_module_permission(self, request): return True def has_view_permission(self, request, obj=None): opts = self.opts if request.user.has_perm("%s.%s" % (opts.app_label, 'can_verify')): return True return super().has_view_permission(request, obj) def has_change_permission(self, request, obj=None): opts = self.opts if request.user.has_perm("%s.%s" % (opts.app_label, 'can_verify')): return True return super().has_change_permission(request, obj) def save_form(self, request, form, change): if 'verification_state' in form.changed_data: form.instance.checked = now() ret = super().save_form(request, form, change) try: form.instance.callback( "{}://{}".format( request.scheme, request.get_host() ) ) except Exception: logger.exception("Callback failed") return ret # def save_model(self, request, obj, form, change): # """ # Given a model instance save it to the database. # """ # if 'verification_state' in form.changed_data: # obj.checked = now() # ret = super().save_model(request, obj, form, change) # obj.callback() # return ret PK!v;;%spkcspider/apps/verifier/constants.py__all__ = [ "VERIFICATION_CHOICES", "BUFFER_SIZE" ] from django.utils.translation import gettext_lazy as _ VERIFICATION_CHOICES = [ ("pending", _("pending")), ("verified", _("verified")), ("invalid", _("invalid")), ("rejected", _("rejected")), ] BUFFER_SIZE = 65536 # read in 64kb chunks PK!edP P !spkcspider/apps/verifier/forms.py__all__ = ["CreateEntryForm"] import tempfile import shutil from django import forms from django.forms import widgets from django.utils.translation import gettext_lazy as _ from django.conf import settings from spkcspider.apps.spider.helpers import merge_get_url, get_settings_func from .models import VerifySourceObject _source_url_help = _( "Url to content or content list to verify" ) _source_file_help = _( "File with data to verify" ) class CreateEntryForm(forms.Form): url = forms.URLField(help_text=_source_url_help) dvfile = forms.FileField( required=False, max_length=settings.VERIFIER_MAX_SIZE_DIRECT_ACCEPTED ) # MAX_FILE_SIZE = forms.CharField( # disabled=True, widget=forms.HiddenInput(), required=False, # initial=settings.VERIFIER_MAX_SIZE_ACCEPTED # ) def __init__(self, instance, *args, **kwargs): super().__init__(*args, **kwargs) if settings.VERIFIER_MAX_SIZE_DIRECT_ACCEPTED > 0: self.fields["dvfile"].help_text = _source_file_help self.fields["url"].required = False else: self.fields["dvfile"].disabled = True self.fields["dvfile"].widget = widgets.HiddenInput() def clean(self): ret = super().clean() if not ret.get("url", None) and not ret.get("dvfile", None): raise forms.ValidationError( _('Require either url or dvfile'), code="missing_parameter" ) return ret if ret.get("url", None): self.cleaned_data["url"] = merge_get_url( self.cleaned_data["url"], raw="embed" ) url = self.cleaned_data["url"] if not get_settings_func( "SPIDER_URL_VALIDATOR", "spkcspider.apps.spider.functions.validate_url_default" )(url): self.add_error( "url", forms.ValidationError( _('Insecure url: %(url)s'), params={"url": url}, code="insecure_url" ) ) return ret return ret def save(self): if self.cleaned_data.get("url", None): split = self.cleaned_data["url"].split("?", 1) return VerifySourceObject.objects.update_or_create( url=split[0], defaults={"get_params": split[1]} )[0].id else: f = tempfile.mkstemp() shutil.copyfileobj(self.cleaned_data["dvfile"].file, f) return f.name, self.cleaned_data["dvfile"].size PK!> > %spkcspider/apps/verifier/functions.py__all__ = [ "clean_graph", "get_hashob", "validate_request_default", "verify_tag_default", "domain_auth" ] import logging from urllib.parse import parse_qs, urlencode from django.conf import settings from django.urls import reverse from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from rdflib import XSD, URIRef, Literal import requests import certifi from spkcspider.apps.spider.constants import spkcgraph from spkcspider.apps.spider.helpers import create_b64_token def validate_request_default(request, form): if not form.is_valid(): return False return True def verify_tag_default(tag): tag.save() def domain_auth(source, hostpart): GET = parse_qs(source.get_params) if "token" not in GET: GET["token"] = "prefer" GET["intention"] = "domain" GET["referrer"] = "{}{}".format( hostpart, reverse("spider_verifier:create") ) source.update_secret = create_b64_token() source.save(update_fields=["update_secret"]) GET["payload"] = urlencode( { "url": source.url, "update_secret": source.update_secret } ) url = "{}?{}".format( source.url, urlencode(GET) ) ret = True try: resp = requests.get( url, verify=certifi.where(), timeout=settings.VERIFIER_REQUESTS_TIMEOUT ) resp.raise_for_status() except Exception: if settings.DEBUG: logging.exception("domain_auth failed") ret = False # other or own update_secret was successful or url has problems source.refresh_from_db() return ret def clean_graph(mtype, graph, start, source, hostpart): if not mtype: return None elif "UserComponent" in mtype: return "list" elif "SpiderTag" in mtype: if ( URIRef(start), spkcgraph["features"], Literal("verify", datatype=XSD.string) ) in graph: return "layout_cb" ret = "layout" if domain_auth(source, hostpart): graph.set( ( start, spkcgraph["action:view"], Literal(source.get_url(), datatype=XSD.anyURI) ) ) ret = "layout_cb" return ret else: return "content" def get_hashob(): return hashes.Hash( getattr( settings, "VERIFICATION_HASH_ALGORITHM", settings.SPIDER_HASH_ALGORITHM ), backend=default_backend() ) PK!) 8spkcspider/apps/verifier/locale/de/LC_MESSAGES/django.po# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-03 00:50+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: spkcspider/apps/verifier/constants.py:10 msgid "pending" msgstr "" #: spkcspider/apps/verifier/constants.py:11 msgid "verified" msgstr "" #: spkcspider/apps/verifier/constants.py:12 msgid "rejected" msgstr "" #: spkcspider/apps/verifier/forms.py:27 msgid "Url to content or content list to verify" msgstr "" #: spkcspider/apps/verifier/forms.py:31 spkcspider/apps/verifier/models.py:32 msgid "File with data to verify" msgstr "" #: spkcspider/apps/verifier/forms.py:107 msgid "Require either url or dvfile" msgstr "" #: spkcspider/apps/verifier/forms.py:121 spkcspider/apps/verifier/forms.py:263 #: spkcspider/apps/verifier/forms.py:352 #, python-format msgid "Insecure url: %(url)s" msgstr "" #: spkcspider/apps/verifier/forms.py:132 spkcspider/apps/verifier/forms.py:273 #: spkcspider/apps/verifier/forms.py:362 #, python-format msgid "invalid url: %(url)s" msgstr "" #: spkcspider/apps/verifier/forms.py:141 spkcspider/apps/verifier/forms.py:280 #, python-format msgid "Retrieval failed: %s" msgstr "" #: spkcspider/apps/verifier/forms.py:153 spkcspider/apps/verifier/forms.py:290 #, python-format msgid "" "Retrieval failed, no length specified, invalid\n" "or too long, length: %(length)s, url: %(url)s" msgstr "" #: spkcspider/apps/verifier/forms.py:196 #, python-format msgid "not a \"%(format)s\" file" msgstr "" #: spkcspider/apps/verifier/forms.py:204 #, python-format msgid "invalid graph, scopes: %(scope)s" msgstr "" #: spkcspider/apps/verifier/forms.py:213 #, python-format msgid "invalid graph, pages: %(page)s" msgstr "" #: spkcspider/apps/verifier/forms.py:223 #, python-format msgid "invalid graph, view url: %(url)s" msgstr "" #: spkcspider/apps/verifier/forms.py:247 #, python-format msgid "Invalid type: %(type)s" msgstr "" #: spkcspider/apps/verifier/forms.py:330 #, python-format msgid "%(page)s is not a \"%(format)s\" file" msgstr "" #: spkcspider/apps/verifier/forms.py:369 #, python-format msgid "Retrieval failed: %(reason)s" msgstr "" #: spkcspider/apps/verifier/templates/spider_verifier/dv_detail.html:8 msgid "Hash:" msgstr "" #: spkcspider/apps/verifier/templates/spider_verifier/dv_detail.html:12 msgid "Hash Algorithm:" msgstr "" #: spkcspider/apps/verifier/templates/spider_verifier/dv_detail.html:16 msgid "Verification State:" msgstr "" #: spkcspider/apps/verifier/templates/spider_verifier/dv_detail.html:17 msgid "unverified" msgstr "" #: spkcspider/apps/verifier/templates/spider_verifier/dv_form.html:5 msgid "If the graph is too big, the connection is resetted" msgstr "" #: spkcspider/apps/verifier/templates/spider_verifier/dv_form.html:9 msgid "Submit" msgstr "" PK!_]J3spkcspider/apps/verifier/migrations/0001_initial.py# Generated by Django 2.1.2 on 2018-10-31 15:06 from django.db import migrations, models import spkcspider.apps.verifier.models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='DataVerificationTag', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('created', models.DateTimeField(auto_now_add=True)), ('modified', models.DateTimeField(auto_now=True)), ('hash', models.SlugField(max_length=512, unique=True)), ('dvfile', models.FileField(blank=True, help_text='File with data to verify', null=True, upload_to=spkcspider.apps.verifier.models.dv_path)), ('data_type', models.CharField(default='layout', max_length=20)), ('checked', models.DateTimeField(blank=True, null=True)), ('verification_state', models.CharField(choices=[('pending', 'pending'), ('verified', 'verified'), ('rejected', 'rejected')], default='pending', max_length=10)), ('note', models.TextField(default='')), ], ), ] PK!X;;>spkcspider/apps/verifier/migrations/0002_auto_20181031_2121.py# Generated by Django 2.1.2 on 2018-10-31 21:21 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_verifier', '0001_initial'), ] operations = [ migrations.AlterModelOptions( name='dataverificationtag', options={'permissions': [('can_verify', 'Can verify Data Tag?')]}, ), migrations.AlterField( model_name='dataverificationtag', name='note', field=models.TextField(blank=True, default=''), ), ] PK!x;p@spkcspider/apps/verifier/migrations/0003_1_linked_hashes_stub.py# Generated by Django 2.1.3 on 2018-11-21 07:38 from django.db import migrations class Migration(migrations.Migration): """ Introduced new field which was not required after integrating the hashes in the graph file """ replaces = [('spider_verifier', '0003_dataverificationtag_linked_hashes'), ('spider_verifier', '0004_remove_dataverificationtag_linked_hashes')] dependencies = [ ('spider_verifier', '0002_auto_20181031_2121'), ] operations = [ ] PK!𔨆Mspkcspider/apps/verifier/migrations/0003_dataverificationtag_linked_hashes.py# Generated by Django 2.1.2 on 2018-11-02 18:26 from django.db import migrations import jsonfield.fields class Migration(migrations.Migration): dependencies = [ ('spider_verifier', '0002_auto_20181031_2121'), ] operations = [ migrations.AddField( model_name='dataverificationtag', name='linked_hashes', field=jsonfield.fields.JSONField(blank=True, default=dict), ), ] PK!>spkcspider/apps/verifier/migrations/0004_auto_20190225_2153.py# Generated by Django 2.1.7 on 2019-02-25 21:53 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ('spider_verifier', '0003_1_linked_hashes_stub'), ] operations = [ migrations.CreateModel( name='VerifySourceObject', fields=[ ('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)), ('url', models.URLField(db_index=True, max_length=400, unique=True)), ('get_params', models.TextField()), ], ), migrations.AlterField( model_name='dataverificationtag', name='verification_state', field=models.CharField(choices=[('retrieve', 'retrieval pending'), ('pending', 'pending'), ('verified', 'verified'), ('invalid', 'invalid'), ('rejected', 'rejected')], default='retrieve', max_length=10), ), migrations.AddField( model_name='dataverificationtag', name='source', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='spider_verifier.VerifySourceObject'), ), ] PK!wssTspkcspider/apps/verifier/migrations/0004_remove_dataverificationtag_linked_hashes.py# Generated by Django 2.1.2 on 2018-11-11 13:16 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('spider_verifier', '0003_dataverificationtag_linked_hashes'), ] operations = [ migrations.RemoveField( model_name='dataverificationtag', name='linked_hashes', ), ] PK!^c"$$>spkcspider/apps/verifier/migrations/0005_auto_20190302_1741.py# Generated by Django 2.1.7 on 2019-03-02 17:41 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_verifier', '0004_auto_20190225_2153'), ] operations = [ migrations.AlterField( model_name='dataverificationtag', name='verification_state', field=models.CharField(choices=[('pending', 'pending'), ('verified', 'verified'), ('invalid', 'invalid'), ('rejected', 'rejected')], default='pending', max_length=10), ), ] PK!+:Lspkcspider/apps/verifier/migrations/0006_verifysourceobject_update_secret.py# Generated by Django 2.1.7 on 2019-03-09 22:48 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('spider_verifier', '0005_auto_20190302_1741'), ] operations = [ migrations.AddField( model_name='verifysourceobject', name='update_secret', field=models.CharField(blank=True, max_length=120, null=True, unique=True), ), ] PK!/spkcspider/apps/verifier/migrations/__init__.pyPK!/"spkcspider/apps/verifier/models.pyimport datetime from django.db import models from django.utils.translation import gettext_lazy as _ from django.urls import reverse from django.core import exceptions from django.conf import settings import requests import certifi from spkcspider.apps.spider.constants import ( MAX_TOKEN_B64_SIZE ) from .constants import ( VERIFICATION_CHOICES ) def dv_path(instance, filename): return 'dvfiles/{}/{}.{}'.format( datetime.datetime.now().strftime("%Y/%m"), instance.hash, "ttl" ) class VerifySourceObject(models.Model): id = models.BigAutoField(primary_key=True, editable=False) url = models.URLField( max_length=400, db_index=True, unique=True ) get_params = models.TextField() update_secret = models.CharField( max_length=MAX_TOKEN_B64_SIZE, unique=True, null=True, blank=True ) def get_url(self, access=None): if access: split = self.url.rsplit("view", 1) if len(split) == 1: raise Exception() return "{}{}/?{}".format(split[0], access, self.get_params) return "{}?{}".format(self.url, self.get_params) class DataVerificationTag(models.Model): """ Contains verified data """ # warning: never depend directly on user, seperate for multi-db setups id = models.BigAutoField(primary_key=True, editable=False) created = models.DateTimeField(auto_now_add=True, editable=False) modified = models.DateTimeField(auto_now=True, editable=False) hash = models.SlugField( unique=True, db_index=True, null=False, max_length=512 ) dvfile = models.FileField( upload_to=dv_path, null=True, blank=True, help_text=_( "File with data to verify" ) ) source = models.ForeignKey( VerifySourceObject, null=True, blank=True, on_delete=models.CASCADE ) # url = models.URLField(max_length=600) data_type = models.CharField(default="layout", max_length=20) checked = models.DateTimeField(null=True, blank=True) verification_state = models.CharField( default="pending", max_length=10, choices=VERIFICATION_CHOICES ) note = models.TextField(default="", blank=True) class Meta: permissions = [("can_verify", "Can verify Data Tag?")] def __str__(self): return "DVTag: ...%s" % self.hash[:30] def get_absolute_url(self): return reverse( "spider_verifier:verify", kwargs={ "hash": self.hash } ) def callback(self, hostpart): if self.source and self.data_type.endswith("_cb"): vurl = self.source.get_url("verify") body = { "url": "{}{}".format( hostpart, self.get_absolute_url() ) } try: resp = requests.post( vurl, data=body, verify=certifi.where(), timeout=settings.VERIFIER_REQUESTS_TIMEOUT ) except requests.exceptions.Timeout: raise exceptions.ValidationError( _('url timed out: %(url)s'), params={"url": vurl}, code="timeout_url" ) except requests.exceptions.ConnectionError: raise exceptions.ValidationError( _('invalid url: %(url)s'), params={"url": vurl}, code="invalid_url" ) if resp.status_code != 200: raise exceptions.ValidationError( _("Retrieval failed: %(reason)s"), params={"reason": resp.reason}, code="error_code:{}".format(resp.status_code) ) PK!fAspkcspider/apps/verifier/templates/spider_verifier/dv_detail.html{# real verifier have to replace this or to depend on spider #} {% extends "spider_base/base.html" %} {% load i18n spider_rdf %} {% block content %}
Results:
{% trans "Hash:" %} {{object.hash}}
{% trans "Hash Algorithm:" %} {{hash_algorithm}}
{% trans "Verification State:" %} {% if object.verification_state == "verified" %}{% trans 'verified' %}:{{verified}}{% else %}{% trans 'unverified' %}{% endif %}
{% endblock content %} PK!잖zz?spkcspider/apps/verifier/templates/spider_verifier/dv_form.html{# just use edit_form, real verifier have to replace this or to depend on spider #} {% extends "spider_base/base.html" %} {% load i18n spider_rdf %} {% block main_attributes %}{{block.super}} prefix="spkc: {% spkc_namespace %} xsd: http://www.w3.org/2001/XMLSchema#" resource="{{ request.path }}"{% endblock %} {% block content %}
{% include "spider_base/edit_form.html" with inner_form=True legend='Send spider or content to verify' %}
{% endblock content %} PK!k ?spkcspider/apps/verifier/templates/spider_verifier/dv_wait.html{# just use edit_form, real verifier have to replace this or to depend on spider #} {% extends "registration/base.html" %} {% load i18n %} {% block extrahead %} {{block.super}} {% endblock %} {% block content %}

{% trans "Please wait (it can take hours)." %}

{% endblock %} {# form errors should not appear here #} {% block errors %}{% endblock errors%} PK!g x-'' spkcspider/apps/verifier/urls.pyfrom django.urls import path from .views import CreateEntry, VerifyEntry, HashAlgoView app_name = "spider_verifier" urlpatterns = [ path( 'task//', CreateEntry.as_view(), name='task' ), path( 'hash//', VerifyEntry.as_view(), name='verify' ), path( 'hash/', HashAlgoView.as_view(), name='hash_algo' ), # NOTE: this view is csrf_exempted path( '', CreateEntry.as_view(), name='create' ), ] PK!F`Hb7b7$spkcspider/apps/verifier/validate.py__all__ = { "validate", "valid_wait_states", "verify_download_size", "async_validate", "verify", "async_verify" } import logging import binascii import tempfile import os from django.utils.translation import gettext as _ from django.core.files import File from django.conf import settings from django.core import exceptions from rdflib import Graph, URIRef, Literal from rdflib.namespace import XSD import requests import certifi from spkcspider import celery_app from spkcspider.apps.spider.constants import spkcgraph from spkcspider.apps.spider.helpers import merge_get_url, get_settings_func from .constants import BUFFER_SIZE from .functions import get_hashob from .models import VerifySourceObject, DataVerificationTag hashable_predicates = set([spkcgraph["name"], spkcgraph["value"]]) valid_wait_states = { "RETRIEVING", "HASHING", "STARTED" } def hash_entry(triple): h = get_hashob() if triple[2].datatype == XSD.base64Binary: h.update(triple[2].datatype.encode("utf8")) h.update(triple[2].toPython()) else: if triple[2].datatype: h.update(triple[2].datatype.encode("utf8")) else: h.update(XSD.string.encode("utf8")) h.update(triple[2].encode("utf8")) return h.finalize() def yield_hashes(graph, hashable_nodes): for t in graph.triples((None, spkcgraph["value"], None)): if ( t[0] in hashable_nodes and t[2].datatype != spkcgraph["hashableURI"] ): yield hash_entry(t) def yield_hashable_urls(graph, hashable_nodes): for t in graph.triples( (None, spkcgraph["value"], spkcgraph["hashableURI"]) ): if t[0] in hashable_nodes: yield t def verify_download_size(length, current_size=0): if not length or not length.isdigit(): return False length = int(length) if settings.VERIFIER_MAX_SIZE_ACCEPTED < length: return False return True def validate(ob, hostpart, task=None): dvfile = None source = None if isinstance(ob, tuple): dvfile = open(ob[0], "r+b") current_size = ob[1] else: current_size = 0 dvfile = tempfile.NamedTemporaryFile(delete=False) source = VerifySourceObject.objects.get( id=ob ) url = source.get_url() try: resp = requests.get( url, stream=True, verify=certifi.where(), timeout=settings.VERIFIER_REQUESTS_TIMEOUT ) except requests.exceptions.Timeout: raise exceptions.ValidationError( _('url timed out: %(url)s'), params={"url": url}, code="timeout_url" ) except requests.exceptions.ConnectionError: raise exceptions.ValidationError( _('invalid url: %(url)s'), params={"url": url}, code="invalid_url" ) if resp.status_code != 200: raise exceptions.ValidationError( _("Retrieval failed: %(reason)s"), params={"reason": resp.reason}, code="error_code:{}".format(resp.status_code) ) c_length = resp.headers.get("content-length", None) if not verify_download_size(c_length, current_size): raise exceptions.ValidationError( _("Content too big: %(size)s"), params={"size": c_length}, code="invalid_size" ) c_length = int(c_length) current_size += c_length # preallocate file dvfile.truncate(c_length) dvfile.seek(0, 0) for chunk in resp.iter_content(BUFFER_SIZE): dvfile.write(chunk) dvfile.seek(0, 0) g = Graph() g.namespace_manager.bind("spkc", spkcgraph, replace=True) try: g.parse( dvfile.name, format="turtle" ) except Exception: if settings.DEBUG: dvfile.seek(0, 0) logging.exception(dvfile.read()) logging.error("Parsing file failed") dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('Invalid graph fromat'), code="invalid_format" ) tmp = list(g.triples((None, spkcgraph["scope"], None))) if len(tmp) != 1: dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('Invalid graph'), code="invalid_graph" ) start = tmp[0][0] scope = tmp[0][2].toPython() tmp = list(g.triples((start, spkcgraph["pages.num_pages"], None))) if len(tmp) != 1: dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('Invalid graph'), code="invalid_graph" ) pages = tmp[0][2].toPython() tmp = list(g.triples(( start, spkcgraph["pages.current_page"], Literal(1, datatype=XSD.positiveInteger) ))) if len(tmp) != 1: dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('Must be page 1'), code="invalid_page" ) if task: task.update_state( state='RETRIEVING', meta={ 'page': 1, 'num_pages': pages } ) tmp = list(g.objects(start, spkcgraph["action:view"])) if len(tmp) != 1: dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('Invalid graph'), code="invalid_graph" ) view_url = tmp[0].toPython() if isinstance(ob, tuple): split = view_url.split("?", 1) source = VerifySourceObject.objects.update_or_create( url=split[0], defaults={"get_params": split[1]} ) mtype = set() if scope == "list": mtype.add("UserComponent") else: mtype.update(map( lambda x: x.toPython(), g.objects(start, spkcgraph["type"]) )) data_type = get_settings_func( "VERIFIER_CLEAN_GRAPH", "spkcspider.apps.verifier.functions.clean_graph" )(mtype, g, start, source, hostpart) if not data_type: dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('Invalid graph type: %(type)s'), params={"type": data_type}, code="invalid_type" ) # retrieve further pages for page in range(2, pages+1): url = merge_get_url( source.get_url(), raw="embed", page=str(page) ) # validation not neccessary here (base url is verified) try: resp = requests.get( url, stream=True, verify=certifi.where(), timeout=settings.VERIFIER_REQUESTS_TIMEOUT ) except requests.exceptions.Timeout: dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('url timed out: %(url)s'), params={"url": url}, code="timeout_url" ) except requests.exceptions.ConnectionError: dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('Invalid url: %(url)s'), params={"url": url}, code="innvalid_url" ) if resp.status_code != 200: dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _("Retrieval failed: %(reason)s"), params={"reason": resp.reason}, code="error_code:{}".format(resp.status_code) ) c_length = resp.headers.get("content-length", None) if not verify_download_size(c_length, current_size): dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _("Content too big: %(size)s"), params={"size": c_length}, code="invalid_size" ) c_length = int(c_length) current_size += c_length # clear file dvfile.truncate(c_length) dvfile.seek(0, 0) for chunk in resp.iter_content(BUFFER_SIZE): dvfile.write(chunk) dvfile.seek(0, 0) try: g.parse( dvfile.name, format="turtle" ) except Exception as exc: if settings.DEBUG: dvfile.seek(0, 0) logging.error(dvfile.read()) logging.exception(exc) dvfile.close() os.unlink(dvfile.name) # pages could have changed, but still incorrect raise exceptions.ValidationError( _("%(page)s is not a \"%(format)s\" file"), params={"format": "turtle", "page": page}, code="invalid_file" ) if task: task.update_state( state='RETRIEVING', meta={ 'page': page, 'num_pages': pages } ) hashable_nodes = set(g.subjects( predicate=spkcgraph["hashable"], object=Literal(True) )) hashes = [ i for i in yield_hashes(g, hashable_nodes) ] if task: task.update_state( state='RETRIEVING', meta={ 'hashable_urls_checked': 0 } ) for count, t in enumerate(yield_hashable_urls(g, hashable_nodes), start=1): if (URIRef(t[2].value), None, None) in g: continue url = merge_get_url(t[2].value, raw="embed") if not get_settings_func( "SPIDER_URL_VALIDATOR", "spkcspider.apps.spider.functions.validate_url_default" )(url): dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('Insecure url: %(url)s'), params={"url": url}, code="insecure_url" ) try: resp = requests.get( url, stream=True, verify=certifi.where(), timeout=settings.VERIFIER_REQUESTS_TIMEOUT ) except requests.exceptions.Timeout: dvfile.close() os.unlink(dvfile.name) raise exceptions.ValidationError( _('url timed out: %(url)s'), params={"url": url}, code="timeout_url" ) except requests.exceptions.ConnectionError: raise exceptions.ValidationError( _('Invalid url: %(url)s'), params={"url": url}, code="innvalid_url" ) if resp.status_code != 200: raise exceptions.ValidationError( _("Retrieval failed: %(reason)s"), params={"reason": resp.reason}, code="code_{}".format(resp.status_code) ) h = get_hashob() h.update(XSD.base64Binary.encode("utf8")) for chunk in resp.iter_content(BUFFER_SIZE): h.update(chunk) # do not use add as it could be corrupted by user # (user can provide arbitary data) g.set(( URIRef(t[2].value), spkcgraph["hash"], Literal(h.finalize().hex()) )) if task: task.update_state( state='RETRIEVING', meta={ 'hashable_urls_checked': count } ) if task: task.update_state( state='HASHING', ) # make sure triples are linked to start # (user can provide arbitary data) g.remove((start, spkcgraph["hashed"], None)) for t in g.triples((None, spkcgraph["hash"], None)): g.add(( start, spkcgraph["hashed"], t[0] )) hashes.append(binascii.unhexlify(t[2].value)) for i in g.subjects(spkcgraph["type"], Literal("Content")): h = get_hashob() h.update(i.encode("utf8")) hashes.append(h.finalize()) hashes.sort() h = get_hashob() for i in hashes: h.update(i) # do not use add as it could be corrupted by user # (user can provide arbitary data) digest = h.finalize().hex() g.set(( start, spkcgraph["hash"], Literal(digest) )) dvfile.truncate(0) dvfile.seek(0, 0) # save in temporary file g.serialize( dvfile, format="turtle" ) result, created = DataVerificationTag.objects.get_or_create( defaults={ "dvfile": File(dvfile), "source": source, "data_type": data_type }, hash=digest ) dvfile.close() os.unlink(dvfile.name) update_fields = set() # and source, cannot remove source without replacement if not created and source and source != result.source: result.source = source update_fields.add("source") if data_type != result.data_type: result.data_type = data_type update_fields.add("data_type") result.save(update_fields=update_fields) if task: task.update_state( state='SUCCESS' ) return result @celery_app.task(bind=True, name='async validation') def async_validate(self, ob, hostpart): ret = validate(ob, hostpart, self) return ret.get_absolute_url() def verify(tagid, task=None): tag = DataVerificationTag.objects.get(id=tagid) get_settings_func( "VERIFIER_TAG_VERIFIER", "spkcspider.apps.verifier.functions.verify_tag_default" )(tag) if tag.verification_state == "verified": try: tag.callback() except exceptions.ValidationError: logging.exception("Error while calling back") @celery_app.task(bind=True, name='async verification', ignore_results=True) def async_verify(self, tagid): verify(tagid, self) PK!!spkcspider/apps/verifier/views.py__all__ = ["HashAlgoView", "CreateEntry", "HashAlgoView"] from urllib.parse import parse_qs, urlencode from django.contrib import messages from django.shortcuts import redirect from django.core.exceptions import NON_FIELD_ERRORS from django.views.generic.edit import UpdateView from django.views.generic.detail import DetailView from django.views import View from django.http import HttpResponse, HttpResponseRedirect from django.utils.translation import gettext as _ from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from django.conf import settings from ratelimit.decorators import ratelimit as ratelimit_deco from celery.exceptions import TimeoutError from rdflib import Literal from spkcspider.apps.spider.helpers import get_settings_func from .models import DataVerificationTag, VerifySourceObject from .forms import CreateEntryForm from .validate import valid_wait_states, async_validate class CreateEntry(UpdateView): # NOTE: this class is csrf_exempted # reason for this are cross post requests model = DataVerificationTag form_class = CreateEntryForm template_name = "spider_verifier/dv_form.html" task_id_field = "task_id" object = None def get_object(self): if self.task_id_field not in self.kwargs: return None return async_validate.AsyncResult( self.kwargs[self.task_id_field] ) # exempt from csrf checks for API usage @method_decorator( [ csrf_exempt, ratelimit_deco( key="user_or_ip", group="create_verification_request", rate=settings.VERIFIER_REQUEST_RATE, block=True, method=ratelimit_deco.UNSAFE ) ] ) def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) def get_form_kwargs(self): ret = super().get_form_kwargs() if "initial" not in ret: # {} not hashable ret["initial"] = {} ret["initial"]["url"] = self.request.META.get("Referer", "") return ret def get(self, request, *args, **kwargs): self.object = self.get_object() form = None if self.object: try: res = self.object.get(timeout=5) if self.object.successful(): ret = HttpResponseRedirect( redirect_to=res ) ret["Access-Control-Allow-Origin"] = "*" return ret # replace form form = self.get_form() form.add_error(NON_FIELD_ERRORS, res) messages.error(self.request, _('Validation failed')) except TimeoutError: if self.object.state in valid_wait_states: self.template_name = "spider_verifier/dv_wait.html" else: messages.error(self.request, _('Invalid Task')) ret = redirect( "spider_verifier:create" ) ret["Access-Control-Allow-Origin"] = "*" return ret else: form = self.get_form() return self.render_to_response( self.get_context_data(form=form, **kwargs) ) def post(self, request, *args, **kwargs): if "payload" in request.POST: try: payload = parse_qs(request.POST["payload"]) # check if token parameter exists request.POST["token"] except Exception: return HttpResponse(400) ob = VerifySourceObject.objects.filter( url=payload.get("url", [None])[0], update_secret=payload.get("update_secret", ["x"])[0] ).first() if ob: GET = parse_qs(ob.get_params) GET["token"] = request.POST["token"] ob.get_params = urlencode(GET) ob.update_secret = None ob.save(update_fields=["get_params", "update_secret"]) return HttpResponse(200) return HttpResponse(404) form = self.get_form() if get_settings_func( "VERIFIER_REQUEST_VALIDATOR", "spkcspider.apps.verifier.functions.validate_request_default" )(self.request, form): return self.form_valid(form) else: return self.form_invalid(form) def form_valid(self, form): task = async_validate.apply_async( args=( form.save(), "{}://{}".format( self.request.scheme, self.request.get_host() ) ), track_started=True ) ret = redirect( "spider_verifier:task", task_id=task.task_id ) ret["Access-Control-Allow-Origin"] = "*" return ret class VerifyEntry(DetailView): model = DataVerificationTag slug_field = "hash" slug_url_kwarg = "hash" template_name = "spider_verifier/dv_detail.html" def get_context_data(self, **kwargs): if self.object.verification_state == "verified": if self.object.checked: kwargs["verified"] = Literal(self.object.checked) else: kwargs["verified"] = Literal(True) else: kwargs["verified"] = Literal(False) kwargs["hash_algorithm"] = getattr( settings, "VERIFICATION_HASH_ALGORITHM", settings.SPIDER_HASH_ALGORITHM ).name return super().get_context_data(**kwargs) class HashAlgoView(View): def get(self, request, *args, **kwargs): algo = getattr( settings, "VERIFICATION_HASH_ALGORITHM", settings.SPIDER_HASH_ALGORITHM ).name return HttpResponse( content=algo.encode("utf8"), content_type="text/plain; charset=utf8" ) PK!-#CCspkcspider/celery.py import os from celery import Celery # set the default Django settings module for the 'celery' program. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'spkcspider.settings.debug') app = Celery('spkcspider') # Using a string here means the worker doesn't have to serialize # the configuration object to child processes. # - namespace='CELERY' means all celery-related configuration keys # should have a `CELERY_` prefix. app.config_from_object('django.conf:settings', namespace='CELERY') # Load task modules from all registered Django app configs. app.autodiscover_tasks() PK! :h%%spkcspider/settings/.gitignore* !.gitignore !__init__.py !debug.py PK!'+spkcspider/settings/__init__.py""" Django settings for spkcspider project. Generated by 'django-admin startproject' using Django 1.11.2. For more information on this file, see https://docs.djangoproject.com/en/1.11/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.11/ref/settings/ """ import os from django.utils.translation import gettext_lazy as _ from cryptography.hazmat.primitives import hashes # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ ALLOWED_HOSTS = [] FILE_UPLOAD_HANDLERS = [ 'django.core.files.uploadhandler.MemoryFileUploadHandler', "spkcspider.apps.spider.functions.LimitedTemporaryFileUploadHandler", ] # Application definition INSTALLED_APPS = [ 'widget_tweaks', 'spkcspider.apps.spider_accounts', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.sites', # for flatpages 'django.contrib.flatpages', 'django.contrib.sitemaps', 'spkcspider.apps.spider', ] try: import django_extensions # noqa: F401 INSTALLED_APPS.append('django_extensions') except ImportError: pass MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', ] ROOT_URLCONF = 'spkcspider.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'spkcspider.apps.spider.context_processors.settings', 'django.template.context_processors.i18n', 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'spkcspider.wsgi.application' # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', # noqa: E501 }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', # noqa: E501 }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', # noqa: E501 }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', # noqa: E501 }, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True STATICFILES_DIRS = [ # add node_modules as node_modules under static ("node_modules", os.path.join(BASE_DIR, "node_modules")) ] # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_ROOT = 'static/' STATIC_URL = '/static/' MEDIA_ROOT = 'media/' MEDIA_URL = '/media/' CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge' CAPTCHA_FONT_SIZE = 40 LOGIN_URL = "auth:login" LOGIN_REDIRECT_URL = "auth:profile" LOGOUT_REDIRECT_URL = "home" AUTH_USER_MODEL = 'spider_accounts.SpiderUser' # uses cryptography SPIDER_HASH_ALGORITHM = hashes.SHA512() # as hex digest MAX_HASH_SIZE = 128 MIN_STRENGTH_EVELATION = 2 # change size of request token. # Note: should be high to prevent token exhaustion # TOKEN_SIZE = 30 # OPEN_FOR_REGISTRATION = True # allow registration # ALLOW_USERNAME_CHANGE = True # allow users changing their username ## Default static token size (!=TOKEN_SIZE) # noqa: E266 # SPIDER_INITIAL_STATIC_TOKEN_SIZE ## captcha field names (REQUIRED) # noqa: E266 SPIDER_CAPTCHA_FIELD_NAME = "sunglasses" ## Update dynamic content, ... after migrations, default=true # noqa: E266 # UPDATE_DYNAMIC_AFTER_MIGRATION = False ## extensions of images (used in file_filets) # noqa: E266 # SPIDER_IMAGE_EXTENSIONS ## extensions of media (used in file_filets) # noqa: E266 # SPIDER_MEDIA_EXTENSIONS ## embeddding function for files in graph, for e.g. linking # noqa: E266 # SPIDER_FILE_EMBED_FUNC ## validator function for url requests # noqa: E266 # SPIDER_URL_VALIDATOR ## validator function for payment intentions # noqa: E266 # SPIDER_PAYMENT_VALIDATOR ## Enable captchas # noqa: E266 # INSTALLED_APPS.append('captcha') # USE_CAPTCHAS = True # DIRECT_FILE_DOWNLOAD = True # ALLOWED_CONTENT_FILTER # SPIDER_TAG_VERIFIER_VALIDATOR # SPIDER_TAG_VERIFY_REQUEST_VALIDATOR # SPIDER_ANCHOR_DOMAIN # SPIDER_COMPONENTS_DELETION_PERIODS # SPIDER_CONTENTS_DEFAULT_DELETION_PERIOD # RATELIMIT_FUNC_CONTENTS ## Enable direct file downloads (handled by webserver) # noqa: E266 # disadvantage: blocking access requires file name change # FILE_DIRECT_DOWNLOAD # FILE_FILET_DIR # FILE_FILET_SALT_SIZE # SPIDER_GET_QUOTA # SPIDER_USER_QUOTA_LOCAL # SPIDER_USER_QUOTA_REMOTE ## in units # noqa: E266 # SPIDER_USER_QUOTA_USERCOMPONENTS # unbreak old links after switch to a new machine friendly url layout SPIDER_LEGACY_REDIRECT = True ## Use subpath to create ids for identifiers # noqa: E266 # SPIDER_ID_USE_SUBPATH # usercomponents created with user DEFAULT_USERCOMPONENTS = { "home": { "public": False, "features": ["Persistence", "WebConfig"] }, "public": { "public": True, "features": [] }, } ## Default description # noqa: E266 SPIDER_DESCRIPTION = "A spkcspider instance for my personal data." SPIDER_BLACKLISTED_MODULES = [ "spkcspider.apps.spider.models.contents.TravelProtection", "spkcspider.apps.spider.protections.TravelProtection", ] # timeout for spkcspider outgoing requests SPIDER_REQUESTS_TIMEOUT = 3 # maximal domain_mode activation per usercomponent/domain SPIDER_DOMAIN_UPDATE_RATE = "10/m" # maximal error rate for a domain before blocking requests SPIDER_DOMAIN_ERROR_RATE = "10/10m" # max description length (stripped) SPIDER_MAX_DESCRIPTION_LENGTH = 200 # how many user components/contents per page SPIDER_OBJECTS_PER_PAGE = 25 # how many raw/serialized results per page? SPIDER_SERIALIZED_PER_PAGE = 50 # max depth of references SPIDER_MAX_EMBED_DEPTH = 5 # how many search parameters are allowed SPIDER_MAX_SEARCH_PARAMETERS = 30 # licences for media SPIDER_LICENSE_CHOICES = { "other": { "url": "" }, "pd": { "name": _("Public Domain/CC0"), "url": "https://creativecommons.org/publicdomain/zero/1.0/legalcode" }, "CC BY": { "url": "https://creativecommons.org/licenses/by/4.0/legalcode" }, "CC BY-SA": { "url": "https://creativecommons.org/licenses/by-sa/4.0/legalcode" }, "CC BY-ND": { "url": "https://creativecommons.org/licenses/by-nd/4.0/legalcode" }, "CC BY-NC": { "url": "https://creativecommons.org/licenses/by-nc/4.0/legalcode" }, "CC BY-NC-SA": { "url": "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode" }, "CC BY-NC-ND": { "url": "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode" }, } SPIDER_DEFAULT_LICENSE_FILE = "CC BY" # licences for text (default file licenses are used) # SPIDER_LICENSE_CHOICES_TEXT # SPIDER_DEFAULT_LICENSE_TEXT # disable when importing backup # ease deploy UPDATE_DYNAMIC_AFTER_MIGRATION = True SITE_ID = 1 PK!{Jspkcspider/settings/debug.py# flake8: noqa from spkcspider.settings import * # noqa: F403, F401 INSTALLED_APPS += [ 'spkcspider.apps.spider_filets', 'spkcspider.apps.spider_keys', 'spkcspider.apps.spider_tags', 'spkcspider.apps.spider_webcfg', # ONLY for tests and REAL verifiers=companies verifing data 'spkcspider.apps.verifier', 'captcha' ] USE_CAPTCHAS = True # Verifier specific options, normally not required # timeout for verifier "requests" requests VERIFIER_REQUESTS_TIMEOUT = 6 # how many verification requests of user/ip per minute VERIFIER_REQUEST_RATE = "10/m" # 40 mb maximal size VERIFIER_MAX_SIZE_ACCEPTED = 40000000 # 2 mb, set to 0 to disable a direct file upload VERIFIER_MAX_SIZE_DIRECT_ACCEPTED = 2000000 # not required, SpiderTokenAuthBackend have to be tested, so here active AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', 'spkcspider.apps.spider.auth.SpiderTokenAuthBackend' ] # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '^_08u&*be(*my6$pv^m3fki!2s5)5e)9@l5l#srch1h)w3p+$l' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # how many user contents/components per page SPIDER_OBJECTS_PER_PAGE = 3 # how many raw/serialized results per page? SPIDER_SERIALIZED_PER_PAGE = 3 # max depth of references SPIDER_MAX_EMBED_DEPTH = 5 CELERY_BROKER_URL = "redis://127.0.0.1:6379" CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379" # specify fixtures directory for tests FIXTURE_DIRS = [ "tests/fixtures/" ] PK!r~spkcspider/urls.py"""spkcspider URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/2.0/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.conf.urls.i18n import i18n_patterns from django.urls import path, include from django.conf.urls.static import static from django.contrib import admin from django.views.generic.base import RedirectView from django.conf import settings from django.apps import apps from spkcspider.apps.spider.views import ComponentPublicIndex from spkcspider.apps.spider.helpers import get_settings_func from spkcspider.apps.spider.functions import admin_login from django.contrib.sitemaps import views as sitemaps_views from django.views.decorators.cache import cache_page from spkcspider.apps.spider.sitemaps import sitemaps favicon_view = RedirectView.as_view( url='{}spider_base/favicon.svg'.format(settings.STATIC_URL), permanent=True ) robots_view = RedirectView.as_view( url='{}spider_base/robots.txt'.format(settings.STATIC_URL), permanent=True ) # disable admin login page admin.site.login = lambda *args, **kwargs: admin_login( admin.site, *args, **kwargs ) # default: allow only non faked user with superuser and staff permissions admin.site.has_permission = lambda *args, **kwargs: get_settings_func( "HAS_ADMIN_PERMISSION_FUNC", "spkcspider.apps.spider.functions.has_admin_permission" )(admin.site, *args, **kwargs) urlpatterns = [] urlpatterns_i18n = [ path('admin/', admin.site.urls), path( '', ComponentPublicIndex.as_view( is_home=True, template_name="spider_base/home.html" ), name="home" ), ] for app in apps.get_app_configs(): url_path = getattr( app, "spider_url_path", None ) if not url_path: continue urlpatterns_i18n.append( path( url_path, include("{}.urls".format(app.name)) ) ) if getattr(settings, "SPIDER_LEGACY_REDIRECT", False): urlpatterns_i18n.insert( 0, path( 'spider/content/access///', RedirectView.as_view( pattern_name='spider_base:ucontent-access', permanent=True ) ) ) urlpatterns_i18n.insert( 0, path( 'spider/ucs/list//', RedirectView.as_view( pattern_name='spider_base:ucontent-list', permanent=True ) ) ) if getattr(settings, "USE_CAPTCHAS", False): urlpatterns.append(path(r'captcha/', include('captcha.urls'))) if 'django.contrib.flatpages' in settings.INSTALLED_APPS: urlpatterns_i18n.append( path('pages/', include('django.contrib.flatpages.urls')) ) urlpatterns += [ # daily path('favicon.ico', cache_page(86400)(favicon_view)), path('robots.txt', cache_page(86400)(robots_view)), # daily path( 'sitemap.xml', cache_page(86400)( sitemaps_views.index ), { 'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps' }, name='django.contrib.sitemaps.views.index' ), # hourly path( 'sitemap-
.xml', cache_page(3600)( sitemaps_views.sitemap ), {'sitemaps': sitemaps}, name='sitemaps' ), ] + i18n_patterns(*urlpatterns_i18n, prefix_default_language=False) if settings.DEBUG: urlpatterns += static( settings.MEDIA_URL, document_root=settings.MEDIA_ROOT ) PK!*Rvyspkcspider/wsgi.py""" WSGI config for spkcspider project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ """ import os import sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if BASE_DIR not in sys.path: sys.path.append(BASE_DIR) from django.core.wsgi import get_wsgi_application # noqa: E402 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "spkcspider.settings.debug") if not os.environ.get( "SPIDER_SILENCE", "django.core.management" in sys.modules # is loaded by manage.py ): print("USE SETTINGS:", os.environ["DJANGO_SETTINGS_MODULE"]) application = get_wsgi_application() PK! 0t)) spkcspider-0.2.dist-info/LICENSECopyright (c) 2017-2019 Alexander Kaftan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HڽTUspkcspider-0.2.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H!y .!spkcspider-0.2.dist-info/METADATAZsǑ}1-Y,%9.FdU"W*ʡ`}yg r@*窮\.q13oldILb[6yݴ"_n/~l-~Z96?~x=3lSdww~ugf{љui7w:r:|՛*.D6aeZ9α>P63BڌLq4_E;*A4$&˱ä0e`5>+cUl[PEi=L xlN&ٖK9W(:ݝ,;sem̶{N㈃` V[lϽ|Y`ƕj|5ЯJTbsVs_Rkd+|x5P4#:q\`pZgjx@G˾ECk6::#{ǓvEas&Z[櫾UmuA4S"b]PLh82GQ&WX |&./_ށrـ*h d.ҁ,ΜqurmQ r 8Eꦄue$i f}?gWwpuV^B$ߊq476p@JXH roͯGz) g;7f$j>(*OLBD ~)BW3U4utY/pFy[.zTWcoEa <]*0f!G.CK%$n{(W4s#h, 8=ŖҺ0\7]Hόʚ"fxrabu}EE0+(F[Ψ3qt]jK&:?Wۢt_@VURH6dg)``ї!wQ(hG( b&TYRT~p(cCq(0%Ԙȍ7q{ +̗_ŁYDoE (`瀫;Z v0an8Ho߾ 9{L54bȲ;`,VwyqFOmP^)Ńfipb4߿K.~ȦG WvE?+[ioLVVnuD@tNdK)39pwfcGqUTs 3OKڊ|x$d@g$ dzض ~&ERdUSߦ] ~/ѿ_֑(CdŎ0-o8jN*[+i]8GѦTZ++>{Enbqj@"q[.V8;.^\>G!pH^Ņ91~e 3A_}uhXgxW\,L= 8ͼ|qqy@KZo5鿿Fxyl3Y@lNfP;v +3GyɈBm;%xc[Aݢ7pm,ZK:u{Ilu { ;4&a]8856qkF ;dU T/:~ o'~WgMg??OD@= Y{hƋ_ߞ9>?}ߜpzy-vIrHÌDDE A'D[?T%dUN'=Ds˶.b24]zkvB:~??Zr)d8H[(NiYt^i*a N Ոԣ!>dwYNǯɇܱTbv^ PY?TO |N:KõyXyؙGKGGw<\_s47˦#8yq*o+˼ce}Եd9fm\X&j%7H]Lhyuy2WVO 5rB0;<҉dvfYfDm9^nIHLwM˄n}lȶgZ ʟ!i<;ɳߙR04!/U*7'm>YW [.d !˥ jqW$K&_9 v<,PŴFqF{0M,8}fWX|2 wr)q3qےyIԊ&// hJH74ՄE4AYۢa[>98еHhA̖}cFXe3AwATVIGh;t.p tYVLbRgn6=yšُc=˴ɍ}RF@#%O" uVcl_XV

Zav([9hpZo?K'eLR) 3n%:q,XP[ׁ_<V ''}cjR-Bcyث} '2U28`HAy7d{]!\*}+"Dp0^(IԜ b6OTq%.8Q"=0P;QBB:( Fgh6| /?EsB$2ag?[eCpc8tɬ ]|SH$kjiتKYdU(>P5y(Vbǐ1BދCxF 4x!'rl#E q=6O_/$>6o,v[ۖ~adɃ ,B?4Xg>뛨0BؗBtEwv$Yݤ#Դ"pt1oOB @mY˵<ݻFg6Otx Wȼx{dd d 2ƸG^GKn'O)^P/>!ea]K̾9|zwgĒꯓ$퇻C֐$5TK< B~URh: Tl>J15w|S|Hƈe[> f 鄙_1'k3sX\קr&x[,$}&{Čdo5FMBa &^E5 Odn:6 +Z\bES_H_ʋ}o^읭Oc H 7ۇM*L+hDSDMf v"@i,sV6E!O|FNYjdixO@.NAn&F\7a9j1H6󔞱2߽%;w\/_F\ݖ d;V#l5h zi2ִQhSpuլJXAE> " G"Ш3" ؿ37ܐ[v0w_!TA0b_# 5;G>r!A|,y d }ۃ65LEVTW߻YֽEsr=c> oTgKnU9 Z5m_.V!VYIA0S.9vZZPK!Hw>+fspkcspider-0.2.dist-info/RECORDG\.?EJ$a 9g~nݵ:5:j^+p͹ZmzmAE9yF.W4ё]Ӻ;YeK\ sXB+ͭO[F`2gڤ8]h/P֓b"U }:^wQ2s*S[:Ֆhn2n(k)@z1# CVwK B3{v|nv.ސćd xrλr7)Mn)fDzDz{_x]Zֳ{9Ou.Wզ!p!eBnT4U-np~;(ȪE۷`,hH G%]0朵c~}_q6n0IJ͂Α]9FO",S1JyEM3l Օ5#v #[źɫ5_)$>J)qdm3JC%)HȘh}0MC_O/`^d;q&sCx oq# | .<822=mO;(V64)e紮AxeaHީB$3P( #_\ `#r>rJ]Ru~ c#c 0/x1bߟz1;ayַ[@7( D̰E)[8h|^@'P"'PI^N5rtDJBF;FeƓPtܶ"9#:[ w+يsy"n@Ӥro:z]]c`d4`%rױ@ @Κ4 SCrڊ LB X+Gfu+(f CEC/uRRA ; a&FyVj[>V-|E^@׭F *MJ;/XYT"Ux9*1=}D?b/Ū3[oG"~tpQ 9cOMtϜu>3.R;Z(9;Hu),5]m5UõYpv2*R!f:f{\-GqN6' G=U oup·`.,UHYc>଎.T|C %r%󶂝B S;W/0 >Hfz7xu؝p:^q~ˣ2Eۨ+?bjħ<.F-&[U$r_z>8_'ĂrB- w,Y[MGqa$[ȇ<,BA I"EX./VOeq:y)ca<JldJ.V>EyĽGLb|qEZƊxqxU+5=>kee\uOCeW^ӽ~MPG$gkbm6x7h| b\UHi.tmPz뢬5^"|~'W˸]񌡑)5U&Bh I>gkC#KFF[JpjpnpuiP{]/E'r s7Y@T}<]R >OؕtN@՗[OFA խmLvto `yjpWTTj8&Hsն}d!CTjѭ<^uDVĿYܻ;ī3O;ŀX-k6s Af*%Slۗ5\f7u?1˗shDi:ijG:mz&3w:9,NPwU8 t 4rCc%Ns.=_v;ٿirEzQjC.Ƀ„yR<ԭؘx_Zo8?ъGַQǬ1 )7 Q.A5u!c=W%;L wzأ7by5 bZUnS?}'^qU`$؞ru ͗w|㣸{)/u|1.+ϕm!' }Xz_O< q;ke Ky!Uyf'ѕ`Uchﹻ͞Kn.]G9L;~x hJm0Ί,mΟǧ:0.GK \8nQHy^Q~ɑr0gfdg|r8lz˒>+s~yPS摝|::׻}/NIo{[Pv&X_K?>3+.s7v|ʔ[c8GZ\S5ƿq0<4+uk.Lmd M9ʔS@YJYaJ7#w7un2ȥcM3]{w5nGyv Ŗ|JE(U)o4MA 6RARfPSLWϣW#2@w6/ؠ1**pqu}+fURѦkWRT3p"h-|5 =3sr?hBpXum0b):69 ԥm6ĔB1܀%̲ҒRM?L/#\Ӻ U ?ϔ=[4M5H]dAbЕ݃.v3ih8k%LGKwq\wB$x/5_ }!3ڰMl^C:sojWm i8?9ZܹShw/I#$֤WO#}m~'Dn(<iCGE ):|>G9U)Vܳ-~X\jGgi3 XH:Fv-rV;v/j̟B ~aP(#4%72S_z]=[Ck|_l")ɜ:Pbޯ[g:e͘N^3q=5i<&;amak?%lҜ'нvʜy9ekQnO7KW?L?; ]$/CķL ,Lf ,i߃ngQՙ sf]m!K-\3^YE-u ӏ; {gg/ bݗp@1߯ϕE ;:f`fn^’n/@Ok|p_io lfvo'M3>bQ9^~爛*7|=i}9+|w|S_3dm}z}6r#{+Gex?9 D4U()zψ[],fo2N%Z;ʛb:{ xt\崇lRpti=m^:$7,`Az>3ճb^;SWkrm,;uu Sec_=6pOjH+wtmb|9ݡ6BX[@&yH_N* /u?SB+Y¹U.BK5fʢޙտ~ǑUT8$SS{Lz|\mk#?("RY-QE/k٠oqG?Se>AO;)-{Yޜ]m/SaJзK&H9blKJa*>Bq~u9+xvA\y#x#б&sXI$G%DqYvu'͋u;c}.F_\OX_zY'mӠ"l{w-ĩwTOc)>."~WM$~N/.dJQ˸ؔD㸸֣I*NnՌǸY+Ĩ'<@e:}5EoOE7l'ڧG=~=YN;fYc| b DpJ' ,ۓJۦ"Wnێe;^aK-2"}}_Qv [ix4tWV2 ]npw=>!g]0twZCn:3eUTgR}A[]`)4MA+qEY_T@Qb{h@0UnCtۀ"yܠo+CO[^5#>gy#XVh'g;Pv쐸&KS(W3k6|O;uH nE.,o@fh z޶}M>AC '^.yc z"M }_S18sGGD80ޠ/ z^}\:(֤H'Z>GDi=Nt&mk}Y'?>=]A]G'"[OaU)obߑ>&b`,XӁmHR A}> TL0LnlHmw ߾kˁ׻O]BgG螲ӁQeXbdgg-~x? ,p0 TRIƈǺ,y&?TpE׭;Y@ gQ0T-i}۾AD G?9xTJ޳cfsKjBrnK͖\Hltے;?.C—585l>ܨ  XafmV;+_pt-XM4/KXL g+riH\BH/{怉Q0*;>n"] N̟:s{1A&:񀚃ImNrU2,Ôa?@ <ɤ6C K] 92/?rE-!S-E*xD#ÄA:j=qXhGn1D΍]\&~0$#QEyD_0ȟ~tjTeop۱֝orCfg>0H2A{7;Xd Mchj!r''OhכSoV<\sR(ޟ9n Op('YfYOה8 Lhvz̈́xmbAq wr[N 24A^E=!34X*& Zgኮ9⡙nM'g3R"m޿py{fIG t’iK׿]~9}<d(ێ!M| gGVzc^h'NmMnz2BAI$#ZC95ʂr&Pm=h:-p"_=̩e<lczI(Ro \߁5`Wuk9/$&* d8/5*wPrcW%/jw)7 .Alby [E@e;^ފc)'+߄$QSfis<&!PEB^CxFro3ow'1Kѷ(> ͮNַbBR6{):χ3y,XuO!qro?]HF+@~?ăH#:/b̎IhAQRA6t;n}T(w3DMmYL섻ygt3#қ4Ys|э4}Ų w* U<և*m M AyƧ/>ZE_I,*J{AkzHt[ 8=*{/uߤSfHH3ީ:0\+SP?;<^_ɯ=~Sx1I4i5)O*۶5~j?~fFwͺQ{a?izA>9(!]]0@0tR%O! &a1,"I_#_ɟwnɵjjKO_uWHCxvd94,~yEvmg}u@9:27C?ƯG ԃ낧xacVgfiCZz⿈TTu`6X'r5OSbhF;Y& }rpx>@w!3]]Kˑ;^dzΟOWjX4AR#{H[c f&>׮9* ^s鉵P1rR*Q즦i{)~l5aOFҰ(ǙuQÙ }eƕeS.C0}ސl5zJĎm#3۰PfCX﫪];TyS[| RnR'*'¸Ϸ'8ƤOLu9h>ueSFUj:\l Vӽ}YaD6(zf5a sAmWBɅkqVke>0jxJ7/J̙gG95&HlAP Ήf7TpGISNPK!su̎spkcspider/__init__.pyPK!^0S>DD"spkcspider/apps/spider/__init__.pyPK!v6"6"Fspkcspider/apps/spider/admin.pyPK!i z #spkcspider/apps/spider/apps.pyPK!,]].spkcspider/apps/spider/auth.pyPK!z >spkcspider/apps/spider/conf.pyPK!. #Espkcspider/apps/spider/constants.pyPK!9KDjDj"Rspkcspider/apps/spider/contents.pyPK!g ,lspkcspider/apps/spider/context_processors.pyPK!R:_<;spkcspider/apps/spider/migrations/0004_auto_20190128_1349.pyPK!u11<Aspkcspider/apps/spider/migrations/0005_auto_20190128_2230.pyPK!9><vHspkcspider/apps/spider/migrations/0008_auto_20190225_2034.pyPK!G2<Mspkcspider/apps/spider/migrations/0009_auto_20190317_1405.pyPK!e%TRspkcspider/apps/spider/migrations/0010_assignedcontent_attached_to_primary_anchor.pyPK!U{{<Tspkcspider/apps/spider/migrations/0011_auto_20190331_1222.pyPK! <Yspkcspider/apps/spider/migrations/0012_auto_20190404_2116.pyPK!6=<_spkcspider/apps/spider/migrations/0013_auto_20190407_1313.pyPK!dx,ww<abspkcspider/apps/spider/migrations/0014_auto_20190407_1416.pyPK!U-c<2espkcspider/apps/spider/migrations/0015_auto_20190414_1743.pyPK!R  <gspkcspider/apps/spider/migrations/0016_auto_20190414_1743.pyPK!-yispkcspider/apps/spider/migrations/__init__.pyPK!0P)ispkcspider/apps/spider/models/__init__.pyPK!'%%kspkcspider/apps/spider/models/base.pyPK!u 6!!-~spkcspider/apps/spider/models/content_base.pyPK!W`"`")ãspkcspider/apps/spider/models/contents.pyPK!Ùĉ++,jspkcspider/apps/spider/models/protections.pyPK!i///%=spkcspider/apps/spider/models/user.pyPK!k@@%%"spkcspider/apps/spider/protections.pyPK![Cspkcspider/apps/spider/templates/spider_base/assignedcontent_form.htmlPK!22FxQspkcspider/apps/spider/templates/spider_base/assignedcontent_list.htmlPK!U  6spkcspider/apps/spider/templates/spider_base/base.htmlPK!;spkcspider/apps/spider/templates/spider_base/edit_form.htmlPK!7DHspkcspider/apps/spider/templates/spider_base/forms/widgets/info.htmlPK!m9r  K1spkcspider/apps/spider/templates/spider_base/forms/widgets/statebutton.htmlPK!%@||Ospkcspider/apps/spider/templates/spider_base/forms/widgets/subsectionstart.htmlPK!T Nspkcspider/apps/spider/templates/spider_base/forms/widgets/subsectionstop.htmlPK!#"Nspkcspider/apps/spider/templates/spider_base/forms/widgets/wrapped_select.htmlPK!UpP;spkcspider/apps/spider/templates/spider_base/forms/widgets/wrapped_textarea.htmlPK!!Mspkcspider/apps/spider/templates/spider_base/forms/widgets/wrapper_attrs.htmlPK!8) ) 6spkcspider/apps/spider/templates/spider_base/home.htmlPK!E;spkcspider/apps/spider/templates/spider_base/nouc_base.htmlPK!sm D)spkcspider/apps/spider/templates/spider_base/partials/base_form.htmlPK!wm ICspkcspider/apps/spider/templates/spider_base/partials/base_view_form.htmlPK!O@spkcspider/apps/spider/templates/spider_base/partials/content_variants_box.htmlPK!zBBFspkcspider/apps/spider/templates/spider_base/partials/form_errors.htmlPK!O!==DVspkcspider/apps/spider/templates/spider_base/partials/headerbar.htmlPK!jGspkcspider/apps/spider/templates/spider_base/partials/language_box.htmlPK!o Fspkcspider/apps/spider/templates/spider_base/partials/list_footer.htmlPK!K Ispkcspider/apps/spider/templates/spider_base/partials/modalpresenter.htmlPK!x' Ispkcspider/apps/spider/templates/spider_base/partials/token_lifetime.htmlPK!o66Vspkcspider/apps/spider/templates/spider_base/protections/authtoken_confirm_delete.htmlPK!%%B spkcspider/apps/spider/templates/spider_base/protections/base.htmlPK!x44MLspkcspider/apps/spider/templates/spider_base/protections/protection_form.htmlPK!OAAMspkcspider/apps/spider/templates/spider_base/protections/protection_item.htmlPK!A``Ispkcspider/apps/spider/templates/spider_base/protections/protections.htmlPK!G^spkcspider/apps/spider/templates/spider_base/protections/referring.htmlPK!  9k-spkcspider/apps/spider/templates/spider_base/uc_base.htmlPK!]UN0spkcspider/apps/spider/templates/spider_base/usercomponent_confirm_delete.htmlPK!r**DS9spkcspider/apps/spider/templates/spider_base/usercomponent_form.htmlPK!9Dcspkcspider/apps/spider/templates/spider_base/usercomponent_list.htmlPK!Y""Msspkcspider/apps/spider/templates/spider_base/usercomponent_list_fragment.htmlPK!3L[[;Jspkcspider/apps/spider/templates/spider_base/view_form.htmlPK!/spkcspider/apps/spider/templatetags/__init__.pyPK!l]1Kspkcspider/apps/spider/templatetags/basic_math.pyPK!t6< < 2spkcspider/apps/spider/templatetags/spider_base.pyPK!̸w4spkcspider/apps/spider/templatetags/spider_paging.pyPK!9Sspkcspider/apps/spider/templatetags/spider_protections.pyPK!d_Z1spkcspider/apps/spider/templatetags/spider_rdf.pyPK!x  spkcspider/apps/spider/urls.pyPK!*_$spkcspider/apps/spider/validators.pyPK!Z߰(_spkcspider/apps/spider/views/__init__.pyPK!U rmEmE+Uspkcspider/apps/spider/views/_components.pyPK!-'[\[\) spkcspider/apps/spider/views/_contents.pyPK!x& ~~%Tspkcspider/apps/spider/views/_core.pyPK!i'spkcspider/apps/spider/views/_tokens.pyPK!W#/#/!spkcspider/apps/spider/widgets.pyPK!EQQ+b#spkcspider/apps/spider_accounts/__init__.pyPK!x(#spkcspider/apps/spider_accounts/admin.pyPK!XR'3-spkcspider/apps/spider_accounts/apps.pyPK!R1 (.spkcspider/apps/spider_accounts/forms.pyPK!1j j :X9spkcspider/apps/spider_accounts/migrations/0001_initial.pyPK!kfEEspkcspider/apps/spider_accounts/migrations/0002_auto_20180810_1433.pyPK!4gCQGspkcspider/apps/spider_accounts/migrations/0003_spideruser_quota.pyPK!xG@EIspkcspider/apps/spider_accounts/migrations/0004_auto_20190109_1505.pyPK!7]]RMspkcspider/apps/spider_accounts/migrations/0005_spideruser_quota_usercomponents.pyPK!6Pspkcspider/apps/spider_accounts/migrations/__init__.pyPK!MDy~~)Qspkcspider/apps/spider_accounts/models.pyPK!^VR**@Xspkcspider/apps/spider_accounts/templates/registration/base.htmlPK!_`FNZspkcspider/apps/spider_accounts/templates/registration/logged_out.htmlPK!EAN\spkcspider/apps/spider_accounts/templates/registration/login.htmlPK!ZP.bspkcspider/apps/spider_accounts/templates/registration/password_change_done.htmlPK!jjPsdspkcspider/apps/spider_accounts/templates/registration/password_change_form.htmlPK!l/v  CKgspkcspider/apps/spider_accounts/templates/registration/profile.htmlPK!1B@Bhspkcspider/apps/spider_accounts/templates/registration/signup.htmlPK!1WBkspkcspider/apps/spider_accounts/templates/registration/thanks.htmlPK!^+'mspkcspider/apps/spider_accounts/urls.pyPK!<'w (9sspkcspider/apps/spider_accounts/views.pyPK!=MM)&~spkcspider/apps/spider_filets/__init__.pyPK!??&~spkcspider/apps/spider_filets/admin.pyPK!ՅCC%=spkcspider/apps/spider_filets/apps.pyPK![%Áspkcspider/apps/spider_filets/conf.pyPK!|&spkcspider/apps/spider_filets/forms.pyPK!}Q=spkcspider/apps/spider_filets/locale/de/LC_MESSAGES/django.poPK!z!8spkcspider/apps/spider_filets/migrations/0001_initial.pyPK!@~Jespkcspider/apps/spider_filets/migrations/0002_textfilet_non_public_edit.pyPK!Cspkcspider/apps/spider_filets/migrations/0003_auto_20180820_1409.pyPK! Cspkcspider/apps/spider_filets/migrations/0004_auto_20180821_0021.pyPK!0ϧCspkcspider/apps/spider_filets/migrations/0005_auto_20180919_1329.pyPK!3ŬH:spkcspider/apps/spider_filets/migrations/0006_textfilet_preview_words.pyPK!Cpspkcspider/apps/spider_filets/migrations/0007_auto_20181016_2002.pyPK!Q83Cspkcspider/apps/spider_filets/migrations/0008_auto_20181121_0749.pyPK!d?ջspkcspider/apps/spider_filets/migrations/0009_textfilet_push.pyPK!#  Cspkcspider/apps/spider_filets/migrations/0010_auto_20190403_0417.pyPK!Y:&&Cdspkcspider/apps/spider_filets/migrations/0011_auto_20190405_0045.pyPK!d//Cspkcspider/apps/spider_filets/migrations/0012_auto_20190419_0800.pyPK!4{spkcspider/apps/spider_filets/migrations/__init__.pyPK!!!'spkcspider/apps/spider_filets/models.pyPK!rttHspkcspider/apps/spider_filets/static/spider_filets/description_helper.jsPK!(~mmDspkcspider/apps/spider_filets/static/spider_filets/licensechooser.jsPK!?spkcspider/apps/spider_filets/templates/spider_filets/file.htmlPK!quDspkcspider/apps/spider_filets/templates/spider_filets/file_form.htmlPK!9Gspkcspider/apps/spider_filets/templates/spider_filets/license_info.htmlPK!?<spkcspider/apps/spider_filets/templates/spider_filets/text.htmlPK!A훑(7spkcspider/apps/spider_filets/widgets.pyPK!0II'spkcspider/apps/spider_keys/__init__.pyPK!??$spkcspider/apps/spider_keys/admin.pyPK!!˟  #!spkcspider/apps/spider_keys/apps.pyPK!>$nspkcspider/apps/spider_keys/forms.pyPK!u ~~;!spkcspider/apps/spider_keys/locale/de/LC_MESSAGES/django.poPK!+6m'spkcspider/apps/spider_keys/migrations/0001_initial.pyPK!f5E*spkcspider/apps/spider_keys/migrations/0002_0002_20190426_squashed.pyPK!f~SSE3spkcspider/apps/spider_keys/migrations/0002_anchorkey_anchorserver.pyPK!aA::A9spkcspider/apps/spider_keys/migrations/0003_auto_20190130_1137.pyPK!8==AJ=spkcspider/apps/spider_keys/migrations/0004_auto_20190221_2253.pyPK!KKDAspkcspider/apps/spider_keys/migrations/0005_remove_publickey_note.pyPK!x!  ACspkcspider/apps/spider_keys/migrations/0006_auto_20190425_2159.pyPK!2Ispkcspider/apps/spider_keys/migrations/__init__.pyPK! 267"7"%LJspkcspider/apps/spider_keys/models.pyPK!(,,:lspkcspider/apps/spider_keys/templates/spider_keys/key.htmlPK!\JA#Jnspkcspider/apps/spider_keys/urls.pyPK!LA$mospkcspider/apps/spider_keys/views.pyPK!?,II'sspkcspider/apps/spider_tags/__init__.pyPK!)$Wtspkcspider/apps/spider_tags/admin.pyPK!aO#uspkcspider/apps/spider_tags/apps.pyPK!_ %xspkcspider/apps/spider_tags/fields.pyPK!W͟++$spkcspider/apps/spider_tags/forms.pyPK!s4l!l!&spkcspider/apps/spider_tags/layouts.pyPK!22;spkcspider/apps/spider_tags/locale/de/LC_MESSAGES/django.poPK!{63spkcspider/apps/spider_tags/migrations/0001_initial.pyPK!nAspkcspider/apps/spider_tags/migrations/0002_auto_20180815_1128.pyPK!ƼA spkcspider/apps/spider_tags/migrations/0003_auto_20180815_1205.pyPK!g#zzA spkcspider/apps/spider_tags/migrations/0004_auto_20190117_1253.pyPK! Fh spkcspider/apps/spider_tags/migrations/0005_spidertag_updateable_by.pyPK!qqA spkcspider/apps/spider_tags/migrations/0006_auto_20190226_1853.pyPK!r /DO spkcspider/apps/spider_tags/migrations/0007_taglayout_description.pyPK!2D spkcspider/apps/spider_tags/migrations/__init__.pyPK!Clٌ33% spkcspider/apps/spider_tags/models.pyPK!9\&cB spkcspider/apps/spider_tags/signals.pyPK!r&!!?C spkcspider/apps/spider_tags/static/spider_tags/scheme_editor.jsPK!I@T spkcspider/apps/spider_tags/templates/spider_tags/edit_form.htmlPK!ZZH|W spkcspider/apps/spider_tags/templates/spider_tags/edit_preview_form.htmlPK!v22#;;% spkcspider/apps/verifier/constants.pyPK!edP P ! spkcspider/apps/verifier/forms.pyPK!> > % spkcspider/apps/verifier/functions.pyPK!) 8 spkcspider/apps/verifier/locale/de/LC_MESSAGES/django.poPK!_]J3 spkcspider/apps/verifier/migrations/0001_initial.pyPK!X;;> spkcspider/apps/verifier/migrations/0002_auto_20181031_2121.pyPK!x;p@Z spkcspider/apps/verifier/migrations/0003_1_linked_hashes_stub.pyPK!𔨆M spkcspider/apps/verifier/migrations/0003_dataverificationtag_linked_hashes.pyPK!> spkcspider/apps/verifier/migrations/0004_auto_20190225_2153.pyPK!wssT spkcspider/apps/verifier/migrations/0004_remove_dataverificationtag_linked_hashes.pyPK!^c"$$> spkcspider/apps/verifier/migrations/0005_auto_20190302_1741.pyPK!+:Lm spkcspider/apps/verifier/migrations/0006_verifysourceobject_update_secret.pyPK!/ spkcspider/apps/verifier/migrations/__init__.pyPK!/" spkcspider/apps/verifier/models.pyPK!fA spkcspider/apps/verifier/templates/spider_verifier/dv_detail.htmlPK!잖zz? spkcspider/apps/verifier/templates/spider_verifier/dv_form.htmlPK!k ? spkcspider/apps/verifier/templates/spider_verifier/dv_wait.htmlPK!g x-''  spkcspider/apps/verifier/urls.pyPK!F`Hb7b7$y spkcspider/apps/verifier/validate.pyPK!!0 spkcspider/apps/verifier/views.pyPK!-#CC H spkcspider/celery.pyPK! :h%%J spkcspider/settings/.gitignorePK!'+J spkcspider/settings/__init__.pyPK!{Jk spkcspider/settings/debug.pyPK!r~r spkcspider/urls.pyPK!*Rvy spkcspider/wsgi.pyPK! 0t)) , spkcspider-0.2.dist-info/LICENSEPK!HڽTU spkcspider-0.2.dist-info/WHEELPK!H!y .!# spkcspider-0.2.dist-info/METADATAPK!Hw>+f۝ spkcspider-0.2.dist-info/RECORDPKX]V