solid / authorization-panel

Github repository for the Solid Authorization Panel
MIT License
19 stars 20 forks source link

restrict acl:mode, accessToClass, agentClass range definition in ontology #187

Open bblfish opened 3 years ago

bblfish commented 3 years ago

The current ACL ontology has

acl:mode a rdf:Property;
    :comment "A mode of access such as read or write.";
    :domain acl:Authorization;
    :label "access mode";
    :range :Class .

The :range here is defined as any instance of rdfs:Class

This is defined as

rdfs:Class a rdfs:Class ;
    rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
    rdfs:label "Class" ;
    rdfs:comment "The class of classes." ;
    rdfs:subClassOf rdfs:Resource .

That is the range of :mode allows the class of Flying Saucers as range too. Clearly this should be restricted to the class of subclasses of acl:Access. which is defined as

acl:Access a :Class;
     :comment "Any kind of access to a resource. Don't use this, use R W and RW";
     acl:label "access"@en .

And indeed that is how Read, Write, Control, ... are defined.

acl:Read a :Class;
    :comment "The class of read operations";
    :label "read"@en;
    :subClassOf acl:Access .

One suggestion would be to fix this with

acl:mode :range [ a owl:Restriction ;
   owl:onProperty   :subClassOf ;
   owl:hasValue  acl:Access
 ] .  

As described in the comment below the same reasoning applies to the range definitions of acl:accessToClass and acl:agentClass.

elf-pavlik commented 3 years ago

That is the range of :mode allows the class of Flying Saucers as range too.

rdf:rage doesn't actually restrict or allow anything it just enables reasoner to infer that object of the statement also has certain class.

https://github.com/solid/web-access-control-spec#authorization-schema has clear way of restricting valid values using ShEx

cl:mode [acl:Read acl:Write acl:Control]+ ;
bblfish commented 3 years ago

In WAC the ontology is the primary normative document we always worked from. The ShEx statements are restrictions added post-hoc that are not agreed upon and are clearly stated to be non-normative.

Furthermore, as shown in the discussion of :agentClass in the wac-acp-diff PR we see how Shape expressions can easily be overly-restrictive and wrong. The acl:mode ShEx may capture a certain usage, but cannot be used to restrict evolution of the ontology.

The ACL ontology allows new acl:Access subclasses such as acl:Delete or acl:Create, and over time these have been proposed and argued for and against. The point is not to argue here that we should use those, just that the ontology clearly means that to be possible, but does not state it clearly enough.

rdf:range doesn't actually restrict or allow anything it just enables reasoner to infer that object of the statement also has certain class.

Indeed, therdf:range on acl:mode should allow the reasoner to infer that the object of the statement is a subclass of acl:Authorization but it does not. The proposed fix would allow that to be inferred.

csarven commented 3 years ago

https://github.com/solid/web-access-control-spec#authorization-schema has clear way of restricting valid values using ShEx

Cool. Nice try. Please read the whole section.. starting with the very first sentence:

