From 84494d6ebfbd9647f068040eb4703fc5f02e7124 Mon Sep 17 00:00:00 2001 From: Kobe Mertens <37635647+kobemertens@users.noreply.github.com> Date: Mon, 25 Aug 2025 16:13:36 +0200 Subject: [PATCH 01/23] Update README.md --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 7ad355b..33d7247 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,38 @@ Next, add the following contents to the config file mounted in `./config/authori It basically configures read/write access for everyone for all data on the `http://mu.semte.ch/graphs/public` graph. +## Tutorials +### Defining prefixes +In order to use the CURIE (Compact URI) form (e.g. `foaf:name`) we need to define the prefixes first. This is done as follows: +```lisp +(define-prefixes + :adms "http://www.w3.org/ns/adms#" + :cal "http://www.w3.org/2002/12/cal/ical#" + :cogs "http://vocab.deri.ie/cogs#" + :dcat "http://www.w3.org/ns/dcat#" + :ext "http://mu.semte.ch/vocabularies/ext/" + :eli "http://data.europa.eu/eli/ontology#") +``` +### Specifying which triples are accessible from which graphs +For this we need a `define-graph` block. This looks as follows: +```lisp +(define-graph organization ("http://mu.semte.ch/graphs/organizations/") + ("foaf:Person" -> _) + ("foaf:OnlineAccount" x> "ext:password") +``` +The `define-graph` macro takes a unique identifier, the URI of the graph where the triples are stored and one or more triple shapes. +The triple shapes have this form: `( )`. `` and `` must be a URI string (e.g. `"foaf:Person"`) or a `_` (indicating a wildcard). Triples that match these shapes will go to (or retrieved from) the specified graph (in the above example this is `http://mu.semte.ch/graphs/organizations/`). +These are all the possible operators: +- `T -> p`: Triples where the subject is of type `T` and the predicate is `p`. +- `T <- p`: Triples where the object is of type `T` and the predicate is `p`. +- `T x> p`: For triples where the subject is of type `T`, allow every predicate except for `p`. +- `T Date: Mon, 25 Aug 2025 17:03:04 +0200 Subject: [PATCH 02/23] restructure and add to readme --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 33d7247..852c157 100644 --- a/README.md +++ b/README.md @@ -64,26 +64,36 @@ Next, add the following contents to the config file mounted in `./config/authori It basically configures read/write access for everyone for all data on the `http://mu.semte.ch/graphs/public` graph. ## Tutorials -### Defining prefixes -In order to use the CURIE (Compact URI) form (e.g. `foaf:name`) we need to define the prefixes first. This is done as follows: +### Specifying groups of users +sparql-parser does authentication based on user groups. We will later define which groups are allowed to perform which operations on which data. So first we need to define some user groups. +User groups are defined based on the result of a query involving the users session id. This can look as follows: ```lisp -(define-prefixes - :adms "http://www.w3.org/ns/adms#" - :cal "http://www.w3.org/2002/12/cal/ical#" - :cogs "http://vocab.deri.ie/cogs#" - :dcat "http://www.w3.org/ns/dcat#" - :ext "http://mu.semte.ch/vocabularies/ext/" - :eli "http://data.europa.eu/eli/ontology#") +(supply-allowed-group "super-mega-admins" + :parameters ("session_group_id" "session_role") + :query "PREFIX ext: + PREFIX mu: + + SELECT ?session_group ?session_role WHERE { + ext:sessionGroup/mu:uuid ?session_group_id; + ext:sessionRole ?session_role. + FILTER( ?session_role = \"SuperMegaAdmin\" ) + }") ``` +If this query returns a result, the user will belong to the `super-mega-admins` group. The value for `` will be filled in automatically at runtime. The value of the variables listed in the `:parameters` argument will be joined using `/` and appended to the graph URI when it is accessed. This allows us to have a separate graph per user group. + ### Specifying which triples are accessible from which graphs -For this we need a `define-graph` block. This looks as follows: +For this we need a `define-graph` block. This will create a *graph spec* and looks as follows: ```lisp (define-graph organization ("http://mu.semte.ch/graphs/organizations/") ("foaf:Person" -> _) - ("foaf:OnlineAccount" x> "ext:password") + ("foaf:OnlineAccount" x> "ext:password")) ``` -The `define-graph` macro takes a unique identifier, the URI of the graph where the triples are stored and one or more triple shapes. -The triple shapes have this form: `( )`. `` and `` must be a URI string (e.g. `"foaf:Person"`) or a `_` (indicating a wildcard). Triples that match these shapes will go to (or retrieved from) the specified graph (in the above example this is `http://mu.semte.ch/graphs/organizations/`). +**NOTE**: Any prefixes such as `foaf` and `ext` need to be defined, see [Defining prefixes](#defining-prefixes) + +The `define-graph` macro takes a unique identifier, the URI of the graph where the triples are stored, and one or more triple shapes. +The triple shapes have this form: `( )`. `` and `` must be a URI string (e.g. `"foaf:Person"`) or a `_` (indicating a wildcard). Triples that match these shapes will go to (or retrieved from) the specified graph (in the above example this is `http://mu.semte.ch/graphs/organizations/`). +**Note**: different *graph specs* can specify the same graph URI. + These are all the possible operators: - `T -> p`: Triples where the subject is of type `T` and the predicate is `p`. - `T <- p`: Triples where the object is of type `T` and the predicate is `p`. @@ -91,11 +101,35 @@ These are all the possible operators: - `T Date: Mon, 25 Aug 2025 17:26:21 +0200 Subject: [PATCH 03/23] improve structure --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 852c157..e4658a3 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ It basically configures read/write access for everyone for all data on the `http ## Tutorials ### Specifying groups of users sparql-parser does authentication based on user groups. We will later define which groups are allowed to perform which operations on which data. So first we need to define some user groups. -User groups are defined based on the result of a query involving the users session id. This can look as follows: +User groups are defined based on the result of a query involving the user's session id. This can look as follows: ```lisp (supply-allowed-group "super-mega-admins" :parameters ("session_group_id" "session_role") @@ -90,7 +90,11 @@ For this we need a `define-graph` block. This will create a *graph spec* and loo ``` **NOTE**: Any prefixes such as `foaf` and `ext` need to be defined, see [Defining prefixes](#defining-prefixes) -The `define-graph` macro takes a unique identifier, the URI of the graph where the triples are stored, and one or more triple shapes. +The `define-graph` macro takes: +- A unique identifier (*here `organization`*) +- The URI of the graph where the triples are stored (*here `http://mu.semte.ch/graphs/organizations/`*) +- One or more triple shapes (*here `("foaf:Person" -> _)` and `("foaf:OnlineAccount" x> "ext:password")`*). + The triple shapes have this form: `( )`. `` and `` must be a URI string (e.g. `"foaf:Person"`) or a `_` (indicating a wildcard). Triples that match these shapes will go to (or retrieved from) the specified graph (in the above example this is `http://mu.semte.ch/graphs/organizations/`). **Note**: different *graph specs* can specify the same graph URI. @@ -105,15 +109,18 @@ In the above example this means the following: - Matches all triples where the subject is of type `foaf:OnlineAccount` and where the predicate is not `ext:password`. ### Specifying which user groups have access to which graphs -Finally we need to specify which users are allowed to access which *graph spec*. This is done using the `grant` macro. +Finally we need to specify which *user groups* are allowed to access which *graph spec*. This is done using the `grant` macro. ```lisp (grant (read write) :to-graph (organization) :for-allowed-group "super-mega-admins") ``` This indicates that we allow the users in the `super-mega-admins` group to read and write tripes from and to the `http://mu.semte.ch/graphs/organizations/` graph, according to the triple restrictions in the `organization` *graph spec*. + The only allowed operation values are `read` and `write`. + `:to-graph` allows specifying multiple *graph specs*. + `:for-allowed-group` specifies which user group is allowed to execute the specified operations. From afc329ea29bd174847bfcdbea13a0d5f94750f91 Mon Sep 17 00:00:00 2001 From: onodrim Date: Wed, 4 Feb 2026 12:29:26 +0100 Subject: [PATCH 04/23] doc: structure according to documentation system --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e4658a3..ac98532 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A rewritten implementation of [`mu-authorization`](https://github.com/mu-semtech > This README is currently incomplete and configuring this service requires diving into the code and comparing with other existing configurations. > We're working on writing a full configuration guide. -## Getting started +## Tutorials ### How to add the sparql-server to your application Add the service to your `docker-compose.yml`: ```yaml @@ -63,7 +63,7 @@ Next, add the following contents to the config file mounted in `./config/authori It basically configures read/write access for everyone for all data on the `http://mu.semte.ch/graphs/public` graph. -## Tutorials +## How-to guides ### Specifying groups of users sparql-parser does authentication based on user groups. We will later define which groups are allowed to perform which operations on which data. So first we need to define some user groups. User groups are defined based on the result of a query involving the user's session id. This can look as follows: @@ -123,8 +123,6 @@ The only allowed operation values are `read` and `write`. `:for-allowed-group` specifies which user group is allowed to execute the specified operations. - -## Reference ### Defining prefixes In order to use the CURIE (Compact URI) form (e.g. `foaf:name`) we need to define the prefixes first. This is done as follows: ```lisp @@ -137,8 +135,11 @@ In order to use the CURIE (Compact URI) form (e.g. `foaf:name`) we need to defin :eli "http://data.europa.eu/eli/ontology#") ``` **NOTE**: This does not affect prefixes that can be used in sparql query strings used in this config. They still need to be specified using the `PREFIX` keyword. -### Existing configurations +## Reference +TODO + +## Existing configurations The following projects are currently using this service as a replacement of `mu-authorization`, either fully or in a limited capacity (e.g. only on the development or testing server). We link their configuration files to provide From 40c11a28def8c067601088fbdc13e9cebf7191bd Mon Sep 17 00:00:00 2001 From: onodrim Date: Wed, 4 Feb 2026 11:21:56 +0100 Subject: [PATCH 05/23] doc(reference): define-graph macro --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac98532..ae97cae 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,32 @@ In order to use the CURIE (Compact URI) form (e.g. `foaf:name`) we need to defin **NOTE**: This does not affect prefixes that can be used in sparql query strings used in this config. They still need to be specified using the `PREFIX` keyword. ## Reference -TODO +### ACL configuration interface +#### `define-graph` +A graph-specification essentially describes the set of triples in a graph to which rights can be assigned. It does this using one or more type-specifications. A type-specification in turn specifies a resource type and predicates that capture the relevant triples. A graph-specification is created using the `define-graph` macro: + +```lisp +define-graph (name (graph &rest args &key (sparql t sparql-p) (delta t delta-p) (file nil file-p) (operations #'identity operations-p)) + &body type-specifications) +``` + +Parameters: +- *`name`* A symbol with which the created `graph-specification` can be referenced in the remainder of the configuration. The name should **not** blank spaces and should **not** be surrounded with (double) quotes. +- *`graph`* A string that is (the prefix for) a URI of a graph in which the triples are stored. + +Keyword parameters: +- *`sparql`* If set to `nil` do **not** send SPARQL queries for this `graph-specification` to the backend. (default: `t`) +- *`delta`* If set to `nil` do **not** generate delta messages for changes for this `graph-specification`. (default: `t`) +- *`file`* Currently not used, intended for future functionality. +- *`operations`* Currently not used, intended for future functionality. + +The *`body`* of a `define-graph` call contains one or more type-specifications. A `type-specification` has the form: `( [ ]+)`. `` and `` must be a URI string (e.g. `"http://xmlns.com/foaf/0.1/Person"` or `"foaf:Person"` if you have defined `foaf:` as a [prefix](#defining-prefixes)) or a `_` to indicate a wildcard. + +There are four supported operations that can be used in a type-specification +- `T -> p`: Triples where the **subject** is of type `T` and the predicate is `p`. +- `T <- p`: Triples where the **object** is of type `T` and the predicate is `p`. +- `T x> p`: For triples where the **subject** is of type `T`, allow every predicate **except** for `p`. +- `T Date: Wed, 4 Feb 2026 11:32:43 +0100 Subject: [PATCH 06/23] doc(reference): supply-allowed-group --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index ae97cae..510433b 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,25 @@ There are four supported operations that can be used in a type-specification - `T x> p`: For triples where the **subject** is of type `T`, allow every predicate **except** for `p`. - `T `. At runtime, `` will be replaced by the value for `mu-session-id` in incoming requests. +- *`:parameters`* a list of strings that is a subset of the variable names used for matches returned by the query. Has no effect if no value is provided for `:query`. +- *`:constraint`* A symbol to specify how group membership should be determined. The supported values are: + + `always`: All users belong to this group. Any specified `:query` (and `:parameters`) will be ignored. + + `never`: No user can belong to this group, and it cannot be used in any access-grants. Any valued for `:query` and/or `:parameters` will be ignored. + + `query`: Whether a user belongs to this group is determined by the query provided in the `:query"` parameter. + + `nil` or no value provided: Same as `query` if a value is provided for the `:query` keyword parameter, otherwise same as `always`. + ## Existing configurations The following projects are currently using this service as a replacement of `mu-authorization`, either fully or in a limited capacity (e.g. only on the From 7185fd9c969ba796f019e2f5429a1334964740d1 Mon Sep 17 00:00:00 2001 From: onodrim Date: Wed, 4 Feb 2026 11:33:12 +0100 Subject: [PATCH 07/23] doc(reference): grant --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 510433b..8bb0436 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,25 @@ Keyword parameters: + `query`: Whether a user belongs to this group is determined by the query provided in the `:query"` parameter. + `nil` or no value provided: Same as `query` if a value is provided for the `:query` keyword parameter, otherwise same as `always`. +#### `define-grant` +An access-grant gives usage rights for a `graph-specification` to an `allowed-group` and can be created using the `define-grant` macro. + +```lisp +grant (right &key to-graph for-allowed-group to for scopes) +``` + +Parameters: +- *`right`* A list of rights that is to be granted, currently only `read` and `write` are supported. + +Keyword parameters: +- *`:to-graph`* The names of one or more a previously defined graph-specifications. If multiple names are provided they must be surrounded by brackets: `(someName anotherName)`. +- *`:for-allowed-group`* The names of one or more previously defined groups, each name quoted as a string. If multiple names are provided they must be surrounded by brackets: `("someGroup" "anotherGroup")`. +- *`:to`* Alias for `:to-graph`. +- *`:for`* Alias for `:for-allowed-group`. +- *`:scopes`* TODO + +**NOTE**: if values for both keyword parameters `:to-graph` and `:to` are provided these values are merged into a single list. Same for `:for-allowed-group` and `:for`. + ## Existing configurations The following projects are currently using this service as a replacement of `mu-authorization`, either fully or in a limited capacity (e.g. only on the From be9e8b438910b1ccea17d28ff1b5e6c325f3f5f3 Mon Sep 17 00:00:00 2001 From: onodrim Date: Wed, 4 Feb 2026 12:41:11 +0100 Subject: [PATCH 08/23] doc(reference): define-prefixes --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 8bb0436..c44e14f 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,33 @@ Keyword parameters: **NOTE**: if values for both keyword parameters `:to-graph` and `:to` are provided these values are merged into a single list. Same for `:for-allowed-group` and `:for`. +#### `define-prefixes` +The `define-prefixes` macro allows to map prefixes to their corresponding expansion, allowing to use CURIEs in the configuration. + +```lisp +define-prefixes (&body body) +``` + +The *`body`* has to be a sequence of keyword/value pairs, each pair of the form`:LABEL "EXPANSION"` where: + +- Each keyword `:LABEL` MUST be preceded by a colon ':' and MAY NOT include a trailing colon. +- Each value `"EXPANSION"` MUST be a string and be surrounded by double quotes. +- Each prefix MUST be defined **before** its first use in the configuration file. +- It is allowed to have multiple `define-prefixes` in a single configuration file. + + +- **NOTE**: These prefixes **cannot** be used in SPARQL query strings such as those provided in `supply-allowed-group`. Such queries prefixes still need to specify their own prefixes using the `PREFIX` keyword. + +The following concrete example snippet defines three prefixes. + +```lisp +(in-package :acl) +(define-prefixes + :foaf "http://xmlns.com/foaf/0.1/" + :adms "http://www.w3.org/ns/adms#" + :dcat "http://www.w3.org/ns/dcat#") +``` + ## Existing configurations The following projects are currently using this service as a replacement of `mu-authorization`, either fully or in a limited capacity (e.g. only on the From cbdaf01edf7d7ea3b0861f920f128f3202be2eb2 Mon Sep 17 00:00:00 2001 From: onodrim Date: Mon, 2 Feb 2026 15:23:33 +0100 Subject: [PATCH 09/23] doc(reference): list configurable variables per package --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/README.md b/README.md index c44e14f..8c71884 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,63 @@ The following concrete example snippet defines three prefixes. :dcat "http://www.w3.org/ns/dcat#") ``` +### Configurable variables +The following sections list, per package, the available variables that can be (indirectly) configured. + +#### acl +- *`*access-specifications*`* List of all known access groups. Do **not** edit directly but use the `acl:supply-allowed-group` macro to define groups. (default: `nil`) +- *`*graphs*`* List all all known graph-specification instances. Do **not** edit directly but use the `acl:define-graph` macro to add graph-specifications. (default: `nil`) +- *`*rights*`* List of all known grant instances connecting access-specification to graph. Do **not** edit directly but use the `acl:grant` macro to define grants. (default: `nil`) + +#### client +- *`*backend*`* The SPARQL endpoint(s) to talk to, allowed values are either a single string or a list of strings. Over time this variable will be deprecated in favor of using `*backends*`. (default: `"http://triplestore:8890/sparql"`) +- *`*backends*`* A list of objects representing SPARQL endpoint(s) to talk to. The contained objects should be created using the `acl::make-sparql-endpoint` function with a URL string as argument. If not explicitly set, this variable is populated based on the value of `*backend*`. (default: `nil`) +- *`*max-concurrent-connections*`* The maximum amount of concurrent queries sent to an individual backend. (default: `8`) + +- *`*log-sparql-query-roundtrip*`* If set to non-nil, log both the outgoing query sent to and response received from the backend to the standard output. (default: `nil`) +- *`*log-failing-query-tries*`* If set to non-nil, log queries which fail in exponential backoff retry to the standard output. (default: `t`) +- *`*log-failing-query-tries-with-condition*`* If set to non-nil, log the condition for queries which fail in exponential backoff retry to the standard output. (default: `t`) +- *`*log-batch-mapping*`* If set to non-nil, warn on processes which want to execute batch mapping. Note, batch mapping is **not yet implemented** and will process as one big query. (default: `nil`) + +- *`*max-query-time-for-retries*`* This is the maximum amount of time (in seconds) to wait until retrying to send a query. (default: `10`) +- *`*max-query-time-for-retries-in-followup-queries*`* This is the maximum amount of time (in seconds) to wait until retrying to send the query once we've already sent the first query. (default: `50`) +- *`*acquire-db-semaphore-timeout*`* Amount of time (in seconds) to wait to acquire a semaphore for a SPARQL endpoint. If set to `nil`, wait forever. (default: `55`) + +#### server +- *`*log-incoming-requests-p*`* If set to non-nil, log incoming requests and access rights for them to the standard output. (default: `nil`) + +#### prefix +- *`*prefixes*`* List of known prefixes with their expansions. Do **not** edit directly, but use the `prefix:define-prefixes` macro to add entries. (default: `'(:skos "http://www.w3.org/2004/02/skos/core#" :schema "http://schema.org/" :rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#)`) + +- *`*uri-protocol-check-on-prefix-expansion*`* If set to non-nil, only expand CURIEs whose prefix expands to a URI for a protocol in `*uri-protocol-accept-list-for-prefix-expansion*` and signal an error upon encountering URIs with other protocols. If `nil`, expand any CURIE irrelevant of the protocol. (default: `t`) +- *`*uri-protocol-accept-list-for-prefix-expansion*`* A list of protocols that is accepted during the prefix expansion. (default: `'("http:" "https:" "mailto:" "ftp:" "ftps:" "share:)"`) + +#### delta-messenger +- *`*delta-handlers*`* List of handlers for the delta messages. Do **not** edit directly but use the `delta-messenger:add-delta-messenger` function to add delta handlers. (default: `nil`) +- *`*log-delta-messenger-message-bus-processing*`* If set to non-nil, log when the delta messenger runs and for what to the standard output. (default `nil`) + +- *`*max-sleep-on-idle-bus*`* The maximum amount of seconds to sleep until the bus is considered idle. This is a safety setting that would cover an erroneous semaphore implementation and can be disable by setting it to `nil`. (default: `60`) +- *`*message-bus-consumer*`* Consumer function to be called for each message on the message bus. (default: `delta-messenger::execute-scheduled-remote-delta-message`) + +#### type-cache +- *`*uri-graph-user-type-providers*`* A list of functions that can calculate the types for a list of combined graph and URI. Do **not** set directly, but use the `type-cache::add-type-for-prefix` function to add types, this constructs the appropriate functions automatically. (default: `nil`) +- *`*debug-prefix-functions*`* If set to non-nil, emit debugging information for prefix type functions to the standard output. (default: `nil`) + +#### support +- *`*string-max-size*`* Maximum size of a string before it gets converted to a file. (default: `4096`) +- *`*file-abbreviation-uri-prefix*`* Prefix for the URIs which will contain the abbreviation of a string. (default `http://services.redpencil.io/sparql-parser/abbreviations/`) +- *`*sha-file-directory*`* The directory where string-files will be stored, should be a mounted volume. (default `/data/strings/`) + +#### administration +- *`*long-db-strings-to-move-per-batch*`* How many long strings in the DB are moved to files per batch. Used to batch the operations in `administration:update-database-long-strings-to-string-files`. (default: `10`) + +#### quad-transformations +- *`*user-quad-transform-functions*`* List of quad transformation functions to try in the order in which they should be applied. These functions can be used to transform quads in insert or delete queries, for example to transform a single quad to multiple ones or vice versa. (default: `nil`) + +#### handle-update-unit +- *`*max-query-size-heuristic*`* Heuristic indicating roughly how many characters the body of quads may be in a single query. Note, current implementation will try to query even if over this size. (default: `8000`) +- *`*max-quads-per-query-heuristic*`* Heuristic indicating roughly how many quads could be in a single query for insert or query. (default: `100`) + ## Existing configurations The following projects are currently using this service as a replacement of `mu-authorization`, either fully or in a limited capacity (e.g. only on the From 318c625ca8c18d91bda7a3b6d6fead13f8763bfd Mon Sep 17 00:00:00 2001 From: onodrim Date: Fri, 30 Jan 2026 12:19:35 +0100 Subject: [PATCH 10/23] doc(guide): define access groups for users --- README.md | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8c71884..6e80e2f 100644 --- a/README.md +++ b/README.md @@ -64,22 +64,37 @@ Next, add the following contents to the config file mounted in `./config/authori It basically configures read/write access for everyone for all data on the `http://mu.semte.ch/graphs/public` graph. ## How-to guides -### Specifying groups of users -sparql-parser does authentication based on user groups. We will later define which groups are allowed to perform which operations on which data. So first we need to define some user groups. -User groups are defined based on the result of a query involving the user's session id. This can look as follows: +### Define a group for users with a certain role +An access control policy typically grants different rights to users based on some criteria. For example, an authenticated user may read and edit certain data, whereas other users are only allowed to read data. This requires that we can determine to which group(s) the user performing a request belongs to. In a sparql-parser configuration is done using the `supply-allowed-group` macro. This macro supports defining SPARQL queries to determine whether a user belongs to a group. More specifically, the provided query should return a match when a user belongs to the defined group. + +Say you want to define a group that contains all authenticated users. In a semantic.works application this usually means that there exists a session associated with an account, indicating that the user previously logged in. The following snippet defines a group named `authenticated` where membership is determined by the existence of a session associated with an account: + +```lisp +(in-package :acl) + +(supply-allowed-group "authenticated" + :query "PREFIX session: + + SELECT DISTINCT ?account WHERE { + session:account ?account. + }") +``` + +Note that the constant `SESSION_ID` is a placeholder and will be automatically replaced by the actual session identifier found in the request when the query is executed. + +For users with a certain role, say `SuperMegaAdmin`, a similar query can be used which filters based on the role associated with a session: + ```lisp (supply-allowed-group "super-mega-admins" - :parameters ("session_group_id" "session_role") :query "PREFIX ext: - PREFIX mu: - SELECT ?session_group ?session_role WHERE { - ext:sessionGroup/mu:uuid ?session_group_id; - ext:sessionRole ?session_role. - FILTER( ?session_role = \"SuperMegaAdmin\" ) - }") + SELECT DISTINCT ?session_role WHERE { + ext:sessionRole ?session_role . + FILTER( ?session_role = \"SuperMegaAdmin\" ) + }") ``` -If this query returns a result, the user will belong to the `super-mega-admins` group. The value for `` will be filled in automatically at runtime. The value of the variables listed in the `:parameters` argument will be joined using `/` and appended to the graph URI when it is accessed. This allows us to have a separate graph per user group. + +While it is rather common to define group membership based on roles, sparql-parser is not limited to this and allows arbitrary queries to be specified. It depends on your application's data model which queries make sense. Note, in the above examples the actual matches returned by the queries are not used, [TODO: link to guide] shows how you can use these matches to simplify access control policies in some situations. ### Specifying which triples are accessible from which graphs For this we need a `define-graph` block. This will create a *graph spec* and looks as follows: From 071df7b695b5aaeac5eb0ade58d07e9da194af0d Mon Sep 17 00:00:00 2001 From: onodrim Date: Fri, 30 Jan 2026 15:24:39 +0100 Subject: [PATCH 11/23] doc(guide): capture triples using graph-specifications --- README.md | 64 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 6e80e2f..4707462 100644 --- a/README.md +++ b/README.md @@ -96,32 +96,58 @@ For users with a certain role, say `SuperMegaAdmin`, a similar query can be used While it is rather common to define group membership based on roles, sparql-parser is not limited to this and allows arbitrary queries to be specified. It depends on your application's data model which queries make sense. Note, in the above examples the actual matches returned by the queries are not used, [TODO: link to guide] shows how you can use these matches to simplify access control policies in some situations. -### Specifying which triples are accessible from which graphs -For this we need a `define-graph` block. This will create a *graph spec* and looks as follows: +### Define which triples are accessible for a graph +Typically you want to explicitly specify which (kind of) triples within a graph an access control rule can be applied to. In sparql-parser such information is captured by *graph-specifications* which you create using the `define-graph` macro. For readability the code snippets use CURIEs as explained in the guide on TODO: link to prefix guide + +For instance, say you have a graph `http://mu.semte.ch/graphs/people` containing triples for resources of types `foaf:Person` and `foaf:OnlineAccount`. The following snippet creates a graph-specification for that graph which covers all triples for resources of these two types. Here `people` is a unique identifier by which this graph-specification can be referred to later on. The URI of the target graph, `http://mu.semte.ch/graphs/people`, is specified as a string between brackets and double quotes. + ```lisp -(define-graph organization ("http://mu.semte.ch/graphs/organizations/") +(in-package :acl) +(define-graph people ("http://mu.semte.ch/graphs/people") ("foaf:Person" -> _) - ("foaf:OnlineAccount" x> "ext:password")) + ("foaf:OnlineAccount" -> _)) +``` + +The remaining elements, `("foaf:Person" -> _)` and `("foaf:OnlineAccount" -> _)`, are so-called *type-specifications*. A type-specification specifies which triples are considered relevant for a certain resource type. More specifically, `("foaf:Person" -> _)` means that triples with a subject of type `foaf:Person` and any predicate are relevant for this graph-specification. The `_` character is thus a wildcard that matches everything. + +If you are interested in a more limited set of triples, you can explicitly specify one or more predicates instead of the the wildcard. For example, if you only want triples for `foaf:Person` that have as predicate `foaf:firstName` or `foaf:familyName` that can be written as shown below. Note, that the operator `->` is repeated for each individual predicate. + +```lisp +(in-package :acl) +(define-graph people ("http://mu.semte.ch/graphs/people") + ("foaf:Person" -> "foaf:firstName" + -> "foaf:familyName") + ("foaf:OnlineAccount" -> _)) ``` -**NOTE**: Any prefixes such as `foaf` and `ext` need to be defined, see [Defining prefixes](#defining-prefixes) -The `define-graph` macro takes: -- A unique identifier (*here `organization`*) -- The URI of the graph where the triples are stored (*here `http://mu.semte.ch/graphs/organizations/`*) -- One or more triple shapes (*here `("foaf:Person" -> _)` and `("foaf:OnlineAccount" x> "ext:password")`*). +Alternatively, you may be interested in most triples for a resource type except those with a few specific predicates. While you can list all relevant predicates as above, sparql-parser provides another operator `x>` to describe such situations his more concisely. For example, if you are interested in all triples with a `foaf:OnlineAccount` resource as subject except those that have as predicate `ext:password` or `account:accountName`. This can be written as follows: -The triple shapes have this form: `( )`. `` and `` must be a URI string (e.g. `"foaf:Person"`) or a `_` (indicating a wildcard). Triples that match these shapes will go to (or retrieved from) the specified graph (in the above example this is `http://mu.semte.ch/graphs/organizations/`). -**Note**: different *graph specs* can specify the same graph URI. +```lisp +(in-package :acl) +(define-graph people ("http://mu.semte.ch/graphs/people") + ("foaf:Person" -> "foaf:firstName" + -> "foaf:familyName") + ("foaf:OnlineAccount" x> "ext:password" + x> "account:accountName")) +``` + +So far the type-specifications only concerned triples with a *subject* of a specific resource type. To specify triples where the *object* is of a given resource type you can use the inverse operators `<-` and ` "foaf:firstName" + -> "foaf:familyName" + <- "schema:employee") + ("foaf:OnlineAccount" x> "ext:password" + x> "account:accountName")) +``` -These are all the possible operators: -- `T -> p`: Triples where the subject is of type `T` and the predicate is `p`. -- `T <- p`: Triples where the object is of type `T` and the predicate is `p`. -- `T x> p`: For triples where the subject is of type `T`, allow every predicate except for `p`. -- `T Date: Tue, 3 Feb 2026 10:15:54 +0100 Subject: [PATCH 12/23] doc(guide): grant rights to groups --- README.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4707462..8ef3c17 100644 --- a/README.md +++ b/README.md @@ -149,20 +149,33 @@ In summary this graph-specification contains all triples in `http://mu.semte.ch/ - has as *object* a resource of type `foaf:Person` AND as *predicate* `schema:employee`; OR - as *subject* a resource of type `foaf:OnlineAccount` AND **not** as *predicate* `ext:password` or `account:accountName`. -### Specifying which user groups have access to which graphs -Finally we need to specify which *user groups* are allowed to access which *graph spec*. This is done using the `grant` macro. +### Granting a group rights to a graph +Once you have defined the necessary [access-groups](#define-a-group-for-users-with-a-certain-role) and [graph-specifications](#define-which-triples-are-accessible-for-a-graph) you can grant rights using the `grant` macro. This macro expects as input a list of granted rights, the target graph-specification(s), and access-group(s). For example, the following snippets grants users that are members of the `authenticated` group read rights to the triples in the `people` graph-specification. + ```lisp +(in-package :acl) +(grant (read) + :to-graph people + :for-allowed-group "authenticated") +``` + +To grant multiple rights you can simply list them in the first argument. For instance, the following snippet grants users in the `super-mega-admin` group read and write rights to triples in the `people` graph-specification. Note, that `read` and `write` are currently the only supported rights. + +```lisp +(in-package :acl) (grant (read write) - :to-graph (organization) + :to-graph people :for-allowed-group "super-mega-admins") ``` -This indicates that we allow the users in the `super-mega-admins` group to read and write tripes from and to the `http://mu.semte.ch/graphs/organizations/` graph, according to the triple restrictions in the `organization` *graph spec*. -The only allowed operation values are `read` and `write`. +It is supported to provide multiple target graph-specifications and/or access-groups by surrounding the corresponding argument with brackets and listing multiple values. For example, users in the `super-mega-admins` access-group can be granted rights to the graph-specifications `people` and `organization` as follows: -`:to-graph` allows specifying multiple *graph specs*. - -`:for-allowed-group` specifies which user group is allowed to execute the specified operations. +```lisp +(in-package :acl) +(grant (read write) + :to-graph (people organization) + :for-allowed-group "super-mega-admins") +``` ### Defining prefixes In order to use the CURIE (Compact URI) form (e.g. `foaf:name`) we need to define the prefixes first. This is done as follows: From 1f4e45bb54b626054f7c3f4002752427106dee3e Mon Sep 17 00:00:00 2001 From: onodrim Date: Wed, 4 Feb 2026 12:38:27 +0100 Subject: [PATCH 13/23] doc(guide): define prefixes for using CURIEs --- README.md | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8ef3c17..9868d66 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,39 @@ For users with a certain role, say `SuperMegaAdmin`, a similar query can be used While it is rather common to define group membership based on roles, sparql-parser is not limited to this and allows arbitrary queries to be specified. It depends on your application's data model which queries make sense. Note, in the above examples the actual matches returned by the queries are not used, [TODO: link to guide] shows how you can use these matches to simplify access control policies in some situations. +### Use compact URIs by defining prefixes +You will often need to write URIs for resources, predicates, etc. while specifying access control policies for sparql-parser. As it is cumbersome to always write full URIs, and this also negatively impacts the readability of a configuration, sparql-parser supports using Compact URIs or [CURIEs](https://www.w3.org/TR/curie/). For this you need to define the prefixes you want to use along with their corresponding expansions. + +Prefixes are defined using the `define-prefixes` macro whose body is a sequence of keyword/value pairs of the form `:PREFIX "EXPANSION"`. For example, to be able to write `foaf:name` instead of `http://xmlns.com/foaf/0.1/name` you can define the `foaf:` prefix as follows: + +```lisp +(in-package :acl) +(define-prefixes + :foaf "http://xmlns.com/foaf/0.1/") +``` + +Note that the keyword `:foaf` does **not** contain a trailing colon ':' as would be the case in other languages such as [SPARQL](https://www.w3.org/TR/sparql11-query/#prefNames) or [TTL](https://www.w3.org/TR/turtle/#sec-iri). The colon preceding a keyword is required for it to be considered a keyword in the underlying data structure in which the pair is inserted. + +Be sure to define prefixes **before** their first use in some other part of your configuration. Otherwise, you will encounter errors when starting sparql-parser. + +You can define multiple prefixes in one go by simply putting multiple keyword/value pairs in the body of `define-prefixes`: + +```lisp +(in-package :acl) +(define-prefixes + :foaf "http://xmlns.com/foaf/0.1/" + :adms "http://www.w3.org/ns/adms#" + :cal "http://www.w3.org/2002/12/cal/ical#" + :cogs "http://vocab.deri.ie/cogs#" + :dcat "http://www.w3.org/ns/dcat#" + :ext "http://mu.semte.ch/vocabularies/ext/" + :eli "http://data.europa.eu/eli/ontology#") +``` + +**NOTE**: Be aware that the defined prefixes do **not** affect prefixes that can be used in SPARQL query strings such as those defined in the [previous section](#define-a-group-for-users-with-a-certain-role). In such queries prefixes still need to be specified using the `PREFIX` keyword. + ### Define which triples are accessible for a graph -Typically you want to explicitly specify which (kind of) triples within a graph an access control rule can be applied to. In sparql-parser such information is captured by *graph-specifications* which you create using the `define-graph` macro. For readability the code snippets use CURIEs as explained in the guide on TODO: link to prefix guide +Typically you want to explicitly specify which (kind of) triples within a graph an access control rule can be applied to. In sparql-parser such information is captured by *graph-specifications* which you create using the `define-graph` macro. For readability the code snippets use CURIEs as explained in the guide on [defining prefixes](#use-compact-uris-by-defining-prefixes). For instance, say you have a graph `http://mu.semte.ch/graphs/people` containing triples for resources of types `foaf:Person` and `foaf:OnlineAccount`. The following snippet creates a graph-specification for that graph which covers all triples for resources of these two types. Here `people` is a unique identifier by which this graph-specification can be referred to later on. The URI of the target graph, `http://mu.semte.ch/graphs/people`, is specified as a string between brackets and double quotes. @@ -177,19 +208,6 @@ It is supported to provide multiple target graph-specifications and/or access-gr :for-allowed-group "super-mega-admins") ``` -### Defining prefixes -In order to use the CURIE (Compact URI) form (e.g. `foaf:name`) we need to define the prefixes first. This is done as follows: -```lisp -(define-prefixes - :adms "http://www.w3.org/ns/adms#" - :cal "http://www.w3.org/2002/12/cal/ical#" - :cogs "http://vocab.deri.ie/cogs#" - :dcat "http://www.w3.org/ns/dcat#" - :ext "http://mu.semte.ch/vocabularies/ext/" - :eli "http://data.europa.eu/eli/ontology#") -``` -**NOTE**: This does not affect prefixes that can be used in sparql query strings used in this config. They still need to be specified using the `PREFIX` keyword. - ## Reference ### ACL configuration interface #### `define-graph` From 11d18ef60a3e217b47b623e87261c9f511458504 Mon Sep 17 00:00:00 2001 From: onodrim Date: Fri, 30 Jan 2026 17:27:17 +0100 Subject: [PATCH 14/23] doc(guide): define access rights for a set of graphs --- README.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9868d66..9657744 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ For users with a certain role, say `SuperMegaAdmin`, a similar query can be used }") ``` -While it is rather common to define group membership based on roles, sparql-parser is not limited to this and allows arbitrary queries to be specified. It depends on your application's data model which queries make sense. Note, in the above examples the actual matches returned by the queries are not used, [TODO: link to guide] shows how you can use these matches to simplify access control policies in some situations. +While it is rather common to define group membership based on roles, sparql-parser is not limited to this and allows arbitrary queries to be specified. It depends on your application's data model which queries make sense. Note, in the above examples the actual matches returned by the queries are not used, [another guide](#define-access-rights-for-a-set-of-similar-graphs) shows how you can use these matches to simplify access control policies in some situations. ### Use compact URIs by defining prefixes You will often need to write URIs for resources, predicates, etc. while specifying access control policies for sparql-parser. As it is cumbersome to always write full URIs, and this also negatively impacts the readability of a configuration, sparql-parser supports using Compact URIs or [CURIEs](https://www.w3.org/TR/curie/). For this you need to define the prefixes you want to use along with their corresponding expansions. @@ -208,6 +208,71 @@ It is supported to provide multiple target graph-specifications and/or access-gr :for-allowed-group "super-mega-admins") ``` +### Define access rights for a set of similar graphs +Your application may have multiple graphs whose contents are structurally similar in that they overlap in terms of resource types and predicates. For example, your application might have a single graph per organization where each graph contains similar triples such as the organization's name, address and employees. + +For such situations sparql-parser supports defining the access rights only once for all graphs together, instead of having to define them individually for each graph and group separately. First, this requires specifying a `:parameters` argument for the relevant access-groups as shown in the snippet below. This argument expects list of strings that is a subset of the variables specified in the `SELECT` clause of the corresponding query. + +```lisp +(in-package :acl) +(supply-allowed-group "organization-member" + :parameters ("session_group") + :query "PREFIX ext: + PREFIX mu: + SELECT DISTINCT ?session_group ?session_role WHERE { + ext:sessionGroup/mu:uuid ?session_group. + }") +``` + +Let's assume that the graphs for the different organizations have URIs for the form `http://mu.semte.ch/graphs/organizations/UUID`, where UUID identifies the specific organization this graph pertains to. The following graph-specification would cover all such graphs. Note that the provided graph URI argument does **not** contain the UUID part. + +```lisp +(in-package :acl) +(define-graph organization ("http://mu.semte.ch/graphs/organizations/") + ("org:Organization" -> _) + ("foaf:Person" -> "foaf:firstName" + -> "foaf:familyName")) +``` + +To grant read access to members of the `organization-member` group to the `organization` graph-specification the following `grant` can be defined. + +```lisp +(in-package :acl) +(grant (read) + :to-graph organization + :for-allowed-group "organization-member") +``` + +The "magic" here happens when sparql-parser processes an appropriate request, i.e. a request from a member of `organization-member` group for triples in the `organization` graph-specification. In such cases sparql-parser determines the target graphs by appending the match(es) for the `session_group` parameter to the graph URI in the `organization` graph-specification. For example, say the query in `organization-member` returns two matches for `session_group`: `someOrganization` and `aCompletelyDifferentOrganization`. The incoming request will then be forward to two graphs with as URIs: + +- `http://mu.semte.ch/graphs/organizations/someOrganization` +- `http://mu.semte.ch/graphs/organizations/aCompletelyDifferentOrganization` + +If you specify multiple values in a group's `:parameters` argument, their matches will be appended to graph URIs in the given order. For example, you can add `session_role` as a second `:parameters` argument to the above `organization-member` access-group as follows: + +```lisp +(in-package :acl) +(supply-allowed-group "organization-member" + :parameters ("session_group" "session_role") + :query "PREFIX ext: + PREFIX mu: + SELECT DISTINCT ?session_group ?session_role WHERE { + ext:sessionGroup/mu:uuid ?session_group. + }") +``` + +Let's say that the group's query returns the following matches for your application: + +| session_group | session_role | +|----------------------------------|---------------------------| +| someOrganization | someRole | +| aCompletelyDifferentOrganization | aCompletetlyDifferentRole | + +In this case sparql-sparser will use the following graph URIs to forward requests for the `organization` graph-specification: + +- `http://mu.semte.ch/graphs/organizations/someOrganization/someRole` +- `http://mu.semte.ch/graphs/organizations/aCompletelyDifferentOrganization/aCompletetlyDifferentRole` + ## Reference ### ACL configuration interface #### `define-graph` From e55b54f0c000f3a9ee41835cb5b0628ce71981ac Mon Sep 17 00:00:00 2001 From: onodrim Date: Wed, 4 Feb 2026 12:36:01 +0100 Subject: [PATCH 15/23] doc(guide): generate delta messages on changes --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 9657744..6321920 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,33 @@ In this case sparql-sparser will use the following graph URIs to forward request - `http://mu.semte.ch/graphs/organizations/someOrganization/someRole` - `http://mu.semte.ch/graphs/organizations/aCompletelyDifferentOrganization/aCompletetlyDifferentRole` +### Generating delta messages for data changes +Sparql-parser can be configured to generate delta messages when quads are inserted or deleted. Such delta messages can be further distributed to interested parties using a [delta-notifier](https://github.com/mu-semtech/delta-notifier) service. To enable generating delta messages add the following to your configuration for sparql-parser. + +```lisp +(in-package :delta-messenger) +(add-delta-messenger "http://delta-notifier/") +``` + +Here `delta-notifier` is the name of the delta-notifier service in your application as defined in `docker-compose.yml`. If this service is called differently in your case, modify the string accordingly. + +If you also want to log delta messages to the standard output add the following to your configuration as well well. + +```lisp +(in-package :delta-messenger) +(add-delta-logger) +``` + +The `define-graph` macro supports more fine-grained control for which graph-specifications delta messages should be generated. The `:delta` keyword parameter allows to disable delta messages for a specific graph-specification: + +```lisp +(define-graph organization ("http://mu.semte.ch/graphs/organizations/" :delta nil) + ("foaf:Person" -> _) + ("foaf:OnlineAccount" x> "ext:password")) +``` + +Any other value than `nil` will be interpreted as `t`, which is the default value, and will enable delta messages for the graph specification. + ## Reference ### ACL configuration interface #### `define-graph` From 33a2c4008528739f675015a3499ab33d0e5b651f Mon Sep 17 00:00:00 2001 From: onodrim Date: Tue, 27 Jan 2026 15:57:54 +0100 Subject: [PATCH 16/23] doc(guide): configure additional logging --- README.md | 17 +++++++++++++++++ connection/client.lisp | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6321920..b795742 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,23 @@ The `define-graph` macro supports more fine-grained control for which graph-spec Any other value than `nil` will be interpreted as `t`, which is the default value, and will enable delta messages for the graph specification. +### Enable additional logging +By default sparql-parser only logs to its standard output when requests fail to execute. To log more information different variables can be set to non-nil values. + +The following snippet enables logging the queries sent to the SPARQL endpoint as well as the responses received from it. + +```lisp +(in-package :client) +(setf *log-sparql-query-roundtrip* t) +``` + +If you want to log the requests and associated access rights that arrive at sparql-sparser add the following snippet to your configurations: + +```lisp +(in-package :server) +(setf *log-incoming-requests-p* t) +``` + ## Reference ### ACL configuration interface #### `define-graph` diff --git a/connection/client.lisp b/connection/client.lisp index c454938..a298fda 100644 --- a/connection/client.lisp +++ b/connection/client.lisp @@ -13,7 +13,8 @@ (defparameter *max-concurrent-connections* 8 "The maximum amount of concurrent queries sent to a sparql endpoint.") -(defparameter *log-sparql-query-roundtrip* nil) +(defparameter *log-sparql-query-roundtrip* nil + "When set to non-nil, we log both the outgoing query sent to and response received from the sparql endpoint.") (defparameter *aquire-db-semaphore-timeout* 55 "Amount of time (in seconds) to wait to aquire the semaphore (default is now 55). From fd93e0690a049e0e7473f7470e27845d1010098b Mon Sep 17 00:00:00 2001 From: onodrim Date: Fri, 30 Jan 2026 17:57:56 +0100 Subject: [PATCH 17/23] doc(guide): assume types for resources based on URI --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index b795742..4ab81fc 100644 --- a/README.md +++ b/README.md @@ -317,6 +317,26 @@ If you want to log the requests and associated access rights that arrive at spar (setf *log-incoming-requests-p* t) ``` +### Handle resources without an explicit type +Possibly your application's data contains resources that do not have an explicit resource type. Consequently, such resources cannot be directly used in type-specifications in the body of graph-specifications. To work around this sparql-parser supports assuming certain types for resources based on their URI. + +For example, say you have a graph `http://mu.semte.ch/graphs/sessions` which contains session resources whose URIs start with `http://mu.semte.ch/sessions/`. The following snippet essentially tells sparql-parser to assume such resources have as type `http://mu.semte.ch/vocabularies/session/Session`. + +```lisp +(in-package :type-cache) +(add-type-for-prefix "http://mu.semte.ch/sessions/" "http://mu.semte.ch/vocabularies/session/Session") +``` + +This allows using that type in graph-specifications the same as other resources: + +```lisp +(in-package :acl) +(define-graph sessions ("http://mu.semte.ch/graphs/sessions") + ("http://mu.semte.ch/vocabularies/session/Session" -> _)) +``` + +**NOTE**: this is not strictly limited to resources without a type. But can also be used to assume additional types for resources next to those types explicitly specified in the data. + ## Reference ### ACL configuration interface #### `define-graph` From db4a1e0c079fa4b5d6d0c15a4d38734a15939087 Mon Sep 17 00:00:00 2001 From: onodrim Date: Fri, 30 Jan 2026 18:06:23 +0100 Subject: [PATCH 18/23] doc(tutorial): add service to stack --- README.md | 49 +++++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 4ab81fc..0783704 100644 --- a/README.md +++ b/README.md @@ -7,61 +7,58 @@ A rewritten implementation of [`mu-authorization`](https://github.com/mu-semtech > We're working on writing a full configuration guide. ## Tutorials -### How to add the sparql-server to your application -Add the service to your `docker-compose.yml`: +### How to add the sparql-parser service to your application +Start by adding the service to your application's `docker-compose.yml`: + ```yaml services: database: - image: semtech/sparql-parser:0.0.8 + image: semtech/sparql-parser:0.0.15 volumes: - ./config/authorization:/config - ./data/authorization:/data ``` -Next, add the following contents to the config file mounted in `./config/authorization/config.lisp`. -```lisp -;;;;;;;;;;;;;;;;;;; -;;; delta messenger -(in-package :delta-messenger) +**NOTE:** If necessary, change the name of your triplestore service to something other than `database`. -(add-delta-logger) -(add-delta-messenger "http://delta-notifier/") +Next, create a configuration file `./config/authorization/config.lisp`. In this file you can configure the sparql-parser service by setting variables to appropriate values and defining the access control policy for your application. For example, the following snippet first configures a SPARQL endpoint service as the `*backend*` to which sparql-parser will talk. Note that `triplestore` here is the name of the endpoint service as set in your application's `docker-compose.yml`. Next it enables logging some extra information to the standard output by setting two variables to `t`. Finally, it enables the generation of delta messages for data changes caused by insert or delete queries. +```lisp ;;;;;;;;;;;;;;;;; ;;; configuration (in-package :client) -(setf *log-sparql-query-roundtrip* t) (setf *backend* "http://triplestore:8890/sparql") +(setf *log-sparql-query-roundtrip* t) (in-package :server) -(setf *log-incoming-requests-p* nil) - -;;;;;;;;;;;;;;;;; -;;; access rights -(in-package :acl) +(setf *log-incoming-requests-p* t) -(defparameter *access-specifications* nil - "All known ACCESS specifications.") +;;;;;;;;;;;;;;;;;;; +;;; delta messenger +(in-package :delta-messenger) -(defparameter *graphs* nil - "All known GRAPH-SPECIFICATION instances.") +(add-delta-logger) +(add-delta-messenger "http://delta-notifier/") +``` -(defparameter *rights* nil - "All known GRANT instances connecting ACCESS-SPECIFICATION to GRAPH.") +Now you can start your stack using `docker compose up -d`. At this point you will likely not see any data in your application as you have not yet configured any access rights. Adding the following snippet to the `config.lisp` configures read access for everyone for all data in the `http://mu.semte.ch/graphs/public` graph. -(type-cache::add-type-for-prefix "http://mu.semte.ch/sessions/" "http://mu.semte.ch/vocabularies/session/Session") +```lisp +;;;;;;;;;;;;;;;;; +;;; access rights +(in-package :acl) (define-graph public ("http://mu.semte.ch/graphs/public") (_ -> _)) (supply-allowed-group "public") -(grant (read write) - :to-graph (public) +(grant (read) + :to-graph public :for-allowed-group "public") ``` -It basically configures read/write access for everyone for all data on the `http://mu.semte.ch/graphs/public` graph. +To load the added the access policy, restart your service using `docker compose restart database`. After restarting, data should show up when in your application. Consult the how-to guides in the following section for more information in defining more meaningful access control policies. ## How-to guides ### Define a group for users with a certain role From d55109abb65ee22fd3440ca8043e9c981578076f Mon Sep 17 00:00:00 2001 From: onodrim Date: Mon, 2 Feb 2026 15:57:05 +0100 Subject: [PATCH 19/23] doc: remove warning --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 0783704..9d2bec7 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,6 @@ A rewritten implementation of [`mu-authorization`](https://github.com/mu-semtech/mu-authorization) in Common Lisp. -> [!WARNING] -> This README is currently incomplete and configuring this service requires diving into the code and comparing with other existing configurations. -> We're working on writing a full configuration guide. - ## Tutorials ### How to add the sparql-parser service to your application Start by adding the service to your application's `docker-compose.yml`: From a3a66d838645140c42badd943508218a65421e5a Mon Sep 17 00:00:00 2001 From: onodrim Date: Wed, 4 Feb 2026 11:29:40 +0100 Subject: [PATCH 20/23] chore: fix typos --- acl/prefix.lisp | 2 +- cache/types.lisp | 2 +- connection/client.lisp | 10 +++++----- packages.lisp | 2 +- reasoner/take-1/prefixes.lisp | 2 +- sparql-ast/terminals.lisp | 4 ++-- updates/quad-operations-to-quads.lisp | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/acl/prefix.lisp b/acl/prefix.lisp index 89e4187..e991085 100644 --- a/acl/prefix.lisp +++ b/acl/prefix.lisp @@ -14,7 +14,7 @@ (defmacro define-prefixes (&body body) "Defines a series of prefixes by reading the list as a plist. -The car is assumed to be a keyward and the cadr is assumed to be the expanded string." +The car is assumed to be a keyword and the cadr is assumed to be the expanded string." `(progn ,@(loop for (prefix expansion) on body by #'cddr collect `(define-prefix ,prefix ,expansion)))) diff --git a/cache/types.lisp b/cache/types.lisp index bb860f0..eef1f82 100644 --- a/cache/types.lisp +++ b/cache/types.lisp @@ -242,7 +242,7 @@ COMPLETE-P is to be understood as by DERIVE-TYPE-FROM-PREFIX-FUNCTION." else do (push (cons uri graph) missing-uri-graph-combinations))) - ;; now find which uri graph combitations are missing + ;; now find which uri graph combinations are missing ;; query all missing uri graph combinations (let* ((grouped-by-uri (support:group-by missing-uri-graph-combinations #'string= :key #'car)) (uri-graphs-combinations (loop for ((uri . graph) . rest) in grouped-by-uri diff --git a/connection/client.lisp b/connection/client.lisp index a298fda..de24eb7 100644 --- a/connection/client.lisp +++ b/connection/client.lisp @@ -16,10 +16,10 @@ (defparameter *log-sparql-query-roundtrip* nil "When set to non-nil, we log both the outgoing query sent to and response received from the sparql endpoint.") -(defparameter *aquire-db-semaphore-timeout* 55 - "Amount of time (in seconds) to wait to aquire the semaphore (default is now 55). +(defparameter *acquire-db-semaphore-timeout* 55 + "Amount of time (in seconds) to wait to acquire the semaphore (default is now 55). -NIL symolizes to wait forever.") +NIL symbolizes to wait forever.") ;; Overriding the default values here for now. Ideally these get calculated based on how much time we have left. (setf dexador.util:*default-connect-timeout* 60) @@ -145,7 +145,7 @@ When the VERBOSE keyword is truethy, output is written to STDOUT." (defun query (string &key (send-to-single nil)) "Sends a query to the backend and responds with the response body. -When SEND-TO-SINGLE is truethy and multple endpoints are available, the request is sent to only one of them." +When SEND-TO-SINGLE is truethy and multiple endpoints are available, the request is sent to only one of them." (ensure-backends-variable) (let* ((selected-endpoints (if send-to-single @@ -158,7 +158,7 @@ When SEND-TO-SINGLE is truethy and multple endpoints are available, the request ;; - a single failing endpoint will bring the whole setup down in this implementation ;; - the implementation does not fire off the queries in parallel, we may want a thread per semaphore for that ;; - a full-fledged and parallel implementation likely means rewriting this whole logic and the construction of the sparql-endpoint struct - (support:with-multiple-semaphores ((mapcar #'sparql-endpoint-semaphore selected-endpoints) :timeout *aquire-db-semaphore-timeout*) + (support:with-multiple-semaphores ((mapcar #'sparql-endpoint-semaphore selected-endpoints) :timeout *acquire-db-semaphore-timeout*) (let ((post-handler (lambda () nil))) ; overwritten with handler on error (unwind-protect (support:with-exponential-backoff-retry diff --git a/packages.lisp b/packages.lisp index caa6c0b..fa091e3 100644 --- a/packages.lisp +++ b/packages.lisp @@ -380,7 +380,7 @@ #:operation-data-subfield #:operation-type #:operation-data) - (:import-from #:sparql-parser + (:import-from #:sparql-parser #:make-match #:terminal-match-string) (:import-from #:sparql-manipulation diff --git a/reasoner/take-1/prefixes.lisp b/reasoner/take-1/prefixes.lisp index 283d942..2f8fdc9 100644 --- a/reasoner/take-1/prefixes.lisp +++ b/reasoner/take-1/prefixes.lisp @@ -3,7 +3,7 @@ ;;;; Prefixes extraction ;;;; ;;;; Many URIs are expressed using prefixes. This module extracts -;;;; perfixes from a query and provides a cache-based variant to use +;;;; prefixes from a query and provides a cache-based variant to use ;;;; them. ;;;; ;;;; This module is not thread-safe and assumes prefixes are constructed diff --git a/sparql-ast/terminals.lisp b/sparql-ast/terminals.lisp index 6e11464..d096ccb 100644 --- a/sparql-ast/terminals.lisp +++ b/sparql-ast/terminals.lisp @@ -8,7 +8,7 @@ ;;;; this library was optimized to understand whether a Lisp based ;;;; variant would be sufficiently fast in the interpretation of the ;;;; queries or whether it would be too slow. for this many parsers -;;;; have to approaches available: a regular expression and hand-rolled +;;;; have two approaches available: a regular expression and hand-rolled ;;;; interpretation. further optimizations seem possible but the ;;;; approach here has shown sufficient promise to verify the desired ;;;; performance. @@ -935,7 +935,7 @@ failure." (defmacro with-internal-runtime-processing ((var &optional (activep t)) (&rest operations) &body body) "Run code and do something with the time it took after processing. - The consumed time willb e stored in VAR. + The consumed time will be stored in VAR. The timed operations are BODY. The cleanup processing are OPERATIONS." (if activep diff --git a/updates/quad-operations-to-quads.lisp b/updates/quad-operations-to-quads.lisp index a72ec68..7eb0659 100644 --- a/updates/quad-operations-to-quads.lisp +++ b/updates/quad-operations-to-quads.lisp @@ -148,7 +148,7 @@ variables are missing this will not lead to a pattern." (ebnf::|ValuesClause|)))))))) (defun quads-for-construct-bindings (bindings) - "Ceates a series of quads fo the constructed bindings." + "Creates a series of quads fo the constructed bindings." (loop for binding in bindings for subject = (jsown:val binding "s") for predicate = (jsown:val binding "p") From cfc970eeeec38bd5d5ffb078f755ed706a638a12 Mon Sep 17 00:00:00 2001 From: onodrim Date: Wed, 4 Feb 2026 15:53:20 +0100 Subject: [PATCH 21/23] doc: add intro --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d2bec7..15da9ab 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # SPARQL Parser +The SPARQL endpoint authorization service (SEAS) is a layer that is placed in front of a SPARQL endpoint and that rewrites queries on this endpoint based on the session information of the user and the access rights on the data. -A rewritten implementation of [`mu-authorization`](https://github.com/mu-semtech/mu-authorization) in Common Lisp. +The idea is that data is organized into graphs and the access to these graphs is restricted to a certain group of users. When a query request is sent to the SPARQL endpoint it is intercepted by SEAS. The SEAS then calculates the appropriate access tokens based on the information, such as the session, in the intercepted request. It then iterates over the calculated tokens to determine the accessible graphs per token. Finally, the intercepted query is performed on the of accessible graphs. + +This is a rewritten implementation of [`mu-authorization`](https://github.com/mu-semtech/mu-authorization) in Common Lisp. ## Tutorials ### How to add the sparql-parser service to your application From bea2edc39ff9de2e7eb3457ffe6f65444ad0f709 Mon Sep 17 00:00:00 2001 From: onodrim Date: Thu, 5 Feb 2026 10:09:25 +0100 Subject: [PATCH 22/23] doc(guide,reference): usage of scopes for grants --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15da9ab..ea41fa2 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,69 @@ This allows using that type in graph-specifications the same as other resources: **NOTE**: this is not strictly limited to resources without a type. But can also be used to assume additional types for resources next to those types explicitly specified in the data. +### Define access rights for specific services +It is likely that in your semantic.works application not all requests sent to the SPARQL endpoint are (indirectly) triggered by users with a session. For example, a service may periodically and autonomously retrieve triples from the endpoint. In such cases, requests are not associated with a session from which the appropriate access-groups can be determined. Sparql-parser supports *scopes** which facilitate defining access control rules for such scenarios. + +**NOTE**: This requires the service to which rights are granted is created with [mu-javascript-template](https://github.com/mu-semtech/mu-javascript-template) v1.9.0 or newer. Services based on older templates should first be upgraded or can use [mu-auth-sudo](https://github.com/lblod/mu-auth-sudo) as alternative solution. + +For instance, let's assume your application has the following access control policy: + +```lisp +(in-package :acl) + +(supply-allowed-group "authenticated" + :query "PREFIX session: + + SELECT DISTINCT ?account WHERE { + session:account ?account. + }") + +(define-graph people ("http://mu.semte.ch/graphs/people") + ("foaf:Person" -> _) + ("foaf:OnlineAccount" -> _)) + +(grant (read write) + :to people + :for "authenticated") +``` + +Now say you have a service `peopleservice` in your application which requires periodically retrieve the names of the `foaf:Person`s in the `people` graph. In your `docker-compose.yml` entry for this service, specify a value for the `DEFAULT_MU_AUTH_SCOPE` environment variable. The `peopleservice` will supply this value in the header of each outgoing request. + +```yaml +services: + peopleservice: + image: example/peopleservice:0.0.1 + environment: + DEFAULT_MU_AUTH_SCOPE: "http://services.semantic.works/people-service" +``` + +In your sparql-parser configuration you can use the `with-scope` macro to grant rights within a scope. For instance, the following snippet essentially states that the grant is also applicable for requests with the scope `"http://services.semantic.works/people-service"`. + +```lisp +(with-scope "http://services.semantic.works/people-service" + (grant (read write) + :to people + :for "authenticated")) +``` + +As an alternative notation you can use the `:scopes` keyword parameter for the `grant` macro as shown below. Note, that the argument value is surrounded by brackets and preceded by a quote `'`. + +```lisp +(grant (read write) + :to people + :for "authenticated" + :scopes '("http://services.semantic.works/example-service")) +``` + +Using the `:scopes` parameter notation it is possible to provide multiple scope URIs: + +```lisp +(grant (read write) + :to people + :for "authenticated" + :scopes '("http://services.semantic.works/people-service" "http://services.semantic.works/another-service")) +``` + ## Reference ### ACL configuration interface #### `define-graph` @@ -395,7 +458,7 @@ Keyword parameters: - *`:for-allowed-group`* The names of one or more previously defined groups, each name quoted as a string. If multiple names are provided they must be surrounded by brackets: `("someGroup" "anotherGroup")`. - *`:to`* Alias for `:to-graph`. - *`:for`* Alias for `:for-allowed-group`. -- *`:scopes`* TODO +- *`:scopes`* A list of URIs identifying the scopes in which this grant can be used. (default: `'(_)`) **NOTE**: if values for both keyword parameters `:to-graph` and `:to` are provided these values are merged into a single list. Same for `:for-allowed-group` and `:for`. From 74c62824a78bff16644b3a66e9f9b6de05c4cc3c Mon Sep 17 00:00:00 2001 From: onodrim Date: Fri, 6 Feb 2026 11:45:57 +0100 Subject: [PATCH 23/23] doc(reference): add environment variables section --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ea41fa2..1827a9d 100644 --- a/README.md +++ b/README.md @@ -489,6 +489,9 @@ The following concrete example snippet defines three prefixes. :dcat "http://www.w3.org/ns/dcat#") ``` +### Environment variables +- *`LISP_DYNAMIC_SPACE_SIZE`* Set the size (in megabytes) of the dynamic space reserved on startup by [sbcl](https://www.sbcl.org/manual/#Runtime-Options-1). (Default: `4096`) + ### Configurable variables The following sections list, per package, the available variables that can be (indirectly) configured.