Re: [Txauth] Polymorphism (Was: JSON Schema?)

Justin Richer <jricher@mit.edu> Mon, 13 July 2020 13:52 UTC

Return-Path: <jricher@mit.edu>
X-Original-To: txauth@ietfa.amsl.com
Delivered-To: txauth@ietfa.amsl.com
Received: from localhost (localhost [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id EAAE63A11F9 for <txauth@ietfa.amsl.com>; Mon, 13 Jul 2020 06:52:27 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -1.917
X-Spam-Level:
X-Spam-Status: No, score=-1.917 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, HTML_FONT_LOW_CONTRAST=0.001, HTML_MESSAGE=0.001, RCVD_IN_MSPIKE_H4=-0.01, RCVD_IN_MSPIKE_WL=-0.01, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, URIBL_BLOCKED=0.001] autolearn=ham autolearn_force=no
Received: from mail.ietf.org ([4.31.198.44]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id VAbL7Crk9lxm for <txauth@ietfa.amsl.com>; Mon, 13 Jul 2020 06:52:25 -0700 (PDT)
Received: from outgoing.mit.edu (outgoing-auth-1.mit.edu [18.9.28.11]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id A07CF3A11F1 for <txauth@ietf.org>; Mon, 13 Jul 2020 06:52:24 -0700 (PDT)
Received: from [192.168.1.7] (static-71-174-62-56.bstnma.fios.verizon.net [71.174.62.56]) (authenticated bits=0) (User authenticated as jricher@ATHENA.MIT.EDU) by outgoing.mit.edu (8.14.7/8.12.4) with ESMTP id 06DDqKMT007273 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 13 Jul 2020 09:52:21 -0400
From: Justin Richer <jricher@mit.edu>
Message-Id: <1C24A62A-EDAA-4D70-988A-E121B0F9F27D@mit.edu>
Content-Type: multipart/alternative; boundary="Apple-Mail=_7535ADB8-3B88-4F21-929F-3FD89193E2C7"
Mime-Version: 1.0 (Mac OS X Mail 13.4 \(3608.80.23.2.2\))
Date: Mon, 13 Jul 2020 09:52:20 -0400
In-Reply-To: <CAD9ie-tctw2v1WBUXUJo6hiQMV=5+KRjvWdBDnn-K=tE12ef2g@mail.gmail.com>
Cc: txauth@ietf.org
To: Dick Hardt <dick.hardt@gmail.com>
References: <CAD9ie-vnA98pobbboS00SAHneEG52_8eMxh_sE3r3jg6gyooGg@mail.gmail.com> <E9EC90C9-7A9A-4909-8627-A161B33E941F@mit.edu> <CAD9ie-vyB8+5jS=K_qUHfvxsF2wPV5APRo+7WUDfJxNzJONJpg@mail.gmail.com> <8CC8B466-FD6F-4C23-8DAA-99B8A9BDF548@mit.edu> <CAD9ie-u9z7Mc-wNjztoOTy4N_Z9jFDc2Sb6quLspasMGAMKdSw@mail.gmail.com> <097FB93E-96DA-4DF6-8511-0B32FD321211@mit.edu> <CAD9ie-tpuisauOFGiUj65-RcYPtcvW_gZP1CAadqq5cE6P36HQ@mail.gmail.com> <EE4A7D91-1106-44CB-92BF-C3AA3649BDFE@mit.edu> <CAD9ie-saoc2FUm46r4h1B27iYK04j_skf5-zJR7EXLmWBzj=hA@mail.gmail.com> <F41A8F88-C1B4-4CE2-8573-7A03C086D25B@mit.edu> <CAD9ie-tHCg9Ti1xWuzUP5EGLAcU2cpFALErqq98+fPnD3enZCQ@mail.gmail.com> <820525FD-4556-4617-8D89-C600D8C90C33@mit.edu> <CAD9ie-uwz_Li7n9iuX_--YtWeE+HX5sWEe95nZ8Y0akYh8WTHg@mail.gmail.com> <CAD9ie-vFwjQWQZjeL1Qza9MQ5=35qbWW15umyqGhhawFWephLA@mail.gmail.com> <D3EB60DC-6040-480D-A477-C1346130DF96@mit.edu> <CAD9ie-v9MdskdG74Ou__nn9mMmN2gJT6F45mg3dPs7CzJeD7WQ@mail.gmail.com> <69082870-1A99-4912-95EA-D1B7A1C967E5@mit.edu> <CAD9ie-uJHm4UHzNqreVta6Rrm5iQ=8chAbZqfaUFX6OOyXTbow@mail.gmail.com> <D1CD3AF7-051B-4BBD-9E16-5640FB2A718F@mit.edu> <CAD9ie-v+vv0VWeCC8gxytgjaVaHUjF9XJqwLa=sdB=FPwxpGTA@mail.gmail.com> <4A77832C-9613-48BD-9CB2-EE3121E44DB0@mit.edu> <CAD9ie-tctw2v1WBUXUJo6hiQMV=5+KRjvWdBDnn-K=tE12ef2g@mail.gmail.com>
X-Mailer: Apple Mail (2.3608.80.23.2.2)
Archived-At: <https://mailarchive.ietf.org/arch/msg/txauth/2CWMGS2yHtDIpMECW0okC8rScsE>
Subject: Re: [Txauth] Polymorphism (Was: JSON Schema?)
X-BeenThere: txauth@ietf.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <txauth.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/txauth>, <mailto:txauth-request@ietf.org?subject=unsubscribe>
List-Archive: <https://mailarchive.ietf.org/arch/browse/txauth/>
List-Post: <mailto:txauth@ietf.org>
List-Help: <mailto:txauth-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/txauth>, <mailto:txauth-request@ietf.org?subject=subscribe>
X-List-Received-Date: Mon, 13 Jul 2020 13:52:28 -0000

An important data-point: RAR is a back-port of the functionality in XYZ to OAuth 2, and Torsten and I intended from the outset that the internal structure be kept in synch. In an ideal world, GNAP’s request data structure would be finished already and RAR would refer to it.

So I am actually proposing:

C) GNAP create a GNAP-specific schema for requesting resources (that allows expression in objects and strings), and that this be developed in concert with RAR in OAuth2 so that data structures that fit into a RAR query will also fit into a GNAP query.

The GNAP query should be a proper superset of OAuth scope and OAuth RAR.

Similarly how an OAuth “scope” string should fit into a GNAP query, an OAuth “RAR” object should fit into a GNAP query. But that doesn’t mean that a GNAP query has to live with all of the limitations and contexts of those. For example, the string values in a GNAP query should be allowed to include spaces. OAuth scopes can’t have spaces because of an unfortunate limitation caused by the encoding in a query parameter, and they also can’t have non-ASCII characters without being encoded. We shouldn’t inherit those limitations here, and any JSON String should be allowed. Similarly, RAR objects need to make sense in the context of the OAuth “resource” parameter, but that constraints doesn’t apply to GNAP.

And yes, if a new query language is developed, I am saying it should be in a top-level parameter, as we should not be placing limitations on what that query language can affect. The GNAP language for requesting things in the access token should be scoped (pun intended) exactly to the access token(s) that it creates. A new query language could affect non-access-token responses as well, and that should be allowed. Combining these is obviously going to be tricky, but that happens any time you cross query languages. Putting them all into a lower-level bucket doesn’t make that go away.

 — Justin

> On Jul 10, 2020, at 4:53 PM, Dick Hardt <dick.hardt@gmail.com> wrote:
> 
> Now that you are saying that the scope string is short hand for the object, the polymorphism makes sense. There is only one type in the array, and a string is shorthand for it. In this thread, you have talked about them being separate things.
> 
> I just reread XYZ-08 section on resources, and there is no mention of polymorphism, or an example of it. Given your comments that XAuth should support both scopes and RAR, and that your proposed schema was based on RAR, that scopes and RAR objects were different things.
> 
> Now it is clear that you are proposing that GNAP have a brand new schema for authorization queries. And on closer inspection of your examples in XYZ, there is no "type" in the property in the object.
> 
> So now I see the choice being: 
> 
> A) reinvent a new, GNAP specific schema for requesting access to resources (XYZ)
> B) reuse the RAR and OAuth scope schemas (XAuth)
> 
> If and when another resource requesting schemas is developed, you propose that it be a new, top level object. I'm proposing it be a new type in the authorizations container.
> 
> Have I captured this accurately?
> 
> /Dick
> 
> 
> 
> 
> 
> On Fri, Jul 10, 2020 at 1:09 PM Justin Richer <jricher@mit.edu <mailto:jricher@mit.edu>> wrote:
> 
> 
>> On Jul 10, 2020, at 1:39 PM, Dick Hardt <dick.hardt@gmail.com <mailto:dick.hardt@gmail.com>> wrote:
>> 
>> 
>> 
>> On Thu, Jul 9, 2020 at 5:46 PM Justin Richer <jricher@mit.edu <mailto:jricher@mit.edu>> wrote:
>> 
>> 
>>> On Jul 9, 2020, at 6:50 PM, Dick Hardt <dick.hardt@gmail.com <mailto:dick.hardt@gmail.com>> wrote:
>>> 
>>> 
>>> 
>>> On Thu, Jul 9, 2020 at 12:17 PM Justin Richer <jricher@mit.edu <mailto:jricher@mit.edu>> wrote:
>>> On Jul 9, 2020, at 2:32 PM, Dick Hardt <dick.hardt@gmail.com <mailto:dick.hardt@gmail.com>> wrote:
>>>> 
>>>> Looks like you missed the point of my code example, as your response is focussed on the aspects I had in comments, so let me clarify. 
>>> 
>>> The point seemed to be about the overall complexity and readability, and that’s what I responded to.
>>> 
>>> It was about the complexity and readability -- of those specific lines of code.
>> 
>> But it’s not just about those lines, it’s about the context that those lines are in and the follow-on code paths that they cause. A lot of what you had “in comments” for the XAuth example would have repeated what was already included in the XYZ example, as I pointed out.
>> 
>> The context between those lines is pretty much the same. Yes, the XAuth example would have had to loop over each scope and RAR object, which is not that different than XYZ looping over each item, and the deciding how to process each item. My point is that checking the object type to decide what to do is an opaque decision vs checking an explicit type.
> 
> The context is really not the same though, as you actually  point out here, and calling one style of type checking “opaque” vs. “explicit” is stylistic and not a helpful comparison. I’m not surprised to find that you think the way you did it is easier, especially given the disconnect we have of what the components of the array represent. I started with some of the type-field-based structures in early versions of XYZ and moved away from them after having bad experiences with building it. There are still a few parts of the XYZ protocol that have that style (the “user” section, for example) that I think need to be changed. I think that the data-type based style of polymorphism, coupled with using the left-hand-side value of object members to indicate type or identifier, is significantly simpler in design and implementation.
> 
> Instead, I realized that we could get a cleaner protocol and better underlying code by dispatching based on the type within the protocol itself. I’ve gotten comments from a number of implementers that they like this design aspect, and I really like it myself.
> 
>>  
>> 
>>> 
>>> I've used JS type polymorphism to provide a cleaner API. For example, making an HTTP request. If I'm good with all the defaults, I can just provide the URI as a string, if I want to change a default, I provide an object and the URI is now a property of the object. Polymorphism allows the caller to have a simpler interface when it is a simple operation.
>>> 
>>> Do you have any examples of JSON polymorphism used in other protocols besides in a JWT?
>> 
>> Sounds to me like you’re describing exactly the kind of polymorphism that I’m talking about here. Use a string where it makes sense and an object where it makes sense, and there’s no ambiguity in calling the function. As you say, it's all about providing a cleaner API for the caller, and making things consistent in the model that the caller and provider are using. It takes a little bit of extra effort on the part of the API provider, but that’s where the complexity cost should be paid.
>> 
>> My examples are not the same at all. The caller has a simpler version of passing the same object. XYZ is passing two different types, where a string means one thing, and an object means something else. The string is not a shorthand for the object.
> 
> The string is, semantically, a shorthand for the object. That’s what I’ve been saying from the beginning, and I’ve put into several examples so far. There are two ways to add rights to an access token resulting from the request. So:
> 
> {
>   “resources”: [ “photo” ]
> }
> 
> Can really be thought of as a shorthand, for example it could expand to this:
> 
> {
>   “resources”: [
>    {
>      “datatypes”: [ “pohoto” ],
>      “actions”: [ “read” ],
>      “locations”: [ “https://server/photoapi/ <https://server/photoapi/>“ ]
>    }
> }
> 
> In this straw man, if the client wants write access to the API, it needs to use the object-style request to do so.
> 
> It’s up to the AS, or really the API that it’s protecting, to figure out what that expansion really looks like, and how much of the more complex policy to expose. My example of processing only string-based resource requests was for the case where the AS only gives the shorthand version, and doesn’t allow detailed access into its protection policies. It’s not a different kind of request, it’s a shorthand, much like passing an identifier in lieu of a key to identify the client software instance making the call.
> 
> But really, the point is that in both cases the string and the object both represent the same kind of thing: a piece of access rights that the client is requesting at the AS which gets put into the access token. Thus, having them together in a single array and processing them together makes sense to me. If you are thinking of strings and objects as completely different from each other,  you’d expect them to be handled separately, but I don’t think they are actually different because they both represent a view of the same underlying thing.
> 
> 
>> 
>> In polymorphic APIs, it makes the code cleaner to pass a singular item when the API takes an array, or the API takes a string for the common property of the object which is the parameter.
> 
> Yes, those are two examples of why it can be good. There are other reasons to apply it, like having an array with differently typed items that represent the same kind of thing underneath.
> 
>> Here are some code examples for both:
>> 
>> // passing a singular item, a simpler version of foo(['string'])
>> foo('string')
>> // passing a plurality of the same item
>> foo(['string1','string2','string3'])
>> 
>> // implementation
>> foo = ( param ) => {
>>     var a = []
>>     if (typeof param === 'string') {
>>         a[0] = param;
>>     } else if (typeof param === 'array') {
>>         a = param;
>>     }
>>     // process array a
>> }
>> 
>> 
>> // pass a uri as string and accept all defaults, a simpler version of bar({uri:'uri'})
>> bar('uri')
>> // pass an object with uri and method
>> bar({uri: 'uri', method: 'POST'})
>> 
>> // implementation
>> bar = ( param ) => {
>>     var p = DEFAULT_OBJ;
>>     if (typeof param === 'string') {
>>         p.uri = param;
>>     } else if (typeof param === 'object') {
>>         Object.keys(param).forEach( item => {
>>             p[item] = param[item];
>>         });
>>     }
>>     // process p object
>> }
>> 
>> In GNAP, we could take a string rather than an array for a scope parameter, for example:
>> 
>> "scope": "read"
>> 
>> instead of 
>> 
>> "scope": ["read"]
>> 
>> OIDC uses the latter polymorphism for 
>> 
>> { 
>>   "name": {"essential": true},
>>   "photo": null
>> }
>> 
>> as being shorthand for 
>> 
>> { 
>>   "name": {"essential": true},
>>   "photo": {"essential": false}
>> }
>> 
> 
> All of these are great examples of why object-type-based polymorphism is a good and powerful thing, and that’s the approach I’m saying we should use within the GNAP protocol. I would argue that the last one isn’t polymorphism so much as a default based on a null value (often taken as a special class of an “object” value anyway), but that’s splitting hairs.
> 
>>  
>> 
>>>  
>>> 
>>>> 
>>>> This line (which is determining the type of the item in the array):
>>>> 
>>>> if (typeof item === string')
>>>> 
>>>> is implicitly stating that the item is an oauth scope. 
>>> 
>>> No, it is not. It is saying that it is a resource request represented as a string. One way to represent a resource request as a string is an OAuth 2 style scope. And if you’re building up from an existing OAuth 2 system, then you could use a scope there. But that’s not the same as stating “the item is a scope”. In my examples, I had been trying to use the terminology that you were using, but I’m afraid that made things more unclear.
>>> 
>>> We are comparing how to do it in XYZ vs XAuth -- so to make it apples and apples, the string is a scope, since that is the use case we are looking at.
>> 
>> You can use a scope as the string, and if you want to think of all string values in this request as all being “scopes", that’s fine, but only if you can concede that the scope can mean whatever the AS wants. I think that perhaps you’re putting a certain amount of semantic weight to “scope” that I’m missing, though.
>> 
>> The string could be a scope, or it could be a claim such as in my "oidc" type example. The AS could support both scope and claims, and they could have overlapping string values. 
> 
> Both would represent “things that the client wants back in the access token”, and therefore the AS would need to differentiate. In that case I think it actually makes a lot more sense for the AS to define a more rich mechanism to request user information. In XYZ it could look like this:
> 
> {
>    “resources”: [
>      {
>         “type”: “oidc_userinfo”,
>         “datatypes”: [ “email”, “phone”, “address”, “profile.photo" ]
>      }
>    ]
> }
> 
> But defining the details of that kind of query is clearly outside the scope of GNAP (see below), and I think better suited to an identity protocol built on top of GNAP.
> 
>> 
>>  
>> 
>>>  
>>> 
>>>> Whereas it is explicit in this statement
>>>> 
>>>> if (authorizations.type === 'oauth_scope')
>>>> 
>>>> which I think it easier to understand what is happening (in my opinion of course).
>>>> 
>>>> XYZ has types, they are just implicit. RAR has explicit types, and that does not look to be holding back RAR. I don't understand why you think having explicit types will hold us back. 
>>> 
>>> The “type” in RAR is a name spacing device to allow extensibility in different aspects of the request. This is at a lower layer than how they’re being applied here, and it therefore makes more sense at that layer. It’s a way for a particular API to define the dimensions that it cares about in its request. It’s not really an even comparison.
>>> 
>>> And the type in XAuth authorizations is different schemas for making a request. "oauth_scope", "oauth_rar", and now "oidc". I would expect that there may be more in the future, and we have a clear way of adding schemas, and for the client and AS to know they are talking the same "language".
>> 
>> And I’m saying that additional “schemas” for requesting information should be defined separately from the GNAP schema. We disagree on this topic and we’re repeating ourselves. 
>> 
>> The charter is pretty clear that GNAP should not define schemas, so I don't know what you mean by the "the GNAP schema"
>>  
> 
> That’s not true. The charter is clear that "nor is the group chartered to develop schemas for user information, profiles, or other identity attributes”, but not that it’s not defining schemas for the other parts of the protocol. I’ve been clear on the other thread that we shouldn’t be defining a user schema, for attachment either to the access token or non-identifier items returned directly as plaintext or in assertions. 
> 
> By my read, we are fully within our charter to define schemas for authorization requests and responses, including both coarse and fine-grained access. I understand that it’s your stance that we should simply re-use OAuth 2’s “scope” parameter for this, and I disagree with that. My proposed approach is to have a place within the GNAP protocol to plug in a “scope” value if you have one, and for the AS to interpret it, but not to have that value always be a “scope”. 
> 
>> 
>>>  
>>> 
>>>> Do you want to let the string and object be anything the AS and RS decide they could mean? 
>>> 
>>> Yes. Just like the AS can decide that an OAuth scope could mean any number of things.
>>> 
>>> That was what I was afraid of. While an OAuth scope could mean whatever the AS decides it wants to be, the Client and AS know it is an OAuth scope.
>> 
>> How is that any different? I’m confused as to why you think it’s important to call this item a “scope” so that you “know what it is”, but then you’re OK with “scope” meaning literally anything.
>> 
>> A scope string is one of a set of strings defined by the AS.
>> 
>> The AS may use strings to define a different schema, such as which claims to return from a userinfo endpoint.
>> 
> 
> Exactly. A scope is a string that represents access to something controlled by the AS. The object also represents that same thing, but in multiple, more specific dimensions. 
> 
>>  I'm going to separate the my other responses into separate threads as they are different topics.
>> 
>> /Dick
>> 
>> ᐧ
> 
> 
>  - Justin