This section is non-normative. ( Issue: https://github.com/solid/specification/issues/169 )

I've accepted the PR https://github.com/solid/web-access-control-spec/pull/77 from Eric (which you were also a participating in) conditionally.

elf-pavlik commented 3 years ago

Indeed, the rdf:range on acl:mode should allow the reasoner to infer that the object of the statement is a subclass of acl:Authorization but it does not. The proposed fix would allow that to be inferred.

I don't see how that helps with case suggested by your previous statement:

That is the range of :mode allows the class of Flying Saucers as range too.

One could still use ex:FlyingSaurcers as value. Maybe I misunderstood OWL reasoning but your restriction would just imply ex:FlyingSaurcers rdfs:subClassOf acl:Access which technically seems fine as long as reasoner doesn't get statements like

 []  rdf:type     owl:AllDisjointClasses ;
     owl:members  ( acl:Access  ex:FlyingSaurcers ) . 

In other words I don't think it can address https://github.com/solid/specification/issues/169 while a Data Shape, ether ShEx or SHACL, does.

I don't have objections to having suggested OWL restriction added to the vocab. I just don't see where it would come useful.

bblfish commented 3 years ago

While we are at it we could also fix acl:accessToClass and acl:agentClass in the same way. These are currently defined as:

 acl:accessToClass  a rdf:Property;
     :comment "A class of information resources to which access is being granted.";
     :domain acl:Authorization;
     :label "to all in";
     :range :Class .

acl:agentClass  a rdf:Property;
    :comment "A class of persons or social entities to being given the right";
    :domain acl:Authorization;
    :label "agent class";
    :range :Class .         

But it would be more correct for them to be:

 acl:accessToClass rdf:range [ a owl:Restriction ;
   owl:onProperty :subClassOf ;
   owl:hasValue  ldp:Resource
 ] .  

 acl:agentClass   rdf:range [ a owl:Restriction ;
   owl:onProperty :subClassOf ;
   owl:hasValue  foaf:Agent
 ] .  
bblfish commented 3 years ago

@elf-pavlik:

I don't have objections to having suggested OWL restriction added to the vocab.

Good.

I just don't see where it would come useful.

It allows us to think more clearly about modeling issues. For example, in the case of acl:agentClass the ontology allows usages such as that described in issue 176, allowing us to describe classes of agents by attribute such as age, possession of a credential, etc... Those are clearly allowed by the ontology but not by the proposed ShEx rules. This can lead to the false belief that the ACL ontology is limited in ways it is not. See for example the assumption that WAC is limited in ability to reuse policies.

elf-pavlik commented 3 years ago

In https://github.com/solid/authorization-panel/pull/178#discussion_r587447559

@bblfish: note: the acl Ontology would be a lot clearer if it restricted the range of acl:mode to the subclasses of acl:Access. See issue #187

And in initial comment here

@bblfish: That is the range of :mode allows the class of Flying Saucers as range too.

In on of later comments

@bblfish: Furthermore, as shown in the discussion of :agentClass in the wac-acp-diff PR we see how Shape expressions can easily be overly-restrictive and wrong.

I just want to make sure that we all are on the same page. Neither rdfs:range or rdfs:domain restrict or allow anything. It just implies something specific by providing grounds for reasoner to infer additional statements that were not explicitly asserted.

Even if we use class defined using owl:Restriction for rdfs:range or rdfs:domain when defining a predicate it still doesn't serve as any form of restriction or allowance on valid values for object or subject respectively in statements using that predicate.

I don't think we can say that data shapes definitions are more restrictive and RDFS (range & domain) are less restrictive. The later have nothing to do with restricting valid values so we can't directly compare them.

bblfish commented 3 years ago

@elf-pavlik

Neither rdfs:range or rdfs:domain restrict or allow anything. It just implies something specific by providing grounds for reasoner to infer additional statements that were not explicitly asserted.

I disagree that those do not restrict anything. Say we define the infinite set of numbers divisible by 3 using the following description in mathematical notation, assuming we could do the same in RDF

 Tr = { x |  x % 3 == 0 }

This set includes the numbers {0, 3, 6, 9, ... }. Now, with the current ontology I can state that

:x acl:mode :Tr . 

from which we can infer that :Tr :subClassOf :Class which is true, since it :Tr is a set of numbers.

But what the proposed change would allow us to conclude is that

:Tr :subClassOf acl:Access.

which is false, since a set of numbers is not an access mode. Indeed there is no interpretation where we have a reasonable definition of acl:Access where that is true.

So for people who value true statements, that means that the second option is out of bounds. As such it does restrict what it is possible to express truthfully.

This restriction is very helpful, even if it is not fully automatiseable: for one it helps make the intent of the ontology clearer. For example it makes it clear that acl:Delete or acl:Create would be quite reasonable things to have as the object of an acl:mode statement. I believe those modes have been proposed recently for ACP we should be clear that the ACL ontology is compatible with such extensions. (But not with :Tr as the object of :mode - not everything goes).

The non-normative ShEx on the other hand state that

<#authShape> {
  acl:mode [acl:Read acl:Write acl:Control]+ ;
}

which does restrict that objects of acl:mode to only Read, Write and Control. So the ShEx here again over-constrain the way the ontology can be used.

Note: I am not to here taking a position on whether or not one should add new access modes.

matthieubosquet commented 3 years ago

I think it is fine to infer that :x is an rdfs:subClassOf acl:Access if :x is the object in a statement where acl:mode is the predicate.

That would be simply expressed by amending the ACL ontology statement acl:mode rdfs:range rdfs:Class to acl:mode rdfs:range acl:Access.

@bblfish I believe that your proposed change would actually mean that :x as defined above would be infered as a rdfs:subclassOf a blank node that happens to be an owl:Restriction. So I don't think it is correct.

bblfish commented 3 years ago

I think it is fine to infer that :x is an rdfs:subClassOf acl:Access if :x is the object in a statement where acl:mode is the predicate.

ok, great! That is the main thing I was hoping to get agreement on.

That would be simply expressed by amending the ACL ontology statement acl:mode rdfs:range rdfs:Class to acl:mode rdfs:range acl:Access.

I had considered that but the problem I think is that the range of acl:mode is not an instance of an acl:Access, but a subclass of acl:Access. That is acl:Read is defined as

 acl:Read     a :Class;
         :comment "The class of read operations";
         :label "read"@en;
         :subClassOf acl:Access .

So if you look at how foaf:knows is defined

   foaf:knows     a rdf:Property,
                owl:ObjectProperty;
         :domain foaf:Person;
         :range foaf:Person.

Then you use it like this:

<#Jim> a foaf:Person.
:i foaf:knows <#Jim>.

Here the object of the above foaf:knows instance <#jim> is an instance of a Person, not a subclass of Person. Subclass of foaf:Person could be :Man, :Woman, :Child, etc....

So what I was hoping to say with

acl:mode :range [ a owl:Restriction ;
   owl:onProperty   :subClassOf ;
   owl:hasValue  acl:Access
 ] .  

is that the range of acl:mode is anything that is a :subClassOf the class acl:Access.

@bblfish I believe that your proposed change would actually mean that :x as defined above would be infered as a rdfs:subclassOf a blank node that happens to be an owl:Restriction. So I don't think it is correct.

I think I have it right, but I think it definitely needs careful checking. Looking at the primer with Turtle descriptions enabled (must be done manually).

matthieubosquet commented 3 years ago

The triple P rdfs:range C states that P is an instance of the class rdf:Property, that C is an instance of the class rdfs:Class and that the resources denoted by the objects of triples whose predicate is P are instances of the class C. -- https://www.w3.org/TR/rdf-schema/#ch_range

Therefore if you say :p rdfs:range :ParentClass and :x :p :y, you get the triple :x a :ParentClass inferred.

A triple of the form: C1 rdfs:subClassOf C2 states that C1 is an instance of rdfs:Class, C2 is an instance of rdfs:Class and C1 is a subclass of C2. The rdfs:subClassOf property is transitive. -- https://www.w3.org/TR/rdf-schema/#ch_subclassof

So similarly, if you say :ChildrenClass rdfs:subClassOf :ParentClass and :x a :ChildrenClass, you get the triple :x a :ParentClass inferred.

I guess what you're talking about might be inferring :ChildrenClass rdfs:subClassOf :ParentClass if :x :p :ChildrenClass which I don't think is very doable (and not what would result from a blank node object in an rdfs:range statement).

I don't see the text in the OWL 2 primer that suggests you can use blank nodes in the way you describe with rdfs:range. Is it something you might quote or pinpoint?

bblfish commented 3 years ago

The triple P rdfs:range C states that P is an instance of the class rdf:Property, that C is an instance of the class rdfs:Class and that the resources denoted by the objects of triples whose predicate is P are instances of the class C. -- https://www.w3.org/TR/rdf-schema/#ch_range

Therefore if you say :p rdfs:range :ParentClass and :x :p :y, you get the triple :x a :ParentClass inferred.

A triple of the form: C1 rdfs:subClassOf C2 states that C1 is an instance of rdfs:Class, C2 is an instance of rdfs:Class and C1 is a subclass of C2. The rdfs:subClassOf property is transitive. -- https://www.w3.org/TR/rdf-schema/#ch_subclassof

So similarly, if you say :ChildrenClass rdfs:subClassOf :ParentClass and :x a :ChildrenClass, you get the triple :x a :ParentClass inferred.

yes. Agree with all the above. What we want to specify is the range of :mode, where it is used like this:

<#Rule> :mode :Read

:Read is a subclass of acl:Access, so the range of :mode has to be sets of classes, not sets of individuals.

I guess what you're talking about might be inferring :ChildrenClass rdfs:subClassOf :ParentClass if :x :p :ChildrenClass which I don't think is very doable (and not what would result from a blank node object in an rdfs:range statement).

I don't see the text in the OWL 2 primer that suggests you can use blank nodes in the way you describe with rdfs:range. Is it something you might quote or pinpoint?

Here is an example from the the OWL Primer

:Parent  owl:equivalentClass  [
   rdf:type            owl:Restriction ;
   owl:onProperty      :hasChild ;
   owl:someValuesFrom  :Person
 ] .

That is the class :Parent is the set of all things that have a relation :hasChild to some :Person. That is an owl:Restriction defines a class, via relations that objects have to other objects. It is denoted by a blank node, but that is for some complex logical reason I would need to study to understand fully. That is why one uses the owl:equivalentClass relation there to name that class.

I could make it clearer by doing the same and name the class of access modes.

 :ClassOfAccessModes owl:equivalentClass [ a owl:Restriction ;
   owl:onProperty   :subClassOf ;
   owl:hasValue  acl:Access
 ] .  

acl:mode :range  :ClassOfAccessModes .

:ClassOfAccessModes is not a particularly good name, but I hope it gets the idea across.

matthieubosquet commented 3 years ago

The owl:equivalentClass expressed as an owl:Restriction over owl:someValuesFrom is described as "existential quantification" which allows us to infer, for example, as per the OWL Primer that someone that is a parent has at least one child. So indicating a potentially untold/unknown statement.


The predicate you use in your owl:Restriction axiom: "owl:hasValue"; is meant to describe classes of individuals that are related to one particular individual via a predicate. In other words, by defining the owl:equivalentClass of rdfs:subClassOf acl:Access, you are defining the class of subclasses of acl:Access.

However:

...the subclass relationship between classes is transitive. Besides this, it is also reflexive, meaning that every class is its own subclass... -- https://www.w3.org/TR/2012/REC-owl2-primer-20121211/#Class_Hierarchies

An axiom EquivalentClasses( CE1 CE2 ) is equivalent to the following two axioms: SubClassOf( CE1 CE2 ) SubClassOf( CE2 CE1 ) -- https://www.w3.org/TR/owl2-syntax/#Equivalent_Classes

Maybe the most important bit is that the subclass relationship is reflexive and that every class is its own subclass. Therefore, the class of subclasses of acl:Access is acl:Access.

There is no need for an owl:equivalentClass over an owl:Restriction here.

I think what we want is: acl:mode rdfs:range acl:Access.

bblfish commented 3 years ago

you are defining the class of subclasses of acl:Access.

yes. This is expressed in functional programming as the difference between a function that has a range of A and one that has as range Set[A] ie Sets of As. In mathematics it is the difference between a range of A and a range of the PowerSet of A, noted 𝒫(A). Note that the sets of a Powerset are all ordered by subset inclusion βŠ† which is transitive and reflexive, so that if A βŠ† B and B βŠ† C then A βŠ† C. You can order the subsets of a Powerset by subset inclusion with the empty set at the bottom and the full set at the top. This forms a category.

It is a bit tricky to see how to model this in a type safe language like Scala. But perhaps the following gives an idea. The idea is that Read specifies a subclass of Access and Work a subclass of Agent.

trait AcessControlRule {
   type Mode <: Access
   type A <: Agent

   def allow(agent: A, accessTo: Uri, mode: Mode): Boolean
}

trait Access {
   def request(req: HttpRequest): Boolean
}
class Read extends Access {
    def request(req: HttpRequest): Boolean = req.method == GET
}
class Write extends Access {
    def request(req: HttpRequest): Boolean = req.method == PUT
}

object ReadAccessOnDocs {
   type Mode  = Read
   type A = Work

   def allow(agent: A, accessTo: Uri, mode: Mode): Boolean = accesstTo.path.startsWith("/docs/")
}

But of course it would not make sense to do this in a statically typed way. Instead on would have a function Access => Boolean. Perhaps something like this:

val Read: Request => Boolean  = (req: Request) => req.method == `GET`

But the set of functions A => Boolean is the Powerset of A.

matthieubosquet commented 3 years ago

Let me try to ask a simple question: Are you trying to express that the range is all the subclasses of acl:Access but not acl:Access itself?

bblfish commented 3 years ago

No, the range is the Powerset of acl:Access, ie. 𝒫(Access) not Access. An element of 𝒫(Access) is a set like Read, not an instance like an individual request event. On this reading, it would be ok to have

<#auth> :mode acl:Access.

which could be a way of expressing the authorization is enabled for "All modes of access". This is ok because acl:Access ∈ 𝒫(acl:Access) just like acl:Read ∈ 𝒫(acl:Access).

TallTed commented 3 years ago

[@bblfish] This is ok because acl:Access ∈ 𝒫(acl:Access) just like acl:Read ∈ 𝒫(acl:Access).

If I'm understanding the rest of this right, it seems that last should be acl:Read ∈ 𝒫(acl:Read), not acl:Read ∈ 𝒫(acl:Access)...?

bblfish commented 3 years ago

@TallTed it is also true that acl:Read ∈ 𝒫(acl:Read), but my point was to say that :ClassOfAccessModes is really equivalent to 𝒫(acl:Access), which is I think expressed by the following:

:ClassOfAccessModes owl:equivalentClass [ a owl:Restriction ;
   owl:onProperty   :subClassOf ;
   owl:hasValue  acl:Access
 ] . 

The reason I come to this conclusion is that acl:Read, acl:Write and acl:Control are all subclasses of acl:Access in the ontology. So that would be compatible with the object ?obj of a <#x> :mode ?obj relation being any subclass of acl:Access.

matthieubosquet commented 3 years ago

I see nothing relevant distinguishing :ClassOfAccessModes from acl:Access.

The only thing you gain is a couple of inferences that are probably not useful or welcome. To be clear, neither rdfs:range nor owl:Restrictions are actual restrictions, they are classifiers.

With acl:mode rdfs:range acl:Access, if :x acl:mode :Y, then :Y a acl:Access.

With your axiom, if :Y rdfs:subClassOf acl:Access AND :x acl:mode :Y, then :Y a :ClassOfAccessModes as well as :Y a _:BlankNodeOfTheRestrictionClass. Besides, the first statement holding means that :Y a acl:Access (since :Y is a subclass of acl:Access).

I don't see how that's an improvement and it is confusing.

the object ?obj of a <#x> :mode ?obj relation being any subclass of acl:Access.

Yes, absolutely. That is exactly what acl:mode rdfs:range acl:Access expresses.


EDIT: And I mean, I get how the idea would be nice if it were an actual restriction. But I don't see how useful or fitting it is in this ontology/in order to draw classification inferences.

bblfish commented 3 years ago

I see nothing relevant distinguishing :ClassOfAccessModes from acl:Access.

Do you see a difference between A and 𝒫(A)? It's the same difference.

Say you have the type Person and the type 𝒫(Person) . In the first case members of Person are actual people like you and me. E.g. Henry: Person In the second case we have groups of people, E.g. Adults: 𝒫(Person). Those are two different types.

With your axiom, if :Y rdfs:subClassOf acl:Access AND :x acl:mode :Y, then :Y a :ClassOfAccessModes as well as :Y a _:BlankNodeOfTheRestrictionClass. Besides, the first statement holding means that :Y a acl:Access (since :Y is a subclass of acl:Access).

