Closed woutermont closed 1 year ago
@woutermont do you plan to adjust the relevant places in the interop spec as part of this proposal?
@justinwb yes, working on it now; will hopefully finish tomorrow!
Okay, so I've updated the proposal, taking more terminology from the main document, and then subtituted references to the proposal for some portions of the relevant sections in the main document. I tried to be conservative, so there is still quite a lot of text repeating what is already stated in the proposal.
In trying to align the two as good as possible, I wondered why we want to distinguish Social Agents from Applications at this level. Does the Authorization Agent need to know what type it is, given that they really contain the same info. Especially the double Registry->Registration predicate (hasApplicationRegistration
/hasSocialAgentRegistration
) is hard to incorporate. I feel that it only adds redundant data, since we get the same info from the resource it points to: the following paths hold the same information.
:registry interop:hasApplicationRegistration :registration .
:registry interop:hasRegistration / rdf:type interop:ApplicationRegistration .
:registry interop:hasRegistration / inter:registeredAgent / rdf:type interop:Application .
On another note: the resource hierachy included at the end of the section on Agent Registrations seems to give the wrong hint that it always has to be a filesystem-like structure. I think just including Turtle examples, or graphs like the ones in the Primer, are clearer.
Ass requested by @elf-pavlik in yesterday's meeting, here's a quickly drafted snippet of how a client would discover its registration, with support for the "legacy" use of pim:preferencesFile
, reading links out of the body rather than the headers, and supporting all reponse codes (303
, 204
and 200
). I've commented out (with //
) those parts that would be superfluous when we would not provide this additional support (only read a single relation, only out of headers, and with only 204
responses) ... it's not much difference i.m.o.
The only thing I left out is the rdfs:seeOther
link, because I must agree with @elf-pavlik that this would put too much burden on the client. So we only check for one or two predicates/links of which we specify that they should be unique.
/* RDF Parser */
// import { Parser } from 'n3';
// const parser = new Parser();
/* Shorthands */
const hasAM = 'http://www.w3.org/ns/solid/interop#hasAgentManager';
const regAgent = 'http://www.w3.org/ns/solid/interop#registeredAgent';
// const prefFile = 'https://www.w3.org/ns/pim/space#preferencesFile';
/* Helper functions */
// const const rdfLinks = (rdf) => Object.fromEntries(parser.parse(rdf)
// .map((quad) => [quad.predicate.value, { target: quad.object.value, context: quad.subject.value }]))
const headerLinks = (response) => Object.fromEntries(
response.headers?.get('link')?.split(',')?.map((link) => link.trim().split('; ')).map((params) => [
params.find((param) => param.startsWith('rel=')).replaceAll(/(^rel="|"$)/g,''), ({
target: params[0].replaceAll(/(^<|>$)/g,''),
context: params.find((param) => param.startsWith('anchor='))?.replaceAll(/(^anchor="|"$)/g,'') ?? response.url
})
])
)
const getRegistration = async (webid) {
/* Get links from profile header and body */
const profile = await fetch(webid);
const hlinks = headerLinks(profile)
let am = hlinks[hasAM];
// let pf = hlinks[prefFile];
// if (!am && !pf) {
// const rlinks = rdfLinks(await profile.text());
// am = rlinks[hasAM];
// pf = rlinks[prefFile];
// }
/* Abort if no indication of agent manager */
if (!am && !pf) return undefined;
/* Request agent registration */
const token = getToken(profile);
const target = am?.target ?? pm?.target
const dpop = getDPoP('GET', target);
const registration = await fetch(target, {
redirect: manual,
headers: {
Authorization: `DPoP ${token}`,
DPoP: dpop,
},
});
/* Optimal case, we're done */
// if (registration.status === 200) return parser.parse(await registration.text());
/* Otherwise, follow registration link, then we're done */
if (registration.status === 204 || registration.status === 303) {
const reg = headerLinks(registration)[regAgent].context;
// const loc = registration.headers.get('location');
const dpop = getDPoP('GET', reg ?? loc);
const registration = await fetch(reg ?? loc, {
headers: {
Authorization: `DPoP ${token}`,
DPoP: dpop,
},
});
return parser.parse(await registration.text());
}
/* All other cases indicate some auth error
* which SAI-aware clients could have foreseen. */
throw new Error('Auth error');
}
PS: I wrote this code directly into this comment, so please treat it as pseudocode; no idea if it would work without some finetuning.
Closing this since I am no longer convinced that a separate discovery service is needed (cf. https://github.com/solid/data-interoperability-panel/issues/315). An agent should at all times be able to request access to (types of) resources at an AS, and the combination of the AS's response and a token info endpoint should suffice to gain all necessary information. If not, a better approach than proposed in this PR would be to add a client/agent info endpoint that functions similarly to the OIDC user info endpoint.
@tomhgmns