Skip to content

Table permissions#10

Open
pdowler wants to merge 10 commits into
ivoa-std:mainfrom
pdowler:table-permissions
Open

Table permissions#10
pdowler wants to merge 10 commits into
ivoa-std:mainfrom
pdowler:table-permissions

Conversation

@pdowler
Copy link
Copy Markdown
Collaborator

@pdowler pdowler commented May 20, 2025

Possible solution using HTTP headers to convey minimal permission related information.
For issue #8 this is option 3.

As optional headers, any service can ignore request headers they do not support and only include response headers they do support.

This adds 3 optional headers that can be included in:
PUT /tables/{name} -- set permissions during create table op
POST /tables/{name} -- set permissions on an existing table
GET /tables/{name} -- headers will describe the current permissions

The 3 headers are: x-vosi-anon-read, x-vosi-group-read, x-vosi-group-write.

In addition, a 4th header specifies the owner of the table: x-vosi-owner and is only used in output from the service (GET requests).

Detail to be described in the standard document: a lack of x-vosi-owner header can be interpretted as "this is a normal table"... maybe we also specify that a missing x-anon-read header means the same thing as true: this is a normal table anyone can query?

pros:

  • headers can be used by the client to change permissions and the server to output the current permissions independent of the document format; this is much simpler that modifying VOSI-table schema or making up an ad-hoc way to encode this info in VOTable
  • it is easy for services that do not implement user-managed permissions to ignore incoming headers and never send any for GET requests

cons:

  • as noted above in "details", we need to specify how a client can tell that a service does not accept/support user-managed permissions... need to prototype to see

pdowler added 4 commits May 9, 2025 13:20
the table description payload does not contain any notion of
table ownership or permissions; this defines 3 headers that can
be used to set and get minimum permissions without encumbering
the table description document format(s)

these headers are entirely optional and would only be applicable
if a service wants to expose and allow modification of permissions
via the API
@pdowler
Copy link
Copy Markdown
Collaborator Author

pdowler commented May 6, 2026

If headers prove to a good/acceptable way to get and set permissions, this may also be a suitable approach to manage other optional attributes.
eg. lifetime of tables that are longer-lived than TAP upload tables but not permanent
TBD

@pdowler pdowler marked this pull request as ready for review May 6, 2026 22:57
Copy link
Copy Markdown
Collaborator

@brianmajor brianmajor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our other (minimal) use of headers, though not part of any spec at the moment, is for communicating your authentication status. If I call a service with my credentials I see

x-vo-authenticated: bmajor

Should we strive for a convention where IVOA headers start with x-vo-?

@Zarquan
Copy link
Copy Markdown
Member

Zarquan commented May 9, 2026

Should we strive for a convention where IVOA headers start with x-vo-?

I think this is a good idea, but we should use x-ivoa rather than just x-vo
The term vo has many different meanings.

If someone outside our community notices these odd headers being sent to their service they might try to use a search engine to find out where they are coming from. Putting ivoa into a search gets better results than vo.

@pdowler
Copy link
Copy Markdown
Collaborator Author

pdowler commented May 28, 2026

I think if headers work out as an acceptable approach (more below) we could considier VOSI as a good place to define general purpose headers. I would include the current x-vo-authenticated in there (it was nominally in SSO-next).

For naming style, x-vo- was short; x-ivoa- would be a viable option. Here I used x-vosi- because they are defined in VOSI, but that's not required.

Headers currently defined here:

x-vosi-auth-read -- indicates that authentication/authorization are required to access
x-vosi-group-read -- indicates members of this group have read-only permission
x-vosi-group-write -- indicates members of this group have read-write permission
x-vosi-owner -- indicates the user that owns (created)

By switching to x-vosi-auth-read: true the interpretation of a missing header as false means that this header is only ever added when needed. Standard TAP tables that can be queried anonymously (eg tap_schema tables and other tables in normal TAP services) look the same as before.

The general idea to be documented in the standard is that services do not output any of these headers for normal tables, but can output them if they wish to reveal the info to the caller. For input (PUT and POST actions) services can simply ignore (reject?) attempts to set any of these that they do not support or allow. For example, The OpenCADC youcat prototype does not allow users to change the ownership of a table, so it can ignore or reject x-vosi-owner in a POST request.

more on implementation experience to follow...

@pdowler
Copy link
Copy Markdown
Collaborator Author

pdowler commented May 28, 2026

Implementation experience (unreleased version of OpenCADC youcat):

POST to set auth-read, group-read, and group-write attributes on schemas and tables using headers is working; can combine update metadata (body document) and permissions in one request

  • tricky: accept a POST with only headers (no body, no content-type) to only manipulate permissions
  • more tricky: no obvious way to clear an attr (reset to null) using the same header
    PUT to set attributes during create table is working and was easy to make it atomic

This part not in the openapi...

To clear existing permissions, we could either define special values or a set of headers to unset.

There is prior art in having headers behave like commands (set-cookie is an instruction, not just passing information) so in principle set and unset make some sense.

I implemented "clear a permission setting in two ways: special header value null (can't successfully get a blank string through) and a separate list of unset headers (x-vosi-unset-auth-read, etc). I made both approaches work.

The special values felt a little icky, but I don't see any obvious problems aside from arguing about null vs None vs nil.

x-vosi-auth-read: true|false|null
x-vosi-group-read: {group uri} | null
x-vosi-group-write: {group uri} | null

OpenAPI includes a boolean type but it's not strictly required to be serialised as "true", so one best practice I found was to use an enum, which then allows for that special null value.

The explicit unset headers are straightforward but one has to define whether set or unset has precedence if/when a client sends both. I chose unset to have precedence (and ignore the corresponding set header). This option will require more explaining in the standard doc and even then clients and services can (will) make mistakes.

TL;DR - I think defining a special null value for the above 3 headers is simpler and preferred and I will also change boolean to enum (here at least, maybe in general).

use enum instead of boolean for explicit serialisation
@pdowler
Copy link
Copy Markdown
Collaborator Author

pdowler commented May 28, 2026

OpenAPI updated to use special values; actual value null is TBD.

aside: this value inspired by database where boolean has 3 values (true,false,null) more than other programming languages.

@brianmajor
Copy link
Copy Markdown
Collaborator

Both approaches seem reasonable to me, so going with 'special value' sounds good. We talked about this a bit yesterday @pdowler -- what about when multiple, conflicting headers are provided? For example, x-vosi-auth-read=my-group and x-vosi-auth-read=null. 400 - Bad Request?

@pdowler
Copy link
Copy Markdown
Collaborator Author

pdowler commented May 29, 2026

I did find an OpenAPI mechanism to differentiate between single-valued and multi-valued params and I think the spec here means single values (because type: string rather than type: array) but I haven't confirmed if that's strictly true. The error response for that would be specified in the prose document (because lots of different bad input all map to 400) but yeah: probably 400.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants