Error 403 for PATCH after creating document ACL

Hi

I’m working on a project where I need to change the ACL for some files and I’ve run into an issue, hoping I can find some help here in debugging it because I can’t seem to find where it goes wrong.

I have a script running that updates a file every ~30 secs with some new data using the rdflib updater. (Authentication happens using solid-auth-cli by Jeffz and I’m using the WebId of the actual Solid Pod)
If the individual file does not have its own ACL yet, all works fine and I’m getting responses like this:

Updating the content at https://flordigipolis.inrupt.net/private/iot/urn:dev:mac:807d3afffe367a58_humidity.ttl with 1 messages.
    UpdateManager: Return success 200 elapsed 971ms
Succesfully updated resource at https://flordigipolis.inrupt.net/private/iot/urn:dev:mac:807d3afffe367a58_humidity.ttl

Though after adding the ACL suddenly I get 403 errors, like these:

Updating the content at https://flordigipolis.inrupt.net/private/iot/urn:dev:mac:807d3afffe367a58_humidity.ttl with 1 messages.
    UpdateManager: Return FAILURE undefined elapsed 1059ms
Error updating document: Web error: 403 (Forbidden) on PATCH of <https://flordigipolis.inrupt.net/private/iot/urn:dev:mac:807d3afffe367a58_humidity.ttl>

(Note: the first and third lines result from my own code, not the rdflib library)

At first I though something must be going wrong while creating the ACL, but when I print fetch it right after creating the document and it looks like this:

@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.

<#default> a acl:Authorization;
    acl:agent </profile/card#me>;
    acl:accessTo <./urn%3Adev%3Amac%3Ab4e62dfffe703f4d_light.ttl>;
    acl:mode acl:Append, acl:Control, acl:Read, acl:Write.
<#ReadWriteAppendControl-0> a acl:Authorization;
    acl:agent </profile/card#me>;
    acl:accessTo <./urn%3Adev%3Amac%3Ab4e62dfffe703f4d_light.ttl>;
    acl:mode acl:Read, acl:Write, acl:Append, acl:Control.

The rule I want to be in there is now in there twice, because I explicitly added the rule again to make sure. Though if I don’t do this it doesn’t make any difference to the end result.

Any idea of what could be going on? Or at least of how I could try to fix this?
Thanks in advance

Edit: Just realized I copied the ACL and 403 error messages for different files, but I’ve tried the same action on different files and all give the same result. Sorry if this causes confusion.

Could you post the ACL and 403 messages for the correct files? And also possibly the ACL for the parent of the file you’re trying to update?

Hi Vincent,
The content of the different ACL files and 403 messages are identical other than the file name.
I just shut down for today but I’ll make sure to post the ACL for the parent directory first thing tomorrow.

Okay so here’s an update: (Along with hopefully a better description of the situation)
(I just created a new Pod to isolate the issue to what’s happening now.)

I have 3 files in the /private/iot/ folder, created using rdflib’s createIfNotExists.
Before sharing, only the private folder has an ACL, which looks like:

# ACL resource for the private folder
@prefix acl: <http://www.w3.org/ns/auth/acl#>.

# The owner has all permissions
<#owner>
    a acl:Authorization;
    acl:agent <https://cleenmail.inrupt.net/profile/card#me>;
    acl:accessTo <./>;
    acl:defaultForNew <./>;
    acl:mode acl:Read, acl:Write, acl:Control.

For managing the ACL I’m using the SolidAclUtils by A_A which correctly gives me back the permissions from the /private folder when asking for the permissions on one of the files.

Up to this moment the updating works perfectly as well:

Updating the content at https://cleenmail.inrupt.net/private/iot/urn:dev:mac:807d3afffe367a58_humidity.ttl with 1 messages.
    UpdateManager: Return success 200 elapsed 387ms
Succesfully updated resource at https://cleenmail.inrupt.net/private/iot/urn:dev:mac:807d3afffe367a58_humidity.ttl