(Let's ignore the blank node comment that a good reasoner will resolve)

What you are saying is that :Y rdfs:subClassOf acl:Access implies :Y a acl:Access. But that is to confuse membership (written ∈) and inclusion (written βŠ†). If we have a set of 3 Friends F = { Alice, Ben, Camille} then Alice ∈ F but not {Alice, Ben} ∈ F, even if {Alice, Ben} βŠ† F.

On the other hand {Alice, Ben} ∈ 𝒫(F). That is because 𝒫(F) = { {}, {Alice}, {Ben}, {Camille}, {Alice, Ben}, {Alice, Camille}, {Ben, Camille}, {Alice, Ben, Camille}} These sets are related by inclusion as show in the diagram on the Powerset wikipedia page.

matthieubosquet commented 3 years ago

Do you see a difference between A and 𝒫(A)?

Yes, and everything you say makes sense from a functional programming perspective.

What you are saying is that :Y rdfs:subClassOf acl:Access implies :Y a acl:Access. But that is to confuse membership (written ∈) and inclusion (written βŠ†).

It's not an implication or a confusion, it's basic inference. It's how RDF works.

RDF is not a strongly typed functional programming language and rdfs:range is a simple classifier, not a restriction. All it does is help you infer some statements.


Can you translate with a few statements what you are trying to do in RDF?

We have currently a few Access Modes acl:Read, acl:Write, acl:Append and acl:Control they all happen to be of rdf:type acl:Access and acl:Append is also of rdf:type acl:Write. We can also imagine having a future Access Mode x:Y and when used in a statement of the form :x acl:mode x:Y, then an inference engine would deduct that x:Y rdf:type acl:Access.

What is your expected inferencing behaviour?

bblfish commented 3 years ago

Do you see a difference between A and 𝒫(A)?

Yes, and everything you say makes sense from a functional programming perspective.

What you are saying is that :Y rdfs:subClassOf acl:Access implies :Y a acl:Access. But that is to confuse membership (written ∈) and inclusion (written βŠ†).

It's not an implication or a confusion, it's basic inference. It's how RDF works.

Ok, so I think we need to go straight to the source here. Let's look at the inference rules from RDF 1.1 Semantics.

We have as rule 9, translated to N3 for readability the inference

{ ?xxx rdfs:subClassOf ?yyy .
   ?zzz rdf:type ?xxx . } => {?zzz rdf:type ?yyy}

This is different from the rule you are asserting which would be written

{ ?xxx rdfs:subClassOf ?yyy . } => {?xxx rdf:type ?yyy}

Rule 9, states that the instances of the subClass are instances of the superclass. ie that

x  βŠ† y & z ∈ x => z ∈ y

This is not the same as stating that subclasses of the superclass are members of the superclass.

matthieubosquet commented 3 years ago

Henry, thank you for your patience. I have to say that you are right. I didn't think the distinction would hold, but it does.

Given a graph:

prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix : <https://example.com/>

:mode rdfs:range :Access .
:x :mode :Read .

The following statements hold:

@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix : <https://example.com/> .

:Access
  a rdfs:Class, rdfs:Resource;
  rdfs:subClassOf rdfs:Resource, :Access .

:Read
  a rdfs:Resource, :Access . # The Read class is inferred to be of rdf:type :Access

:mode
  a rdf:Property, rdfs:Resource;
  rdfs:range :Access;
  rdfs:subPropertyOf :mode .

:x
  a rdfs:Resource;
  :mode :Read .

Given a graph:

prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix : <https://example.com/>

:mode rdfs:range [
    a owl:Restriction ;
    owl:onProperty   rdfs:subClassOf ;
    owl:hasValue  :Access ;
 ] .
:x :mode :Read .

The following statements hold:

@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix : <https://example.com/> .

_:node0
  a rdfs:Class, rdfs:Resource, owl:Restriction;
  rdfs:subClassOf _:node0, rdfs:Resource;
  owl:hasValue :Access;
  owl:onProperty rdfs:subClassOf .

:Access
  a _:node0, rdfs:Class, rdfs:Resource;
  rdfs:subClassOf rdfs:Resource, :Access .

:Read
  a _:node0, rdfs:Class, rdfs:Resource; # The Read class is inferred to be of rdf:type _:node0 (not :Access)
  rdfs:subClassOf rdfs:Resource, :Access, :Read . # The Read class is inferred to be rdfs:subClassOf :Access

:x
  a rdfs:Resource;
  :mode :Read .