Remove support for @graph and @reverse within jsonld signature checks

main
bashrc 2026-04-23 16:15:47 +01:00
parent ca1debf437
commit 81ff2a4cee
1 changed files with 21 additions and 233 deletions

View File

@ -124,7 +124,6 @@ KEYWORDS = [
'@default', '@default',
'@embed', '@embed',
'@explicit', '@explicit',
'@graph',
'@id', '@id',
'@index', '@index',
'@language', '@language',
@ -132,7 +131,6 @@ KEYWORDS = [
'@omitDefault', '@omitDefault',
'@preserve', '@preserve',
'@requireAll', '@requireAll',
'@reverse',
'@set', '@set',
'@type', '@type',
'@value', '@value',
@ -788,16 +786,8 @@ class JsonLdProcessor(object):
if ctx_length == 1: if ctx_length == 1:
ctx = ctx[0] ctx = ctx[0]
# add context and/or @graph # add context
if _is_array(compacted): if _is_object(compacted) and has_context:
# use '@graph' keyword
kwgraph = self._compact_iri(active_ctx, '@graph')
graph = compacted
compacted = {}
if has_context:
compacted['@context'] = ctx
compacted[kwgraph] = graph
elif _is_object(compacted) and has_context:
# reorder keys so @context is first # reorder keys so @context is first
graph = compacted graph = compacted
compacted = {} compacted = {}
@ -894,11 +884,7 @@ class JsonLdProcessor(object):
# do expansion # do expansion
expanded = self._expand(active_ctx, None, document, options, False) expanded = self._expand(active_ctx, None, document, options, False)
# optimize away @graph with no other properties if expanded is None:
if (_is_object(expanded) and '@graph' in expanded and
len(expanded) == 1):
expanded = expanded['@graph']
elif expanded is None:
expanded = [] expanded = []
# normalize to an array # normalize to an array
@ -937,7 +923,7 @@ class JsonLdProcessor(object):
return flattened return flattened
# compact result (force @graph option to true, skip expansion) # compact result (force @graph option to true, skip expansion)
options['graph'] = True options['graph'] = False
options['skipExpansion'] = True options['skipExpansion'] = True
try: try:
compacted = self.compact(flattened, ctx, options) compacted = self.compact(flattened, ctx, options)
@ -1037,7 +1023,7 @@ class JsonLdProcessor(object):
try: try:
# compact result (force @graph option to True, skip expansion, # compact result (force @graph option to True, skip expansion,
# check for linked embeds) # check for linked embeds)
options['graph'] = True options['graph'] = False
options['skipExpansion'] = True options['skipExpansion'] = True
options['link'] = {} options['link'] = {}
options['activeCtx'] = True options['activeCtx'] = True
@ -1771,9 +1757,6 @@ class JsonLdProcessor(object):
{'expanded': element, 'compacted': rval}) {'expanded': element, 'compacted': rval})
return rval return rval
# FIXME: avoid misuse of active property as an expanded property?
inside_reverse = (active_property == '@reverse')
rval = {} rval = {}
if options['link'] and '@id' in element: if options['link'] and '@id' in element:
@ -1806,35 +1789,6 @@ class JsonLdProcessor(object):
{'propertyIsArray': is_array}) {'propertyIsArray': is_array})
continue continue
# handle @reverse
if expanded_property == '@reverse':
# recursively compact expanded value
compacted_value = self._compact(
active_ctx, '@reverse', expanded_value, options)
# handle double-reversed properties
for compacted_property, value in \
list(compacted_value.items()):
mapping = active_ctx['mappings'].get(
compacted_property)
if mapping and mapping['reverse']:
container = JsonLdProcessor.get_context_value(
active_ctx, compacted_property, '@container')
use_array = (container == '@set' or
not options['compactArrays'])
JsonLdProcessor.add_value(
rval, compacted_property, value,
{'propertyIsArray': use_array})
del compacted_value[compacted_property]
if len(compacted_value.keys()) > 0:
# use keyword alias and add value
alias = self._compact_iri(
active_ctx, expanded_property)
JsonLdProcessor.add_value(rval, alias, compacted_value)
continue
# handle @index # handle @index
if expanded_property == '@index': if expanded_property == '@index':
# drop @index if inside an @index container # drop @index if inside an @index container
@ -1850,8 +1804,7 @@ class JsonLdProcessor(object):
# skip array processing for keywords that aren't # skip array processing for keywords that aren't
# @graph or @list # @graph or @list
if expanded_property != '@graph' and \ if expanded_property != '@list' and \
expanded_property != '@list' and \
_is_keyword(expanded_property): _is_keyword(expanded_property):
# use keyword alias and add value as is # use keyword alias and add value as is
alias = self._compact_iri(active_ctx, expanded_property) alias = self._compact_iri(active_ctx, expanded_property)
@ -1865,7 +1818,7 @@ class JsonLdProcessor(object):
if len(expanded_value) == 0: if len(expanded_value) == 0:
item_active_property = self._compact_iri( item_active_property = self._compact_iri(
active_ctx, expanded_property, expanded_value, active_ctx, expanded_property, expanded_value,
vocab=True, reverse=inside_reverse) vocab=True, reverse=False)
JsonLdProcessor.add_value( JsonLdProcessor.add_value(
rval, item_active_property, [], rval, item_active_property, [],
{'propertyIsArray': True}) {'propertyIsArray': True})
@ -1875,7 +1828,7 @@ class JsonLdProcessor(object):
# compact property and get container type # compact property and get container type
item_active_property = self._compact_iri( item_active_property = self._compact_iri(
active_ctx, expanded_property, expanded_item, active_ctx, expanded_property, expanded_item,
vocab=True, reverse=inside_reverse) vocab=True, reverse=False)
container = JsonLdProcessor.get_context_value( container = JsonLdProcessor.get_context_value(
active_ctx, item_active_property, '@container') active_ctx, item_active_property, '@container')
@ -1938,14 +1891,13 @@ class JsonLdProcessor(object):
else: else:
# use an array if compactArrays flag is false, # use an array if compactArrays flag is false,
# @container is @set or @list, value is an empty # @container is @set or @list, value is an empty
# array, or key is @graph # array
is_array = (not options['compactArrays'] or is_array = (not options['compactArrays'] or
container == '@set' or container == '@set' or
container == '@list' or container == '@list' or
(_is_array(compacted_item) and (_is_array(compacted_item) and
len(compacted_item) == 0) or len(compacted_item) == 0) or
expanded_property == '@list' or expanded_property == '@list')
expanded_property == '@graph')
# add compact value # add compact value
JsonLdProcessor.add_value( JsonLdProcessor.add_value(
@ -2017,10 +1969,6 @@ class JsonLdProcessor(object):
active_ctx = self._process_context( active_ctx = self._process_context(
active_ctx, element['@context'], options) active_ctx, element['@context'], options)
# expand the active property
expanded_active_property = self._expand_iri(
active_ctx, active_property, vocab=True)
rval = {} rval = {}
for key, value in sorted(element.items()): for key, value in sorted(element.items()):
if key == '@context': if key == '@context':
@ -2037,12 +1985,6 @@ class JsonLdProcessor(object):
continue continue
if _is_keyword(expanded_property): if _is_keyword(expanded_property):
if expanded_active_property == '@reverse':
raise JsonLdError(
'Invalid JSON-LD syntax; a keyword cannot be used as '
'a @reverse property.',
'jsonld.SyntaxError', {'value': value},
code='invalid reverse property map')
if expanded_property in rval: if expanded_property in rval:
raise JsonLdError( raise JsonLdError(
'Invalid JSON-LD syntax; colliding keywords detected.', 'Invalid JSON-LD syntax; colliding keywords detected.',
@ -2065,14 +2007,6 @@ class JsonLdProcessor(object):
if expanded_property == '@type': if expanded_property == '@type':
_validate_type_value(value) _validate_type_value(value)
# @graph must be an array or an object
if (expanded_property == '@graph' and
not (_is_object(value) or _is_array(value))):
raise JsonLdError(
'Invalid JSON-LD syntax; "@graph" must not be an '
'object or an array.', 'jsonld.SyntaxError',
{'value': value}, code='invalid @graph value')
# @value must not be an object or an array # @value must not be an object or an array
if (expanded_property == '@value' and if (expanded_property == '@value' and
(_is_object(value) or _is_array(value))): (_is_object(value) or _is_array(value))):
@ -2102,48 +2036,6 @@ class JsonLdProcessor(object):
'a string.', 'jsonld.SyntaxError', {'value': value}, 'a string.', 'jsonld.SyntaxError', {'value': value},
code='invalid @index value') code='invalid @index value')
# reverse must be an object
if expanded_property == '@reverse':
if not _is_object(value):
raise JsonLdError(
'Invalid JSON-LD syntax; "@reverse" value must be '
'an object.', 'jsonld.SyntaxError', {'value': value},
code='invalid @reverse value')
expanded_value = self._expand(
active_ctx, '@reverse', value, options, inside_list)
# properties double-reversed
if '@reverse' in expanded_value:
for rprop, rvalue in expanded_value['@reverse'].items():
JsonLdProcessor.add_value(
rval, rprop, rvalue,
{'propertyIsArray': True})
# merge in all reversed properties
reverse_map = rval.get('@reverse')
for property, items in expanded_value.items():
if property == '@reverse':
continue
if reverse_map is None:
reverse_map = rval['@reverse'] = {}
JsonLdProcessor.add_value(
reverse_map, property, [],
{'propertyIsArray': True})
for item in items:
if _is_value(item) or _is_list(item):
raise JsonLdError(
'Invalid JSON-LD syntax; "@reverse" '
'value must not be an @value or an @list',
'jsonld.SyntaxError',
{'value': expanded_value},
code='invalid reverse property value')
JsonLdProcessor.add_value(
reverse_map, property, item,
{'propertyIsArray': True})
continue
container = JsonLdProcessor.get_context_value( container = JsonLdProcessor.get_context_value(
active_ctx, key, '@container') active_ctx, key, '@container')
@ -2169,8 +2061,6 @@ class JsonLdProcessor(object):
is_list = (expanded_property == '@list') is_list = (expanded_property == '@list')
if is_list or expanded_property == '@set': if is_list or expanded_property == '@set':
next_active_property = active_property next_active_property = active_property
if is_list and expanded_active_property == '@graph':
next_active_property = None
expanded_value = self._expand( expanded_value = self._expand(
active_ctx, next_active_property, value, options, active_ctx, next_active_property, value, options,
is_list) is_list)
@ -2196,23 +2086,6 @@ class JsonLdProcessor(object):
'@list': JsonLdProcessor.arrayify(expanded_value) '@list': JsonLdProcessor.arrayify(expanded_value)
} }
# merge in reverse properties
mapping = active_ctx['mappings'].get(key)
if mapping and mapping['reverse']:
reverse_map = rval.setdefault('@reverse', {})
expanded_value = JsonLdProcessor.arrayify(expanded_value)
for item in expanded_value:
if _is_value(item) or _is_list(item):
raise JsonLdError(
'Invalid JSON-LD syntax; "@reverse" value must '
'not be an @value or an @list.',
'jsonld.SyntaxError', {'value': expanded_value},
code='invalid reverse property value')
JsonLdProcessor.add_value(
reverse_map, expanded_property, item,
{'propertyIsArray': True})
continue
# add value for property, use an array exception for certain # add value for property, use an array exception for certain
# key words # key words
use_array = (expanded_property not in ['@index', '@id', '@type', use_array = (expanded_property not in ['@index', '@id', '@type',
@ -2283,9 +2156,10 @@ class JsonLdProcessor(object):
rval = None rval = None
# drop certain top-level objects that do not occur in lists # drop certain top-level objects that do not occur in lists
if (_is_object(rval) and not options.get('keepFreeFloatingNodes') and if _is_object(rval) and \
not inside_list and (active_property is None or not options.get('keepFreeFloatingNodes') and \
expanded_active_property == '@graph')): not inside_list and \
active_property is None:
# drop empty object or top-level @value/@list, # drop empty object or top-level @value/@list,
# or object with only @id # or object with only @id
if (count == 0 or '@value' in rval or '@list' in rval or if (count == 0 or '@value' in rval or '@list' in rval or
@ -2309,14 +2183,6 @@ class JsonLdProcessor(object):
# add all non-default graphs to default graph # add all non-default graphs to default graph
default_graph = graphs['@default'] default_graph = graphs['@default']
for graph_name, node_map in graphs.items():
if graph_name == '@default':
continue
graph_subject = default_graph.setdefault(
graph_name, {'@id': graph_name, '@graph': []})
graph_subject.setdefault('@graph', []).extend(
[v for k, v in sorted(node_map.items())
if not _is_subject_reference(v)])
# produce flattened output # produce flattened output
return [value for key, value in sorted(default_graph.items()) return [value for key, value in sorted(default_graph.items())
@ -2615,12 +2481,6 @@ class JsonLdProcessor(object):
result = [] result = []
for subject, node in sorted(default_graph.items()): for subject, node in sorted(default_graph.items()):
if subject in graph_map:
graph = node['@graph'] = []
for s, n in sorted(graph_map[subject].items()):
# only add full subjects to top-level
if not _is_subject_reference(n):
graph.append(n)
# only add full subjects to top-level # only add full subjects to top-level
if not _is_subject_reference(node): if not _is_subject_reference(node):
result.append(node) result.append(node)
@ -2796,9 +2656,8 @@ class JsonLdProcessor(object):
type_ = JsonLdProcessor.get_context_value( type_ = JsonLdProcessor.get_context_value(
active_ctx, active_property, '@type') active_ctx, active_property, '@type')
# do @id expansion (automatic for @graph) # do @id expansion
if (type_ == '@id' or (expanded_property == '@graph' if type_ == '@id':
and _is_string(value))):
return {'@id': self._expand_iri(active_ctx, value, base=True)} return {'@id': self._expand_iri(active_ctx, value, base=True)}
# do @id expansion w/vocab # do @id expansion w/vocab
if type_ == '@vocab': if type_ == '@vocab':
@ -3080,31 +2939,6 @@ class JsonLdProcessor(object):
if property == '@id': if property == '@id':
continue continue
# handle reverse properties
if property == '@reverse':
referenced_node = {'@id': name}
reverse_map = input_['@reverse']
for reverse_property, items in reverse_map.items():
for item in items:
item_name = item.get('@id')
if _is_bnode(item):
item_name = namer.get_name(item_name)
self._create_node_map(
item, graphs, graph, namer, item_name)
JsonLdProcessor.add_value(
graphs[graph][item_name], reverse_property,
referenced_node,
{'propertyIsArray': True, 'allowDuplicate': False})
continue
# recurse into graph
if property == '@graph':
# add graph subjects map entry
graphs.setdefault(name, {})
g = graph if graph == '@merged' else name
self._create_node_map(objects, graphs, g, namer)
continue
# copy non-@type keywords # copy non-@type keywords
if property != '@type' and _is_keyword(property): if property != '@type' and _is_keyword(property):
if property == '@index' and '@index' in subject \ if property == '@index' and '@index' in subject \
@ -3755,12 +3589,7 @@ class JsonLdProcessor(object):
prefs = [] prefs = []
# determine prefs for @id based on whether value compacts to term # determine prefs for @id based on whether value compacts to term
if ((type_or_language_value == '@id' or if type_or_language_value == '@id' and _is_subject_reference(value):
type_or_language_value == '@reverse') and
_is_subject_reference(value)):
# prefer @reverse first
if type_or_language_value == '@reverse':
prefs.append('@reverse')
# try to compact value to a term # try to compact value to a term
term = self._compact_iri( term = self._compact_iri(
active_ctx, value['@id'], None, vocab=True) active_ctx, value['@id'], None, vocab=True)
@ -3825,12 +3654,8 @@ class JsonLdProcessor(object):
type_or_language = '@language' type_or_language = '@language'
type_or_language_value = '@null' type_or_language_value = '@null'
if reverse:
type_or_language = '@type'
type_or_language_value = '@reverse'
containers.append('@set')
# choose most specific term that works for all elements in @list # choose most specific term that works for all elements in @list
elif _is_list(value): if _is_list(value):
# only select @list containers if @index is NOT in value # only select @list containers if @index is NOT in value
if '@index' not in value: if '@index' not in value:
containers.append('@list') containers.append('@list')
@ -4012,15 +3837,13 @@ class JsonLdProcessor(object):
return rval return rval
# value is a subject reference # value is a subject reference
expanded_property = self._expand_iri(
active_ctx, active_property, vocab=True)
type_ = JsonLdProcessor.get_context_value( type_ = JsonLdProcessor.get_context_value(
active_ctx, active_property, '@type') active_ctx, active_property, '@type')
compacted = self._compact_iri( compacted = self._compact_iri(
active_ctx, value['@id'], vocab=(type_ == '@vocab')) active_ctx, value['@id'], vocab=(type_ == '@vocab'))
# compact to scalar # compact to scalar
if type_ in ['@id', '@vocab'] or expanded_property == '@graph': if type_ in ('@id', '@vocab'):
return compacted return compacted
rval = {} rval = {}
@ -4091,32 +3914,7 @@ class JsonLdProcessor(object):
# create new mapping # create new mapping
mapping = active_ctx['mappings'][term] = {'reverse': False} mapping = active_ctx['mappings'][term] = {'reverse': False}
if '@reverse' in value: if '@id' in value:
if '@id' in value:
raise JsonLdError(
'Invalid JSON-LD syntax; an @reverse term definition must '
'not contain @id.', 'jsonld.SyntaxError',
{'context': local_ctx}, code='invalid reverse property')
reverse = value['@reverse']
if not _is_string(reverse):
raise JsonLdError(
'Invalid JSON-LD syntax; @context @reverse value must be '
'a string.', 'jsonld.SyntaxError', {'context': local_ctx},
code='invalid IRI mapping')
# expand and add @id mapping
id_ = self._expand_iri(
active_ctx, reverse, vocab=True, base=False,
local_ctx=local_ctx, defined=defined)
if not _is_absolute_iri(id_):
raise JsonLdError(
'Invalid JSON-LD syntax; @context @reverse value must be '
'an absolute IRI or a blank node identifier.',
'jsonld.SyntaxError', {'context': local_ctx},
code='invalid IRI mapping')
mapping['@id'] = id_
mapping['reverse'] = True
elif '@id' in value:
id_ = value['@id'] id_ = value['@id']
if not _is_string(id_): if not _is_string(id_):
raise JsonLdError( raise JsonLdError(
@ -4202,13 +4000,6 @@ class JsonLdProcessor(object):
'must be one of the following: @list, @set, @index, or ' 'must be one of the following: @list, @set, @index, or '
'@language.', 'jsonld.SyntaxError', '@language.', 'jsonld.SyntaxError',
{'context': local_ctx}, code='invalid container mapping') {'context': local_ctx}, code='invalid container mapping')
if (mapping['reverse'] and container != '@index' and
container != '@set' and container is not None):
raise JsonLdError(
'Invalid JSON-LD syntax; @context @container value for '
'an @reverse type definition must be @index or @set.',
'jsonld.SyntaxError', {'context': local_ctx},
code='invalid reverse property')
# add @container to mapping # add @container to mapping
mapping['@container'] = container mapping['@container'] = container
@ -4493,11 +4284,8 @@ class JsonLdProcessor(object):
entry = container_map.setdefault( entry = container_map.setdefault(
container, {'@language': {}, '@type': {}}) container, {'@language': {}, '@type': {}})
# term is preferred for values using @reverse
if mapping['reverse']:
entry['@type'].setdefault('@reverse', term)
# term is preferred for values using specific type # term is preferred for values using specific type
elif '@type' in mapping: if '@type' in mapping:
entry['@type'].setdefault(mapping['@type'], term) entry['@type'].setdefault(mapping['@type'], term)
# term is preferred for values using specific language # term is preferred for values using specific language
elif '@language' in mapping: elif '@language' in mapping: