ietf-wg-gnap / gnap-core-protocol

146 stars 26 forks source link

Requesting resources with insufficient access #203

Closed Denisthemalice closed 3 years ago

Denisthemalice commented 3 years ago

The current text from Section 9.4 (Requesting Resources With Insufficient Access) states:

" If the client instance calls an RS without an access token, or with an invalid access token, the RS MAY respond to the client instance with an authentication header indicating that GNAP needs to be used to access the resource".

The text omits to mention which HTTP error code should be returned.

The error codes from RFC 7231 and from RFC 7325 should be used, in particular:

400 Bad Request Section 6.5.1 of RFC 7231 401 Unauthorized Section 3.1 of RFC 7235 403 Forbidden Section 6.5.3 of RFC 7231 404 Not Found Section 6.5.4 of RFC 7231 405 Method Not Allowed Section 6.5.5 of RFC 7231 406 Not Acceptable Section 6.5.6 of RFC 7231

When a client instance calls an RS without an access token, or with an invalid access token different HTTP errors types may be returned, in particular:

401 "Unauthorized" which indicates an authentication error. It always includes a WWW-Authenticate header that describes how to authenticate.

403 "Forbidden" which indicates that the server understood the request but that the client instance does not have permission to access this resource, even if it has been authenticated. A server that wishes to make public why the request has been forbidden can describe that reason in the response payload (if any).

Note: A server that wishes to hide the existence of a forbidden target resource MAY instead respond with a status code of 404 (Not Found).

404 "Not Found" which indicates either that the server did not find a current representation for the target resource or that the server is not willing to disclose that one exists.

405 "Method not allowed" which indicates that the method received in the request-line is known by the server but not supported by the target resource. In that case, the server MUST generate an Allow header field containing a list of the target resource’s currently supported methods.

Discussion:

401 "Unauthorized", despite its name, is dedicated to authentication errors whereas 403 "Forbidden" is dedicated to authorization errors.

When the request is recognized by the server but sent "without an access token or with an invalid access token", the HTTP status 403 Forbidden should be used. If the server wants to make known why a request is forbidden, it can provide the reason in the payload.

This means that the use of an WWW-Authenticate header is not adequate in case of a HTTP status 403 Forbidden.

In the context of GNAP, we should define the elements that can appear in the response body of a 403 Forbidden error. Such elements would be a combination (AND or OR) of URLs for Authentication Servers (ASs) and for each of them a combination (AND or OR) of privileges types (i.e. attributes and/or rights), where attributes types may optionally be associated with attribute values.

jricher commented 3 years ago

GNAP has absolutely no business defining the response body of an API that it is protecting. Let's say we define a JSON body, but the API being protected is in XML or ProtoBuf or CBOR. Any error responses we help define will need to be in the HTTP headers, as well as suggestions on response codes.

Denisthemalice commented 3 years ago

When an authorization error is detected, the RS should respond with 403 Forbidden as defined in Section 6.5.3 of RFC 7231.

The full text is copied below.

6.5.3. 403 Forbidden

The 403 (Forbidden) status code indicates that the server understood the request but refuses to authorize it.
A server that wishes to make public why the request has been forbidden can describe that reason in the response payload (if any).**

If authentication credentials were provided in the request, the server considers them insufficient to grant access.
The client SHOULD NOT automatically repeat the request with the same credentials. The client MAY repeat the request with new or different credentials. However, a request might be forbidden for reasons unrelated to the credentials. An origin server that wishes to "hide" the current existence of a forbidden target resource MAY instead respond with a status code of 404 (Not Found).

The text does not prescribe the use of a header for the response, but the use of the response payload. We are not going to change RFC 7231. When accessing a resource at a RS, major errors should be described in the document with their reasons codes. In particular 401 "Unauthorized" which indicates an authentication error may also happen if an end-user authentication is required by the RS before the presentation of an access token. In such a case, it includes a WWW-Authenticate header that describes how to authenticate.

Since GNAP is not an authentication framework, but an authorization framework, a GNAP method should never appear under an WWW-Authenticate header.

