8.3 File Upload route permissions issue

Hi,
We’ve run into a permission issue when updating our module to 8.3
Out SNMP module has a setting for uploading and listing additional configuration files like so:

    // Upload MIB files

    this.routeGroup.newRoute("/mib/upload")

            .method(HttpMethod.POST)

            .type("application/json")

            .nocache()

            .handler(this::uploadMibFiles)

            .requirePermission(PermissionType.WRITE)

            .mount();



    // List uploaded MIB files

    this.routeGroup.newRoute("/mib/list")

            .method(HttpMethod.GET)

            .type("application/json")

            .nocache()

            .handler(this::listMibFiles)

            .requirePermission(PermissionType.READ)

            .mount();

We then have a GET and a POST in our react code to list uploaded files and upload them respectively:

getMibFiles: builder.query<MibFileListResponse, void>({

  query: () => \`/data/snmp/mib/list\`,

}),

uploadMibFiles: builder.mutation<UploadResponse, FormData>({

  query: (formData) => ({

    url: \`/data/snmp/mib/upload\`,

    method: "POST",

    body: formData,

  }),

}),

While listing files works without a hitch, when uploading a file, we receive a 403 response from the gateway. There is nothing in the gateway log. It seems that the problem stems from the:

requirePermission(PermissionType.WRITE)

If we try to change this permission, the route mounting fails Ignition’s internal validation as unsafe, and the routes don’t mount at all.

Are we missing something in our route setup or mounting?

PermissionType.WRITE implicitly declares that the incoming request must be authorized with the gateway write permission (set in your gateway security settings), by either a logged in user session, a valid API key, or a security zone based permission.

If you don't want to use those or want to provide your own access control, use RouteMounter.accessControl instead. If you want to make a totally public route, pass AccessControlStrategy.OPEN_ROUTE in the accessControl builder method.

That’s kind of the problem, since the requests are done under an authenticated user with the Administrator role, which has the write permissions.

1 Like

Ah, okay, Perspective works differently (because it's not part of platform).

So if you're in a module that depends on Perspective you should have com.inductiveautomation.perspective.gateway.comm.Routes on your classpath - that class has a utility method you can use:
requireSession method you can use - just pass in the "session scopes" (runtime session or designer) allowed.

This is an SNMP module, not anything to do with Perspective.

Okay, last try (too distracted today).

The POST is likely failing because it's missing a CSRF token implicitly required by 'unsafe' HTTP methods and required by the WRITE PermissionType.

com.inductiveautomation.ignition.gateway.web.session.WebUiSession#CSRF_TOKEN_HEADER

The CSRF token for your current session can be retrieved from /data/app/session.

3 Likes

Heh. I thought so. I figured out I would need a csrf token by watching the headers in the network tab of a session while editing the IA native modbus driver's address map. Now I know where to find it for my own use...

{Wrapping up my v8.3 module conversions soon.}

I banged my head on this. Wrong approach, apparently. My IDE is happy with this:

const csrf = useSelector((store: ReduxRootState) => store.userSession.csrfToken);

Will test tomorrow. My brain is fried for the day.

1 Like

We got it working.

Your react code needs something like this:

async function getCsrfToken(): Promise<string | null> {

if (cachedCsrfToken) {

return cachedCsrfToken;

}

try {

const response = await fetch("/data/app/session");

if (response.ok) {

  const data = await response.json();

  cachedCsrfToken = data.csrfToken || null;

  return cachedCsrfToken;

}

} catch (error) {

console.error("Failed to fetch CSRF token:", error);

}

return null;

}

Then you place that in the header like so:

const csrfToken = await getCsrfToken();

if (csrfToken) {

  headers.set("x-csrf-token", csrfToken);

}

Regards,
Serghei

I couldn't convince myself I actually needed to fetch from that route, since the session store already in the browser shows that it already knows the CSRF token. The one-liner I show replaces your fetching and caching operations.

So, there are two different csrf tokens. I’ve done a comparison, and the one in the cookies doesn’t match the one in the data/app/session :man_shrugging:t4:

I'm not getting it from the cookies. I'm getting it from the react-redux session.

I would suggest confirming that the csrf token in react-redux session is the same as one in /data/app/session. If there are 2 different tokens, there could be 3 different tokens.

My retrieval of the token from the react-redux store worked just fine.

As did the rest of the config file import process, so the end of the tunnel for my beta drivers is in sight. :grin:

1 Like