ditto
ditto copied to clipboard
Introduce policy decision API
For our UI, we'd like an endpoint that we can call to check if we should enable/disable certain elements based on policies. As briefly discussed on gitter with @thjaeckle, that endpoint could look something like:
POST /api/2/policies/{policyId}/actions/checkPermissions
with a payload like
{
"resource": "thing:/features/X/properties/Y",
"id": "org.eclipse.ditto:mythingId",
"permissions": [ "READ" ],
}
The response could be something like
[
{
"permission": "READ",
"allowed": true/false
}
]
That would require you to have the policyId at hand, potentially having to fetch the Thing prior to using this api. It does make it generic to use for any kind of resource (thing, policy, messages)
@vhdirk I don't get why you added the "id"
of the Thing in the payload.
You already define by addressing the policy via the "policyId"
in the HTTP path, which policy to check. And as inside of the policy there is no "link" to a special Thing ID, this makes no sense (the relation is the other way around, the thing points to the policy).
Basically what I think you need:
- You have a Thing "namespace:thing-1" which uses policy "namespace:policy-1"
- You check if you have
"READ"
permission on "namespace:thing-1" by doingPOST /api/2/policies/namespace:policy-1/actions/checkPermissions
-
{ "resource": "thing:/", "permissions": [ "READ" ] }
-
- Response could IMO be much simpler:
-
true|false
-
By using the "actions", one would however require to have EXECUTE
permission for policy:/actions/checkPermissions
- I am not certain if that is a good idea or if that API endpoint should be callable by every authenticated user instead.
I forgot to update the payload after I added the policy id as a pathparam :) I was pondering with the idea of not requiring any id in request url, so that you did not need to first fetch the policyId from the resource itself. You could just give it a thingId, policyId or messageId and it would fetch the policyId internally.
As far as the response goes; the idea was that you could test multiple permissions independently. But there's not that many combinations to be made with permissions, so I think the simple boolean response would be fine.
I was pondering with the idea of not requiring any id in request url, so that you did not need to first fetch the policyId from the resource itself. You could just give it a thingId, policyId or messageId and it would fetch the policyId internally.
That won't work as the Ditto "policies" service/persistence does not track "backward relations" and architecturally we don't want it to.
Solution could be simple: Always make "policyId" and "thingId" the same, then you won't need to look up the "policyId" before checking the policy permissions.
Since frontends usually have to check for multiple paths at once to reduce the number of requests per page load the endpoint should accept multiple requests in one. E.g. with a payload like:
[
{
"resource": "thing:/features/X/properties/Y",
"permissions": [ "READ" ]
},
{
"resource": "thing:/features/Z",
"permissions": [ "WRITE" ]
}
]
And a result payload like:
[
{
"resource": "thing:/features/X/properties/Y",
"permissions": [ "READ" ],
"match": true
},
{
"resource": "thing:/features/Z",
"permissions": [ "READ" ],
"match": false
}
]
Or simplified:
{
"thing:/features/X/properties/Y": true,
"thing:/features/Z": false
}
Maybe it's also useful if I can call that permission check action on a thing and ditto then forwards this to the policy check action with the corresponding thingId. That way I don't have to find out the policyId of a thing first.
When retrieving a thing or feature one could also supply a path parameter to request deviating permissions so if I GET a feature and I'm interested if there are properties on that feature I can't READ or WRITE then I'd like to know that. Similar to the extraFields
parameter. Actually maybe only if I can't write, because if I can't read then I don't get it returned anyway.
Thinking about this feature request again ..
- a user might not have the permission to even READ the "policyId" of a thing
- but still, a UI would need to query Ditto which "permissions" a provided login/JWT has for a specific thing
- so, maybe (thinking out loud) this would have to be an endpoint which is neither "thing" nor "policy" related - similar to the whoami endpoint
Idea (building on input provided by @w4tsn and @vhdirk):
POST /api/2/checkPermissions
Payload:
[
{
"resource": "thing:/features/lamp/properties/on",
"entityId": "org.eclipse.ditto:some-thing-1",
"hasPermissions": [ "READ" ]
},
{
"resource": "message:/features/lamp/inbox/toggle",
"entityId": "org.eclipse.ditto:some-thing-1",
"hasPermissions": [ "WRITE" ]
},
{
"resource": "policy:/",
"entityId": "org.eclipse.ditto:some-policy-1",
"hasPermissions": [ "READ", "WRITE" ]
}
]
The response payload would either just be:
[
true,
true,
false
]
Or it would include the whole "input" (maybe optionally with a "verbose" flag).
And an alternative to that (as JsonArrays are often problematic to handle, using indices, etc.):
{
"lamp_reader": {
"resource": "thing:/features/lamp/properties/on",
"entityId": "org.eclipse.ditto:some-thing-1",
"hasPermissions": [ "READ" ]
},
"lamp_toggler": {
"resource": "message:/features/lamp/inbox/toggle",
"entityId": "org.eclipse.ditto:some-thing-1",
"hasPermissions": [ "WRITE" ]
},
"policy_admin": {
"resource": "policy:/",
"entityId": "org.eclipse.ditto:some-policy-1",
"hasPermissions": [ "READ", "WRITE" ]
}
}
Which could (by default) just return a response like:
{
"lamp_reader": true,
"lamp_toggler": true,
"policy_admin": false
}
Or it would include the whole "input" (maybe optionally with a "verbose" flag).
Ideas:
- optional "verbose" mode, responding with requested "input" on "resources":
POST /api/2/checkPermissions?verbose=true
-
"entityId"
could also be provided via the HTTP path or query params .. to reduce payload for large permission check requests:POST /api/2/checkPermissions?entityId=<entityId>