class TagLayoutForm(forms.ModelForm): layout = JsonField(widget=SchemeWidget(registry.fields.keys())) default_verifiers = MultipleOpenChoiceField(widget=ListWidget( items={"format_type": "url"}, item_label=_("Url to Verifier")), required=False) class Meta: model = TagLayout fields = ["name", "unique", "layout", "default_verifiers"] usertaglayout = None def __init__(self, usertaglayout, **kwargs): self.usertaglayout = usertaglayout super().__init__(**kwargs) def _save_m2m(self): self.instance.usertag = self.usertaglayout.associated self.instance.name = self.usertaglayout.associated.name self.instance.description = self.usertaglayout.associated.description 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 clean(self): self.usertaglayout.full_clean() return super().clean() def save(self, commit=True): if commit: self.usertaglayout.save() self._save_m2m() else: self.save_m2m = self._save_m2m return self.usertaglayout
class TagLayoutAdminForm(forms.ModelForm): layout = JsonField(widget=SchemeWidget(registry.fields.keys())) default_verifiers = MultipleOpenChoiceField(widget=ListWidget( items={"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 FileForm(LicenseForm): request = None file = forms.FileField() key_list = JsonField(widget=forms.HiddenInput(), initial=None, required=False) quota_fields = {"key_list": None} quota_fields.update(LicenseForm.quota_fields) 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)) initial2 = {} if kwargs.get("instance", None): initial2.update(kwargs["instance"].free_data) initial2.update(initial) super().__init__(initial=initial2, **kwargs) if self.instance.pk: self.initial["file"] = \ self.instance.associated.attachedfiles.filter( name="file" ).first() if self.initial["file"]: self.initial["file"] = self.initial["file"].file 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, "SPIDER_MAX_FILE_SIZE_STAFF", None) else: self.fields["file"].max_length = getattr(settings, "SPIDER_MAX_FILE_SIZE", None) if request.is_owner: # self.user = request.user return self.fields["file"].editable = False self.fields["name"].editable = False # for SPIDER_UPLOAD_FILTER self.request = request def clean_key_list(self): ret = self.cleaned_data["key_list"] if not ret: return None if not isinstance(ret, dict): raise forms.ValidationError(_("key_list is not a dictionary")) for val in ret.values(): # key is extended by padding + base64 so extra buffer if len(val) > 8000: raise forms.ValidationError(_("key has invalid length")) return ret def clean(self): ret = super().clean() if "file" not in ret: return ret # has to raise ValidationError get_settings_func("SPIDER_UPLOAD_FILTER", "spkcspider.apps.spider.functions.allow_all_filter")( self.request, ret["file"], self) return ret def get_prepared_attachements(self): if "file" not in self.changed_data: return {} f = None if self.instance.pk: f = self.instance.associated.attachedfiles.filter( name="file").first() if not f: f = AttachedFile(unique=True, name="file", content=self.instance.associated) f.file = self.cleaned_data["file"] return {"attachedfiles": [f]}
class TextForm(LicenseForm): text = SanitizedHtmlField(widget=TrumbowygWidget(), localize=True) editable_from = forms.ModelMultipleChoiceField( queryset=UserComponent.objects.all(), required=False, initial=[], widget=SelectizeWidget(allow_multiple_selected=True, attrs={"style": "min-width: 150px; width:100%"})) push = forms.BooleanField(required=False, initial=False, help_text=_("Improve ranking of this Text.")) key_list = JsonField(widget=forms.HiddenInput(), initial=None, required=False) file = forms.FileField(required=False, initial=None) free_fields = {"push": False} free_fields.update(LicenseForm.free_fields) quota_fields = {"key_list": None} quota_fields.update(LicenseForm.quota_fields) class Media: 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)) initial2 = {} if kwargs.get("instance", None): initial2.update(kwargs["instance"].free_data) initial2.update(initial) super().__init__(initial=initial2, **kwargs) if self.instance.pk and not self.instance.quota_data.get("key_list"): self.initial["text"] = \ self.instance.associated.attachedblobs.filter( name="text" ).first() if self.initial["text"] is not None: self.initial["text"] = \ self.initial["text"].as_bytes.decode("utf8") if scope in ("add", "update"): self.fields["editable_from"].help_text = \ _( "Allow editing from selected components. " "Requires protection strength >=%s." ) % settings.SPIDER_MIN_STRENGTH_EVELATION query = models.Q(pk=self.instance.associated.usercomponent_id) if scope == "update": query |= models.Q( contents__references=self.instance.associated) query &= models.Q( strength__gte=settings.SPIDER_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" if self.instance.quota_data.get("key_list"): self.fields["file"].editable = allow_edit self.fields["key_list"].editable = allow_edit del self.fields["text"] else: self.fields["text"].editable = allow_edit del self.fields["file"] del self.fields["key_list"] # sources stay enabled self.fields["sources"].editable = allow_edit def clean_key_list(self): ret = self.cleaned_data["key_list"] if not ret: return None if not isinstance(ret, dict): raise forms.ValidationError(_("key_list is not a dictionary")) for val in ret.values(): # key is extended by padding + base64 so extra buffer if len(val) > 8000: raise forms.ValidationError(_("key has invalid length")) return ret def clean(self): ret = super().clean() if (self.instance.pk and self.instance.quota_data.get("key_list")): if not ret.get("key_list") or not ret.get("file"): raise forms.ValidationError(_("Cannot switch to unencrypted")) if not ret.get("key_list") and ret.get("file"): raise forms.ValidationError( _("Can only use file in connection with key_list")) if "editable_from" in self.fields: self.instance.free_data["editable_from"] = \ list(self.cleaned_data["editable_from"].values_list( "id", flat=True )) return ret def get_prepared_attachements(self): changed_data = self.changed_data if "text" not in changed_data and "file" not in changed_data: return {} b = None if self.instance.pk: b = self.instance.associated.attachedblobs.filter( name="text").first() if not b: b = AttachedBlob(unique=True, name="text", content=self.instance.associated) if self.cleaned_data.get("file"): b.blob = self.cleaned_data["file"].read() else: b.blob = self.cleaned_data["text"].encode("utf-8") return {"attachedblobs": [b]}
class AddressForm(DataContentForm): name = forms.CharField() setattr( name, "spkc_datatype", XSD.base64Binary ) url = forms.CharField() setattr( url, "spkc_datatype", XSD.base64Binary ) nonce = forms.CharField( widget=forms.HiddenInput(), required=False, initial="" ) setattr( nonce, "spkc_datatype", XSD.base64Binary ) key_list = JsonField( initial=None, required=False ) quota_fields = { "name": "", "url": "", "nonce": None, "key_list": {} } def clean(self): ret = super().clean() if not self.cleaned_data.get("nonce"): keys = self.instance.usercomponent.contents.filter( ctype__name="PublicKey", info__contains="\x1epubkeyhash=" ).exclude( info__contains="\x1ethirdparty\x1e" ) if not keys: raise forms.ValidationError( _( "No keys found and no keys specified" ) ) aes_key = os.urandom(32) self.cleaned_data["nonce"] = os.urandom(13) cipher = Cipher( algorithms.AES(aes_key), modes.GCM(self.cleaned_data["nonce"]), backend=default_backend() ) self.cleaned_data["name"] = \ base64.b64encode(cipher.encryptor().update( self.cleaned_data["name"].encode("utf8") )) self.cleaned_data["url"] = \ base64.b64encode(cipher.encryptor().update( self.cleaned_data["url"].encode("utf8") )) self.cleaned_data["key_list"] = {} for key in keys: k = key.content.get_key_ob() if k: algo_hash = key.getlist("pubkeyhash", amount=1)[0] algo = getattr( hashes, algo_hash.split("=", 1)[0].upper() )() enc = k.encrypt( aes_key, padding.OAEP( mgf=padding.MGF1(algorithm=algo), algorithm=self.hash_algo, label=None ) ) self.cleaned_data["key_list"][ algo_hash ] = base64.b64encode(enc).decode("ascii") if not self.cleaned_data.get("key_list"): self.add_error( None, forms.ValidationError( _( "key_list is missing: either specify complete " "or leave nonce out" ) ) ) return ret
class MessageForm(DataContentForm): own_hash = forms.CharField( widget=forms.HiddenInput(), required=False ) fetch_url = forms.CharField(disabled=True, required=False, initial="") was_retrieved = forms.BooleanField( disabled=True, required=False, initial=False, help_text=_( "Retrieved by recipient" ) ) # by own client(s) received = forms.BooleanField( disabled=True, required=False, initial=False, help_text=_( "Already received by own client" ) ) key_list = JsonField( initial=dict, widget=forms.Textarea() ) tokens = MultipleOpenChoiceField(initial=list, disabled=True) amount_tokens = forms.IntegerField(min_value=0, initial=1, required=False) encrypted_content = forms.FileField() hash_algorithm = forms.CharField( disabled=False, required=False ) setattr(hash_algorithm, "hashable", False) first_run = False free_fields = {"hash_algorithm": settings.SPIDER_HASH_ALGORITHM.name} quota_fields = {"fetch_url": None, "key_list": dict} def __init__(self, request, **kwargs): super().__init__(**kwargs) if self.instance.id: self.fields["hash_algorithm"].disabled = True self.initial["tokens"] = \ [ token.token for token in self.instance.associated.attached_tokens.all() ] # hack around for current bad default JsonField widget self.initial["key_list"] = json.dumps(self.initial["key_list"]) setattr(self.fields["key_list"], "spkc_datatype", XSD.string) self.initial["fetch_url"] = \ "{}://{}{}?".format( request.scheme, request.get_host(), reverse( "spider_messages:message" ) ) self.initial["encrypted_content"] = \ self.instance.associated.attachedfiles.get( name="encrypted_content" ).file setattr( self.fields["encrypted_content"], "download_url", self.instance.associated.get_absolute_url("download") ) setattr(self.fields["encrypted_content"], "hashable", False) setattr( self.fields["encrypted_content"], "view_form_field_template", "spider_messages/partials/fields/view_encrypted_content.html" ) self.initial["was_retrieved"] = \ self.instance.associated.smarttags.filter( name="received", target=None ).exists() keyhashes = self.data.getlist("keyhash") keyhashes_q = info_or( pubkeyhash=keyhashes, hash=keyhashes, info_fieldname="target__info" ) if keyhashes: self.initial["received"] = \ self.instance.asspciated.smarttags.filter( name="received" ).filter(keyhashes_q).count() == len(keyhashes) else: del self.fields["received"] del self.fields["amount_tokens"] self.first_run = False else: del self.fields["fetch_url"] del self.fields["was_retrieved"] del self.fields["received"] del self.fields["tokens"] if not self.initial.get("hash_algorithm"): self.initial["hash_algorithm"] = \ settings.SPIDER_HASH_ALGORITHM.name self.initial["was_retrieved"] = False self.first_run = True def clean_hash_algorithm(self): ret = self.cleaned_data["hash_algorithm"] if ret and not hasattr(hashes, ret.upper()): raise forms.ValidationError( _("invalid hash algorithm") ) return ret def clean(self): super().clean() if ( "hash_algorithm" in self.initial and not self.cleaned_data.get("hash_algorithm") ): self.cleaned_data["hash_algorithm"] = \ self.initial["hash_algorithm"] if self.first_run: postbox = \ self.instance.associated.usercomponent.contents.filter( ctype__name="PostBox" ).first() if postbox: self.instance.associated.attached_to_content = postbox else: self.add_error(None, forms.ValidationError( _("This usercomponent has no Postbox") )) return self.cleaned_data def is_valid(self): # cannot update retrieved message if ( self.initial["was_retrieved"] and ( "encrypted_content" in self.changed_data or "key_list" in self.changed_data ) ): return False return super().is_valid() def get_prepared_attachements(self): ret = {} changed_data = self.changed_data # create or update keys if ( "key_list" in changed_data or "encrypted_content" in changed_data ): self.initial["received"] = False if self.first_run: keyhashes_q = info_or( hash=self.cleaned_data["key_list"], pubkeyhash=self.cleaned_data["key_list"] ) ret["smarttags"] = [ SmartTag( content=self.instance.associated, unique=True, name="unread", target=t, data={"hash": t.getlist("hash", 1)[0]} ) for t in self.instance.associated.usercomponent.contents.filter( # noqa: E501 ctype__name="PublicKey" ).filter(keyhashes_q) ] ret["smarttags"].append( SmartTag( content=self.instance.associated, unique=True, name="unread", target=None ) ) else: ret["smarttags"] = self.instance.associated.smarttags.all() for smartkey in ret["smarttags"]: h1 = None h2 = None if smartkey.target: h1 = smartkey.target.getlist("hash", 1)[0] h2 = smartkey.target.getlist("pubkeyhash", 1)[0] if self.cleaned_data["own_hash"] in {h1, h2}: self.initial["received"] = True smartkey.name = "received" # don't allow new tokens after the first run if self.first_run: # update own references to add messagecontent # without updating PostBox ret["referenced_by"] = self.instance.associated.attached_to_content ret["attached_tokens"] = [ AuthToken( persist=0, usercomponent=self.instance.associated.usercomponent, attached_to_content=self.instance.associated, extra={ # don't allow anything than accessing content via # view "ids": [] } ) for _ in range(self.cleaned_data.get("amount_tokens", 1)) ] # self.initial["tokens"] = [ # x.token for x in ret["attached_tokens"] # ] if "encrypted_content" in self.changed_data: f = None if self.instance.pk: f = self.instance.associated.attachedfiles.filter( name="encrypted_content" ).first() if not f: f = AttachedFile( unique=True, name="encrypted_content", content=self.instance.associated ) f.file = self.cleaned_data["encrypted_content"] ret["attachedfiles"] = [f] return ret
class PostBoxForm(DataContentForm): max_receive_size = forms.IntegerField( initial=None, required=False, help_text=_( "maximal message size received" ) ) setattr(max_receive_size, "hashable", False) only_persistent = forms.BooleanField(required=False) setattr(only_persistent, "hashable", False) # TODO: functionality, currently nothing logically. Cleanup shared = forms.BooleanField(required=False, initial=True) setattr(shared, "hashable", False) keys = ContentMultipleChoiceField( queryset=AssignedContent.objects.filter( info__contains="\x1etype=PublicKey\x1e" ).filter( info__contains="\x1epubkeyhash=" ), to_field_name="id", ) setattr(keys, "hashable", True) webreferences = ContentMultipleChoiceField( queryset=AssignedContent.objects.filter( ctype__name="WebReference" ), to_field_name="id", disabled=True, required=False ) message_objects = ContentMultipleChoiceField( queryset=AssignedContent.objects.filter( ctype__name="MessageContent" ), to_field_name="id", disabled=True, required=False ) setattr(message_objects, "hashable", False) attestation = forms.CharField( label=_("PostBox Attestation"), help_text=_( "Re-sign with every active key for activating new key " "or removing a key" ), required=False, widget=forms.TextInput( attrs={ "readonly": True, "style": "width:100%" } ) ) setattr(attestation, "hashable", True) setattr( attestation, "spkc_datatype", XSD.base64Binary ) setattr( attestation, "view_form_field_template", "spider_messages/partials/fields/view_combined_keyhash.html" ) hash_algorithm = forms.CharField( widget=forms.HiddenInput(), disabled=True, required=False ) setattr(hash_algorithm, "hashable", False) signatures = JsonField( widget=SignatureWidget( item_label=_("Signature"), hash_label=_("Hash") ) ) setattr(signatures, "hashable", False) setattr( signatures, "view_form_field_template", "spider_messages/partials/fields/view_signatures.html" ) extract_pubkeyhash = re.compile("\x1epubkeyhash=([^\x1e=]+)=([^\x1e=]+)") free_fields = { "only_persistent": False, "shared": True, # TODO: specify default_mode "max_receive_size": None } def __init__(self, scope, request, **kwargs): super().__init__(**kwargs) self.initial["hash_algorithm"] = settings.SPIDER_HASH_ALGORITHM.name self.fields["keys"].queryset = \ self.fields["keys"].queryset.filter( usercomponent=self.instance.associated.usercomponent ) if scope in {"view", "raw", "list"} and request.is_owner: self.initial["webreferences"] = \ self.instance.associated.attached_contents.filter( ctype__name="WebReference" ) self.initial["message_objects"] = \ self.instance.associated.attached_contents.filter( ctype__name="MessageContent" ) keyhashes = request.POST.getlist("keyhash") if self.data.get("view_all", "") != "true" and keyhashes: self.initial["message_objects"] = \ self.initial["message_objects"].filter( info_or(pubkeyhash=keyhashes, hash=keyhashes) ) if scope != "view": self.initial["webreferences"] = \ self.initial["webreferences"].values_list("id", flat=True) self.initial["message_objects"] = \ self.initial["message_objects"].values_list( "id", flat=True ) else: del self.fields["webreferences"] del self.fields["message_objects"] if scope not in {"add", "update", "export"}: del self.fields["keys"] if self.instance.id: if "keys" in self.fields: self.initial["keys"] = self.instance.associated.smarttags.filter( # noqa: E501 name="key" ).values_list("target", flat=True) signatures = self.instance.associated.smarttags.filter( name="key" ) mapped_hashes = map( lambda x: self.extract_pubkeyhash.search(x).group(2), signatures.values_list( "target__info", flat=True ) ) mapped_hashes = sorted(mapped_hashes) hasher = get_hashob() for mh in mapped_hashes: hasher.update(binascii.unhexlify(mh)) hasher = hasher.finalize() self.initial["attestation"] = \ base64.b64encode(hasher).decode("ascii") self.initial["signatures"] = [ { None: x.target, "hash": x.target.getlist("hash", 1)[0], "signature": x.data["signature"] } for x in signatures.all() ] setattr( self.fields["signatures"], "spkc_datatype", { None: spkcgraph["Content"], "hash": XSD.string, "signature": XSD.string } ) else: del self.fields["attestation"] del self.fields["signatures"] def clean_signatures(self): ret = self.cleaned_data["signatures"] if len(ret) == 0: raise forms.ValidationError( _("Requires signatures") ) try: for i in ret: i["hash"] and i["signature"] except KeyError: raise forms.ValidationError( _("invalid signature format") ) return ret def get_prepared_attachements(self): ret = { "smarttags": [] } if self.instance.id: smarttags = self.instance.associated.smarttags.filter( name="key" ) else: smarttags = SmartTag.objects.none() signatures = dict( map( lambda x: (x["hash"], x.get("signature") or ""), self.cleaned_data.get("signatures", []) ) ) for pubkey in self.cleaned_data.get("keys", []): smarttag = smarttags.filter(target=pubkey).first() if not smarttag: smarttag = SmartTag( content=self.instance.associated, unique=True, name="key", target=pubkey, data={ "signature": None } ) if pubkey.getlist("hash", 1)[0] in signatures: # shown hash of key, it includes some extra information smarttag.data["signature"] = \ signatures[pubkey.getlist("hash", 1)[0]] elif pubkey.getlist("pubkeyhash", 1)[0] in signatures: # in case only the pubkeyhash is available it must be accepted # this is the case for automatic repair smarttag.data["signature"] = \ signatures[pubkey.getlist("pubkeyhash", 1)[0]] ret["smarttags"].append(smarttag) return ret
class ReferenceForm(DataContentForm): url = forms.URLField(max_length=400) key_list = JsonField( widget=forms.Textarea() ) setattr(key_list, "spkc_datatype", XSD.string) hash_algorithm = forms.CharField( required=False, disabled=False ) setattr(hash_algorithm, "hashable", False) create = False free_fields = {"hash_algorithm": settings.SPIDER_HASH_ALGORITHM.name} quota_fields = {"url": None, "key_list": dict} def __init__(self, create=False, **kwargs): self.create = create super().__init__(**kwargs) if not self.initial.get("hash_algorithm"): self.initial["hash_algorithm"] = \ settings.SPIDER_HASH_ALGORITHM.name if not self.create: self.fields["hash_algorithm"].disabled = True def clean_hash_algorithm(self): ret = self.cleaned_data["hash_algorithm"] if ret and not hasattr(hashes, ret.upper()): raise forms.ValidationError( _("invalid hash algorithm") ) return ret def clean_key_list(self): ret = self.cleaned_data["key_list"] for val in ret.values(): # key is extended by padding + base64 so extra buffer if len(val) > 8000: raise forms.ValidationError( _("key has invalid length") ) return ret def clean(self): ret = super().clean() if "key_list" not in self.cleaned_data: return ret if ( "hash_algorithm" in self.initial and not self.cleaned_data.get("hash_algorithm") ): self.cleaned_data["hash_algorithm"] = \ self.initial["hash_algorithm"] q = info_or( pubkeyhash=list(self.cleaned_data["key_list"].keys()), info_fieldname="target__info" ) # get from postbox key smarttags with signature self.cleaned_data["signatures"] = \ self.instance.associated.attached_to_content.smarttags.filter( name="key" ).filter(q) # check if key_list matches with signatures; # otherwise MITM injection of keys are possible if ( self.cleaned_data["signatures"].count() != len(self.cleaned_data["key_list"]) ): self.add_error("key_list", forms.ValidationError( _("invalid keys"), code="invalid_keys" )) return ret def get_prepared_attachements(self): ret = {} if self.create: # update own references to add webrerence # without updating PostBox ret["referenced_by"] = self.instance.associated.attached_to_content ret["smarttags"] = [ SmartTag( content=self.instance.associated, unique=True, name="unread", target=h.target, free=True ) # signatures are prepared and only hold keys in key_list for h in self.cleaned_data["signatures"] ] return ret