In the response payload, we need to allow the RS to provide some guidance to the client (and then the AS) about which kinds of privileges are expected by the RS in order to fulfill the request.

My proposal is to use a combination (AND or OR) of URLs for Authentication Servers (ASs) and for each of them a combination (AND or OR) of privileges types (i.e. attributes and/or rights), where attributes types may optionally be associated with attribute values.

fimbault commented 3 years ago

Let me summarize what I understand you're suggesting. When a response 403 Forbidden is sent, structured headers (as per rfc8941) would define what privileges could be handled by the RS. The the client would make its GNAP request based on that info.

Something like: Accept-GNAP-AS: "as1.com", "as2.com" -> meaning OR Accept-GNAP-Attribute: "foo", 'bar", ("dolphin", "denver") -> meaning foo OR bar OR (dolphin AND denver) (didn't check against a parser to check if that works)

Is that correct?

It doesn't really map to the complexity of access_token semantics though. Compared to section 2.1, how would we know how to map to the request? The location is clear (the RS), the type/actions/datatypes isn't (the json is really more complex than my structured field). So it could work, but only for basic structures such as role based attributes, cf #192 (but the multi-token would work based on the OR statement). The full variety of possibilities in GNAP would be hard to manage.

Here's how that would look like:

"access_token": [
    {
        "label": "token1",
        "access": [
            {
                "roles": [
                    "foo"
                ],
                "locations": [
                    "https://rs.example.com"
                ]
            }
        ]
    },
    {
        "label": "token2",
        "access": [
            {
                "roles": [
                    "bar"
                ],
                "locations": [
                    "https://rs.example.com"
                ]
            }
        ]
    },
   {
        "label": "token3",
        "access": [
            {
                "roles": [
                    "dolphin", 
                    "denver"
                ],
                "locations": [
                    "https://rs.example.com"
                ]
            }
        ]
    }
]

would be sent to either as1 or as2.

Denisthemalice commented 3 years ago

You got the spirit of the idea. However, the example above is not general enough. For each AS, the privileges (not only the attributes) may be different.

Note: Privileges = Attributes and/or Rights

I would consider something like:

Accept-GNAP-AS: "as1.com", "as2.com" -> meaning OR Accept-GNAP-Privileges: "as1.com"; right; attributes : (uid, role values: "foo", 'bar", ("dolphin", "denver"); (uid, group-memberships)
Accept-GNAP-Privileges: "as2.com"; right

On the second line, the Identifier of the first AS is copied first to mention that what follows relates to AS1 only, followed by the magic word "right" which means that a capability for the requested method and the requested object may be presented OR a set attributes may also be presented, like a unique Id for the RS AND, some role values like "foo" or "bar, or both the roles values "dolphin" and "denver" OR a unique Id for the RS AND all the group memberships known to the AS.

On the last line, a capability issued by AS2 with the requested method and the requested object will be accepted by the RS. There is no need to duplicate the method and the URL of the target resource, since it is already known by the client.

If the method is incorrect, 405 "Method not allowed" SHALL have precedence over 403 "Forbidden".

fimbault commented 3 years ago

So (assuming we could use that kind of syntax within a structured header, need to check) :

Denisthemalice commented 3 years ago

With "right", the client does not need "to know what to ask for by a separate mean". Since the information is supplied in addition to the error 403 "Forbidden" , it means that the client sent a correct method and a resource URl but that the request failed ONLY because the access token when missing or was incorrect. So the client knows exactly what to ask for the next time. There is no need for an out of band mechanism, ... like yesterday.

uid would NOT be a sub since sub does not have a sufficient granularity in terms of privacy (see below).

When attributes is indicated by the RS, it can be some sort of role or some sort of functional or hierarchical group membership.

If the client has never visited the RS before, it needs to call first the RS (unless its code is screwed to a single AS).

The client needs to be able to tell to the AS which kind of user identifier should be used, in particular whether a globally unique identifier should be used or whether an identifier unique for the RS should be used.

If the client does not supply the RS location to the AS, the AS has no way to use a user identifier that would be specific to the RS only.

fimbault commented 3 years ago

Ok that's clearer for me now. Thxs! - At a high level at least, all this would need to be tested.

agropper commented 3 years ago

I'm having a hard time following this conversation. The client presents a capability to the RS. The capability is derived or filtered from whatever rights are granted by the RS to the AS. The client does not necessarily have a direct relationship with the AS because there could have been many user agents or clients in the capability delegation chain.

fimbault commented 3 years ago

The point raised by Denis here is that he'd like to not always use capabilities (rights), but attribute based access also.

The delegation use case is very different, and indeed requires capabilities to work.

The needs are pretty orthogonal, and I think both (rights and attributes) make sense for their specific use case. None exactly match the usual jwt implementation.

agropper commented 3 years ago

I'm good with both. The SSI folks seem eager to focus on capabilities rather than attributes. I'd like to stay on their good side for everybody's sake.

Denisthemalice commented 3 years ago

fimbault wrote:

The delegation use case is very different, and indeed requires capabilities to work.

The delegation use case is certainly very different, but it does not necessarily require capabilities to work.

If the RS server is part of a service, it can advertise the name of that service in a RS Discovery response. Then the client may target the access token to both that RS and that service name.

When the target RS re-sends that token to another RS that is a member of that service, it may accept it if it contains both the name of the service and an identifier of the target RS that it considers to be also a member of the service.

fimbault commented 3 years ago

@Denisthemalice yes that's possible, but pure delegation through ocaps supports advanced features such direct attenuation. Not that it applies to every use case, but it avoids resending the initial token. Like you're chief of staff, you get a token for a RS, but you can further delegate to your team members under your own authority (possibly propagating reduced privileges). So that's a different scenario.

I think we'd be better off by sticking to attributes and rights in the context of GNAP, and let people implement whatever they want on top of it.

Denisthemalice commented 3 years ago

This thread is drifting away from the first message that I posted. The current text in section 9.4 starts with:

" If the client instance calls an RS without an access token, or with an invalid access token, the RS MAY respond to the client instance with an authentication header indicating that GNAP needs to be used to access the resource".

The text omits to mention which HTTP error code(s) should be returned.

The error codes from RFC 7231 and from RFC 7325 should be used, in particular:

400 Bad Request Section 6.5.1 of RFC 7231 401 Unauthorized Section 3.1 of RFC 7235 403 Forbidden Section 6.5.3 of RFC 7231 404 Not Found Section 6.5.4 of RFC 7231 405 Method Not Allowed Section 6.5.5 of RFC 7231 406 Not Acceptable Section 6.5.6 of RFC 7231

A key point is to make the difference between 401 and 403:

401 "Unauthorized" which indicates an authentication error. It always includes a WWW-Authenticate header that describes how to authenticate.

403 "Forbidden" which indicates that the server understood the request but that the client instance does not have permission to access this resource, even if it has been authenticated. A server that wishes to make public why the request has been forbidden can describe that reason in the response payload (if any).

It is a simple as this. However this implies to change the definition of a Resource Server from:

Resource Server (RS): server that provides operations on protected resources, where operations require a valid access token issued by an AS.

to:

Resource Server (RS) : any system that contains resources on which clients are willing to perform an operation on them.

fimbault commented 3 years ago

Note that there were other proposals related to errors, to be reviewed

jricher commented 3 years ago
Denisthemalice commented 3 years ago

This issue has been marked as "Pending Close", however the topic is rather important.

@jricher : When a request/command is sent, the error codes should be mentioned. This is one of the pillars for interoperability and testing.

I can't understand why you refuse to mention all of them. The text proposed above would take less than one half page.

@jricher : This issue is fully unrelated with "AS-discovery is discussed in GNAP-RS created by #246".

This issue should not be closed until all error codes are mentioned in the draft document

aaronpk commented 3 years ago

The reason for marking the issue as "pending close" was stated above.

Denisthemalice commented 3 years ago

@aaronpk: Your reply does not respond to my technical arguments.