Then, when I try adding a permission to the ACL for one of the three files (i.c. the one ending on _humidity), using the acl.addRule(READ, url) function, the following occurs:

  1. The file now has its own ACL file with these contents:
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.

<#owner> a acl:Authorization;
    acl:agent </profile/card#me>;
    acl:accessTo <./urn%3Adev%3Amac%3A807d3afffe367a58_humidity.ttl>;
    acl:mode acl:Read, acl:Write, acl:Control.
<#Read-0> a acl:Authorization;
    acl:agent <https://flordigipolis.solidweb.org/profile/card#me>;
    acl:accessTo <./urn%3Adev%3Amac%3A807d3afffe367a58_humidity.ttl>;
    acl:mode acl:Read.
  1. The /private folder still has its own ACL file with unchanged contents.
  2. My script now isn’t succeeding anymore in sending patches to the file in question.
Updating the content at https://cleenmail.inrupt.net/private/iot/urn:dev:mac:807d3afffe367a58_humidity.ttl with 1 messages.
    UpdateManager: Return FAILURE undefined elapsed 394ms
Error updating document: Web error: 403 (Forbidden) on PATCH of <https://cleenmail.inrupt.net/private/iot/urn:dev:mac:807d3afffe367a58_humidity.ttl>
  1. My script still succeeds in patching the contents of the files in the same /iot folder.
Updating the content at https://cleenmail.inrupt.net/private/iot/urn:dev:mac:b4e62dfffe703f4d_light.ttl with 1 messages.
    UpdateManager: Return success 200 elapsed 363ms
Succesfully updated resource at https://cleenmail.inrupt.net/private/iot/urn:dev:mac:b4e62dfffe703f4d_light.ttl

As far as I can see there has been no permissions taken away upon the sharing of the file, only read permissions for an extra webId has been added. And still this causes the patch permissions to break.

Edit: Deleting the file in a tool like Inrupts pod browser apparently also deletes its ACL and thus resets the problem, so I’m able to reproduce the problem several times if necessary.
Also, trying to add read permissions through the pod browser results in this (possibly unrelated) error:
image

So, I have been trying some things to see what could be the issue since everyting looked like it was supposed to work.

  1. I tried saving directly to the /private folder to make sure the issue wasn’t that the private/iot folder still didn’t have an ACL of its own after sharing files within it, this made no difference.
  2. I changed the file name to something more simple (file0.ttl) (shorter and without special characters). This actually solved the issue!

Now the ACL looks like this:

@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.

<#owner> a acl:Authorization;
    acl:agent </profile/card#me>;
    acl:accessTo <./file0.ttl>;
    acl:mode acl:Read, acl:Write, acl:Control.
<#Read-0> a acl:Authorization;
    acl:agent <https://flordigipolis.solidweb.org/profile/card#me>;
    acl:accessTo <./file0.ttl>;
    acl:mode acl:Read.

Which is, other than the filename, identical to the one I got before.
Now I don’t know if the cause is that the file name was too long or if it unsupported characters like colons.
The fact that a file name can break the access control system does worry me though, might be something worth looking into.

Update: I tried again with the original file names, but just replaced all colons : with underscores _ so the filename is immediately URI-safe and this works.
Conclusion: Characters that aren’t URI-safe can cause trouble with access lists, avoid using them.

1 Like

Good work finding that! That sounds like a bug in the Pod server software used at inrupt.net, node-solid-server. Possibly you could report there that it has trouble associating an ACL with resources with a colon in their name?

1 Like

Sure thing, I will open an issue.
Edit: Link added for future reference

I tried to reproduce your problem,

  • creating a file like test:test.txt with solid-ide
  • adding an acl with solid-ide

and everything is correct. I can access the file and the linked ACL.
So I suppose something else occur in your application.
Your acl content is correct with acl:accessTo <%-encoded filename>

My understanding is that the filename logic is :

  • NSS internally uses URL’s so they are %-encoded
  • NSS mashlib displays %-decoded filenames
  • solid NSS store on the filesystem the %-decoded filename

If you used a pod on solid.community I could check if there is a filename problem.

1 Like

Hi @bourgeoa

The file is created using rdflib.js fetcher’s createIfNotExists() function. Though admittedly I’m not pre-%-encoding the filename, just pass that down as an argument with colons and all. This seems to create the file no problem, at least it doesn’t give any errors.

When I fetch a filelist using Jeffz’s solid-file-client the filename is still percent-encoded, so once it’s encoded it seemingly doesn’t get decoded anymore. (Also the percent-encoded filename is visible in the podbrowser etc)

Well I can also still access and edit the file using the built-in ui and other apps like the pod-browser, it’s patch requests that get rejected. (Called using rdflib.js updater)

I have a pod on solid.community with %-encoded files in the https://flordigipolis.solid.community/private/iot/ folder. Though on those files I managed to remove my own access altogether (some older less succesful experiments).

@bourgeoa Since you couldn’t seem to reproduce the issue, I wrote a small script which seems to do the trick.

import auth from 'solid-auth-cli';
import $rdf from 'rdflib';
import aclClient from 'ownacl';

export const IDENTITY_PROVIDER = 'https://solid.community';
export const USERNAME = 'USERNAME';
export const PASSWORD = 'PASSWORD';

const FILE = 'https://flordigipolis.solid.community/private/test:test.ttl';
var RDF = $rdf.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#");

const run = async function () {
    try {
        // Log in
        console.log('Logging in...');
        var session = await auth.currentSession();
        if (!session) session = await auth.login({ idp: IDENTITY_PROVIDER, username: USERNAME, password: PASSWORD });
        console.log(`Logged in as ${session.webId}`);
        
        // Initialize rdflib constructs
        const store = $rdf.graph();
        const fetcher = new $rdf.Fetcher(store);
        const updater = new $rdf.UpdateManager(store);

        // Create file
        await fetcher.createIfNotExists($rdf.sym(FILE), 'text/turtle', '');

        // Send first update
        console.log('Sending first update...')
        const add1 = $rdf.st($rdf.sym('http://example.org/somePerson'), RDF('type'), $rdf.sym('http://example.org/person'), $rdf.sym(FILE));
        updater.update(null, add1, async (uri, ok, msg) => {
            if(ok){
                console.log('First update sent\nAdding an extra read permission to the ACL...')

                const acl = new aclClient(`${FILE}.acl`);
                var accessControl = await acl.readAccessControl();
                console.log('Original access control:')
                console.log(accessControl);
                const agent = { name: 'http://flordigipolis.inrupt.net/profile/card#me', access: ['Read'] }
                await acl.addAgent(agent);
                var accessControl = await acl.readAccessControl();
                console.log('New access control:')
                console.log(accessControl);

                console.log('Extra read permission added\nSending second update...')
                // Send second update
                const add2 = $rdf.st($rdf.sym('http://example.org/someOtherPerson'), RDF('type'), $rdf.sym('http://example.org/person'), $rdf.sym(FILE));
                updater.update(null, add2, (uri, ok, msg) => {
                    if(ok){
                        // Update happened succesfully
                        console.log('Second update sent')
                    } else {
                        console.log(msg);
                    }
                })
            } else {
                // Something went wrong
                console.log(msg);
            }
        })
    } catch (err) {
        console.log(err)
    }
}

run()

This should reproduce the issue when running it in a Node environment (sorry for the hardcoded variables, this was quickly put together).
When I run it the following happens:

  • First run everything goes fine, it creates the file, adds the first update, creates the ACL (printed before and after in console) and performs the second update.
  • If I run it a second time however, I get a 403 error at the createIfNotExists command.
    Also, when I go look on my pod, I have seem to have lost all permissions over the file in question.
  • Critically, this does not happen if I simply remove the : in the FILE variable, then everything just works fine, no matter how many times I run the script.

Hope this helps (maybe you can figure some things out by looking at the file on my Pod).