Inherited / Default Access using the Universal API

Hello all :wave:

I’m new to solid and trying to do something very basic: create a container where are all subcontainers and files within that container are public. I have tried using the Universal API for access management, but so far as I can tell, this only sets the access of a single container/file/dataset but this access is not inherited as shown in the example code below.

I made some attempts to dive into the deeper ACP and WAC code, but I got a bit lost. Surely setting inherited permissions shouldn’t be this hard, right? What am I missing?

My test code:

// Omitted: login logic to get myFetchFunction and myPodUrl
const options = { fetch: myFetch };

// Create a root level container
const containerUrl = `${myPodUrl}test/`;
await createContainerAt(containerUrl, options);

// Use the Universal API to set read-only public access
await universalAccess.setPublicAccess(
  containerUrl,
  {
    read: true,
    append: false,
    write: false,
    controlRead: false,
    controlWrite: false,
  },
  options,
);

// containerUrl is now public:
// I can get this with global unauthenticated fetch
const containerResponse = await fetch(containerUrl);
console.log(containerResponse.status); // 200 (OK)

// Add a file to the container
const fileUrl = `${containerUrl}/test.txt`;
await overwriteFile(
  fileUrl,
  new File(["Hello world!"], "test.txt", { type: "text/plain" }),
  { contentType: "text/plain", ...options },
);

// The file is not public...
const fileResponse = await fetch(fileUrl);
console.log(fileResponse.status); // 401 (Unauthorized)

The acl:default predicate denotes the container resource whose Authorization can be applied to a resource lower in the collection hierarchy.

Thanks for the reply. I tried adding the predicate, but it is still not working. The code I added is below. Am I adding it to the right thing? Should I using the predicate to point to the container’s own url or something else? Is there not a built-in function in the javascript API for this?

let dataset = await getSolidDataset(containerUrl, options);
let thing = getThing(dataset, containerUrl);
if (thing) {
  thing = setUrl(
    thing,
    "http://www.w3.org/ns/auth/acl#default",
    containerUrl,
  );
  dataset = setThing(dataset, thing);
} else {
  throw "missing thing";
}
await saveSolidDatasetAt(containerUrl, dataset, options);
1 Like

acl:default is WAC-only though, if I recall correctly. I don’t think ACP has a simple equivalent; you’d have to set up your own rules and matches and whatnot, I believe.

(The predicate would also have to be added to the Access Control, not to the SolidDataset itself.)

2 Likes

You’d think I’d remember to look at which access system is in play. Sorry.

It looks like to do it with ACP, it is basically the same as this example, except addPolicyUrl is replaced with addMemberPolicyUrl. However this takes quite a lot of code (see below). It would be nice if that was part of the simpler Universal API… whats the pathway for contributing code in Solid like?

Also for practicality, are there statistics on the ratio of pods that support ACP versus WAC? Is one being phased out or are they expected to coexist as competing standards?

ACP Code
async function createPublicReadOnlyContainer(
  url: string,
  options: { fetch: typeof fetch },
) {
  // Create the container if it doesn't already exist
  try {
    await createContainerAt(url, options);
  } catch (e) {
    const response = e.response;
    // Ignore if the container already exists
    if (!(response instanceof Response) || response.status !== 412) {
      throw e;
    }
  }

  // Get the container dataset with ACR
  let datasetWithAcr: Awaited<
    ReturnType<typeof acp_ess_2.getSolidDatasetWithAcr>
  > &
    WithAccessibleAcr;
  try {
    const result = await acp_ess_2.getSolidDatasetWithAcr(url, options);
    if (!acp_ess_2.hasAccessibleAcr(value)) {
      throw "No support for ACR";
    } else {
      datasetWithAcr = result;
    }
  } catch (e) {
    throw e;
  }

  let resourcePublicMatcher = acp_ess_2.createResourceMatcherFor(
    datasetWithAcr,
    "match-public", // Matcher URL will be {ACR URL}#match-public
  );

  const existingPolicy = acp_ess_2.getResourcePolicyAll(datasetWithAcr);
  if (existingPolicy.length) {
    for (const policy of existingPolicy) {
      const match = getUrlAll(policy, "http://www.w3.org/ns/solid/acp#allOf");
      const allow = getUrlAll(policy, "http://www.w3.org/ns/solid/acp#allow");

      if (
        match.includes(resourcePublicMatcher.url) &&
        allow.length === 1 &&
        allow[0] === "http://www.w3.org/ns/auth/acl#Read"
      ) {
        return;
      }
    }
  }

  // Create a public matcher
  resourcePublicMatcher = acp_ess_2.setPublic(resourcePublicMatcher);
  datasetWithAcr = acp_ess_2.setResourceMatcher(
    datasetWithAcr,
    resourcePublicMatcher,
  );

  // Initialize a policy
  let resourcePolicy = acp_ess_2.createResourcePolicyFor(
    datasetWithAcr,
    "public-policy", // Policy URL will be {ACR URL}#public-policy
  );

  // The policy will match any Resource that matches the Public Matcher.
  resourcePolicy = acp_ess_2.addAllOfMatcherUrl(
    resourcePolicy,
    resourcePublicMatcher,
  );

  // The policy is read-only.
  resourcePolicy = acp_ess_2.setAllowModes(resourcePolicy, {
    read: true,
    append: false,
    write: false,
  });

  // Apply the Policy to the Resource.
  datasetWithAcr = acp_ess_2.addMemberPolicyUrl(
    datasetWithAcr,
    asUrl(resourcePolicy),
  );
  datasetWithAcr = acp_ess_2.setResourcePolicy(datasetWithAcr, resourcePolicy);

  // Save to the dataset
  await acp_ess_2.saveAcrFor(datasetWithAcr, options);
}

pathway for contributing

Inrupt has its own policies which I can’t speak to, but as far as the open source side goes - all contributions are welcome. Submit issues or PRs on any existing code or create your own repo (either inside your personal one or in Solid Contributors · GitHub). Join the Solid Practitioners Group where we try to share interoperable code. Mention a project here … so many ways to contribute. If you (or anyone reading this) wants to contribute in any way - code or words or editing or anything you have to offer - please, grab me or any other long-time Solid person and ask, we’d love to help you contribute. I’m @jeffz here and @jeff-zucker in the Solid chat rooms.

Is one being phased out or are they expected to coexist as competing standards?

My opinion (which is only that) is that they’ll coexist as hopefully not-too-competitive standards. It’s early days, maybe too soon to say which way things will shake out.

The library you’re using is by Inrupt; you could file an issue at GitHub - inrupt/solid-client-js: Library for accessing data and managing permissions on data stored in a Solid Pod to propose a code contribution to make.

That said, I do believe ACPs member policies and WAC’s default policies work differently, and therefore are hard (impossible?) to implement in a universal API that match the caller’s expectations.

As far as I know, both WAC and ACP have proponents, and they have basically settled on wanting multiple access control systems to co-exist (and potentially for even more to be added in the future).

(Which personally, as an app developer, I think is the worst of all worlds, but alas, I’m also not prepared to spend lots of energy trying to convince people to converge on one.)

1 Like