diff options
Diffstat (limited to 'doc/disfluid.texi')
-rw-r--r-- | doc/disfluid.texi | 1428 |
1 files changed, 1428 insertions, 0 deletions
diff --git a/doc/disfluid.texi b/doc/disfluid.texi new file mode 100644 index 0000000..bebc61b --- /dev/null +++ b/doc/disfluid.texi @@ -0,0 +1,1428 @@ +\input texinfo @c -*-texinfo-*- +@comment $Id@w{$} +@documentlanguage en +@comment %**start of header +@include version.texi +@settitle Demanding Interoperability to Strengthen the Free (Libre) Web: Introducing Disfluid +@syncodeindex pg cp +@syncodeindex fn cp +@syncodeindex vr cp +@syncodeindex tp cp +@comment %**end of header + +@copying +This is the manual of disfluid (version @value{VERSION}, @value{UPDATED}), an implementation of the Solid authentication protocol for guile, client and server. + +Copyright @copyright{} 2020, 2021 Vivien Kraus +@quotation +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.3 or +any later version published by the Free Software Foundation; with no +Invariant Sections, with no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license is included in the section entitled ``GNU +Free Documentation License'' +@end quotation +@end copying + +@dircategory Software libraries + +@direntry +* disfluid: (disfluid)Interoperability on the web +@end direntry + +@titlepage +@title Demanding Interoperability to Strengthen the Free (Libre) Web: Introducing Disfluid +@subtitle for version @value{VERSION}, @value{UPDATED} +@author Vivien Kraus (@email{vivien@@planete-kraus.eu}) +@page +@vskip 0pt plus 1fill +@insertcopying +@end titlepage + +@contents +@ifnottex +@node Top +@top Disfluid +@end ifnottex + +@menu +* Decentralized Authentication on the Web:: +* Invoking disfluid:: +* The Json Web Token:: +* Caching on server side:: +* Content negociation:: +* Running an Identity Provider:: +* Running a Resource Server:: +* Running a client:: +* Exceptional conditions:: +* GNU Free Documentation License:: +* Index:: +@end menu + +@node Decentralized Authentication on the Web +@chapter Decentralized Authentication on the Web + +Authentication on the web is currently handled in the following way: +anyone can install a server that will authenticate users on the +web. The problem is interoperability. If a client (an application) +wants to authenticate a user, it has to be approved by the +authentication server. In other words, if @var{useful-program} wants +to authenticate @var{MegaCorp} users, then @var{useful-program} has to +register to @var{MegaCorp} first, and get approved. This goes against +the principle of permission-less innovation, which is at the heart of +the web. + +In the decentralized authentication web, the best attempt so far is +that of ActivityPub. All servers are interoperable with respect to +authentication: if user A emits an activity, it is forwarded by A's +server to its recipients, and A's server is responsible for A's +identity. + +The problem with that approach is that the data is tied to the +application. It is not possible to use another application to process +the data differently, or to use multiple data sources, in an +interoperable way (without the ActivityPub server knowing). This means +that on Activitypub, microblogging applications will not present +different activities correctly. This also means that it is difficult +to write a free replacement to a non-free application program, because +it would need to manage the data. + +In the Solid ecosystem, there is a clear distinction between servers +and applications. An application is free to read data from all places +at the same time, using a permission-less authentication system. Since +the applications do not need to store data, the cost of having users +is neglectible, so users do not need prior approval before using them +(making captchas and the like a thing of the past). Servers do not +have a say in which applications the user uses. + +The authentication used is a slight modification of the +well-established OpenID Connect. It is intended to work in a web +browser, but this package demonstrates that it also works without a +web browser. + +@node Invoking disfluid +@chapter Invoking disfluid + +The @samp{disfluid} program provides different modes of operations: + +@table @samp +@item reverse-proxy +Run an authenticating reverse proxy. With this command, you specify a +backend server. When an authenticated user makes a request, you +receive an additional header containing the user’s identity. +@item identity-provider +Run the identity provider only. +@item client-service +The client applications must serve some resources: namely, the client +manifest and the redirect URI. +@item server +Run both an identity provider and a resource server. +@end table + +The server is configured with command-line arguments, and environment +variables. + +@menu +* General options:: +* General server configuration:: +* Configuration for the resource server:: +* Configuration for the identity provider:: +* Configuration for the client service:: +@end menu + +@node General options +@section General options +The server will respond to @samp{-h} and @samp{-v} commands, to get +the help output and the version information. + +The server output (command-line, logs) are localized for the system +administrator. You can control it with the @samp{LANG} environment +variable. So if your locale is not English, you can have the same +commands as in this manual by running with @code{LANG=C}. + +The programs respect the @samp{XDG_DATA_HOME} and +@samp{XDG_CACHE_HOME} to store persistent data and disposable +data. The cache directory can be deleted at any time. If one of these +variables is not set, its value is computed from the @samp{HOME} +environment variable. + +@node General server configuration +@section General server configuration +All servers are published under the Affero GPL, which means that the +service provider needs to publish all changes made to the program to +users over the network. The @samp{disfluid} command provides a +@samp{--complete-corresponding-source} option so that the system +administrator can specify a means to download the source. + +The servers will add a @samp{Source:} header in each response, +containing the value of this configuration option. + +The servers can be configured to redirect output and errors to a log +file and an error file, with the @samp{--log-file} and +@samp{--error-file} options. + +The server will listen to port 8080 by default, but this may be +configured with @samp{--port}. Since the servers do not support TLS, +and they only support HTTP/1.1, they are intended to run behind a +reverse proxy (even for the authenticating reverse proxy). + +Finally, the servers are required to know their public name. This is +configured with the @samp{--server-name} option. + +The server will make requests on the world-wide web, for instance to +download client manifests. The requests can be redirected with XML +Catalog, by setting the @samp{XML_CATALOG_FILES} to a space-separated +list of URIs (can be @code{file:} URIs). The requests cannot be +directed to the file system. + +@node Configuration for the resource server +@section Configuration for the resource server +The reverse proxy sets an identity header to authenticated +requests. By default, it is @samp{XXX-Agent}, but it can be configured +with @samp{--header}. + +The reverse proxy is configured to contact a backend URI with +@samp{--backend-uri}. This backend URI should not be directly exposed, +because a malicious user could set the identity header. + +@node Configuration for the identity provider +@section Configuration for the identity provider +The identity provider can only handle one user. If you want to handle +multiple users, it is highly advised to use a different host name for +each user, in case the server is accessed from a web browser. You can +set the identity of the user with @samp{--subject}, and the user’s +password with @samp{--encrypted-password}. + +The encrypted password format is defined by the crypt function in the +C library. For glibc, it looks like this: +@code{$@var{N}$@var{salt}$@var{hash}}, where @var{N} is the algorithm +identifier, @var{salt} is the password salt annd @var{hash} is its +hash. + +The server uses a key, which is not the same thing as the TLS +certificate of the server (remember, the servers don’t support +TLS). It is in the JWK format. You set its file name with +@samp{--key-file}. If the key file does not exist, it will be +generated. + +Finally, the public openid configuration requires you to set the JWKS +URI (@samp{--jwks-uri}), authorization endpoint URI +(@samp{--authorization-endpoint-uri}) and token endpoint URI +(@samp{--token-endpoint-uri}). The identity provider will publish the +full URIs, but will respond to their path, regardless of the host. + +@node Configuration for the client service +@section Configuration for the client service +The client will serve a stupid page for the redirect URI that will +only display the authorization code. The redirect URI is set with +@samp{--redirect-uri}. + +The client ID is set with @samp{--client-id}. This is the URI under +which the client registrationn is served. + +Finally, you can set some cosmetic options, but since it can confuse +the user, they are hidden by default by the identity provider. + +@table @samp +@item --client-name +set the name of the application. +@item --client-uri +set an URI where to find more information about the client. +@end table + +@node The Json Web Token +@chapter The Json Web Token + +The Json Web Token, or @dfn{JWT}, is a terse representation of a pair +of JSON objects: the @dfn{header}, and the @dfn{payload}. The JWT can +be @dfn{encoded} as a Json Web Signature (@dfn{JWS}), in which case +the header is encoded to base64 with the URL alphabet, and without +padding characters, the payload is also encoded to base64, and the +concatenation of the encoding of the header, a dot, and the encoding +of the payload is signed with some cryptography algorithm. In the +following, we will only be interested by public-key cryptography. The +concatenation of header, dot, payload, dot and signature in base64 is +the encoding of the JWT. + +Decoded JWT are represented as a pair. The car of the pair is the +header, and the cdr is the payload. Both the header and the payload +use the JSON representation from srfi-180: objects are alists of +@strong{symbols} to values, arrays are vectors. It is unfortunate that +guile-json has a slightly different representation, where alist keys +are @emph{strings}, but we hope that in the future SRFI-180 will be +more closely respected. + +@menu +* The ID token:: +* The access token:: +* The DPoP proof:: +* Generic JWTs:: +@end menu + +@node The ID token +@section The ID token + +The ID token is a special JWT that the application keeps for +itself. It is signed by the identity provider, and contains the +following claims: + +@table @emph +@item webid +the URI of the user’s webid; +@item iss +the URI of the identity provider (issuer); +@item sub +the username (the webid-oidc issuer puts the webid again here, but it +could be any string); +@item aud +the ID of the client application that is intended to receive the ID +token; +@item nonce +some random data to change the signature; +@item exp +an UTC time (in seconds) for when the token expires; +@item iat +the time when it was issued. +@end table + +There are functions to work with ID tokens in +@emph{(webid-oidc oidc-id-token)}. + +@deffn function id-token? @var{object} +Check that @var{object} is a decoded ID token. +@end deffn + +The following helper functions convert URIs to the URIs from +@emph{(web uri)} and times to @emph{(srfi srfi-19)} dates. + +@deffn function id-token-webid @var{token} +@deffnx function id-token-iss @var{token} +@deffnx function id-token-sub @var{token} +@deffnx function id-token-aud @var{token} +@deffnx function id-token-nonce @var{token} +@deffnx function id-token-exp @var{token} +@deffnx function id-token-iat @var{token} +Get the suitable field from the payload of @var{token}. +@end deffn + +ID tokens can be signed and encoded as a string, or decoded. + +@deffn function id-token-decode @var{token} @var{[#http-get]} +Decode @var{token}, as a string, into a decoded token. The signature +verification will need to fetch the oidc configuration of the claimed +issuer, and check the signature against the published keys. The +@code{http-get} optional keyword argument can set a different +implementation of @code{http-get} from @emph{(web client)}. Return +@code{#f} if it failed, or the decoded token otherwise. +@end deffn + +@deffn function id-token-encode @var{token} @var{key} +Encode @var{token} and sign it with the issuer’s @var{key}. +@end deffn + +@deffn function issue-id-token @var{issuer-key} @var{#alg} @var{#webid} @var{#iss} @var{#sub} @var{#aud} @var{#exp} @var{#iat} +Create an ID token, and encode it with @var{issuer-key}. +@end deffn + +@node The access token +@section The access token + +The access token is obtained by the client through a token request, +and is presented to the server on each authenticated request. It is +signed by the identity provider, and it contains enough information so +that the server knows who the user is and who the agent is, and most +importantly the fingerprint of the key that the client should use in a +DPoP proof. + +The API is defined in @emph{(webid-oidc access-token)}. + +@deffn function access-token? @var{object} +Check that @var{object} is a decoded access token. +@end deffn + +There are field getters for the access token: + +@deffn function access-token-webid @var{token} +@deffnx function access-token-iss @var{token} +@deffnx function access-token-aud @var{token} +@deffnx function access-token-exp @var{token} +@deffnx function access-token-iat @var{token} +@deffnx function access-token-cnf/jkt @var{token} +@deffnx function access-token-client-id @var{token} +Get the suitable field from the payload of @var{token}. +@end deffn + +Access tokens can be signed and encoded as a string, or decoded. + +@deffn function access-token-decode @var{token} @var{[#http-get]} +Decode @var{token}, as a string, into a decoded token. As with the ID +token, the signature verification will need to fetch the oidc +configuration of the claimed issuer, and check the signature against +the published keys. The @code{http-get} optional keyword argument can +set a different implementation of @code{http-get} from +@emph{(web client)}, for instance to re-use the what has been obtained +by the ID token validation. Return @code{#f} if it failed, or the +decoded token otherwise. +@end deffn + +@deffn function access-token-encode @var{token} @var{key} +Encode @var{token} and sign it with the issuer’s @var{key}. +@end deffn + +@deffn function issue-access-token @var{issuer-key} @var{#alg} @var{#webid} @var{#iss} @var{#exp} @var{#iat} @var{[#client-key} @var{|} @var{#cnf/jkt]} @var{#client-id} +Create an access token, and encode it with @var{issuer-key}. You can +either set the @code{#:cnf/jkt} keyword argument with the fingerprint +of the client key, or set @code{#:client-key} directly, in which case +the fingerprint will be computed for you. +@end deffn + +@node The DPoP proof +@section The DPoP proof + +This is a special JWT, that is signed by a key controlled by the +application. The access token certifies that the key used to sign the +proof is approved by the identity provider. + +@deffn function dpop-proof? @var{proof} +Check that the @var{proof} is a decoded DPoP proof. The validity of +the proof is not checked by this function. +@end deffn + +@deffn function dpop-proof-alg @var{proof} +@deffnx function dpop-proof-jwk @var{proof} +@deffnx function dpop-proof-jti @var{proof} +@deffnx function dpop-proof-htm @var{proof} +@deffnx function dpop-proof-htu @var{proof} +@deffnx function dpop-proof-iat @var{proof} +@deffnx function dpop-proof-ath @var{proof} +Get the corresponding field of the proof. +@end deffn + +@deffn function dpop-proof-decode @var{current-time} @var{jti-list} @var{method} @var{uri} @var{str} @var{cnf/check} @var{[#:access-token]} +Check and decode a DPoP proof encoded as @var{str}. + +The @var{current-time} is passed as a date, time or number (of +seconds). + +In order to prevent replay attacks, each proof has a unique random +string that is remembered in @var{jti-list} until its expiration date +is reached. See the @code{make-jti-list} function. + +The proof is limited to the scope of one @var{uri} and one +@var{method} (@code{'GET}, @code{'POST} and so on). + +The key that is used to sign the proof should be confirmed by the +identity provider. To this end, the @var{cnf/check} function is called +with the fingerprint of the key. The function should check that the +fingerprint is OK (return a boolean). + +Finally, when the DPoP proof is tied to an access token (so, for all +uses except requesting an access token or a refresh token), it must be +bound to an @var{access-token}. +@end deffn + +@deffn function make-jti-list +This function in @emph{(webid-oidc jti-list)} creates an in-memory, +async-safe, thread-safe cache for the proof IDs. +@end deffn + +@deffn function dpop-proof-encode @var{proof} @var{key} +Encode the proof and sign it with @var{key}. To generate valid proofs, +@var{key} should be the private key corresponding to the @code{jwk} +field of the proof. +@end deffn + +@deffn function issue-dpop-proof @var{client-key} @var{#alg} @var{#htm} @var{#htu} @var{#iat} {[#:@var{access-token}=#f]} +Create a proof, sign it and encode it with +@var{client-key}. @var{client-key} should contain both the private and +public key, because the public part is written in the proof and the +private part is used to sign it. For most uses, the DPoP proof should +be encoded for a specific access token. Only token requests should +omit the @samp{access-token} field. +@end deffn + +@node Generic JWTs +@section Generic JWTs + +You can parse generic JWTs signed with JWS with the following +functions from @emph{(webid-oidc jws)}. + +@deffn function jws? @var{jwt} +Check that @var{jwt} is a decoded JWT signed with JWS. +@end deffn + +@deffn function jws-alg @var{jwt} +Get the algorithm used to sign @var{jwt}. +@end deffn + +@deffn function jws-decode @var{str} @var{lookup-keys} +Check and decode a JWT signed with JWS and encoded as @var{str}. + +Since the decoding and signature verification happen at the same time +(for user friendliness), the @var{lookup-keys} function is used. It is +passed as arguments the decoded JWT (but the signature is not checked +yet), and it should return a public key, a public key set or a list of +public keys. If the key lookup failed, this function should raise an +exception. +@end deffn + +@deffn function jws-encode @var{jwt} @var{key} +Encode the JWT and sign it with @var{key}. +@end deffn + +@node Caching on server side +@chapter Caching on server side + +Both the identity provider and the resource server need to cache +things. The identity provider will cache application webids, and the +resource server will cache the identity provider keys, for instance. + +The solution is to use a file-system cache. Every response (except +those that have a cache-control policy of no-store) are stored to a +sub-directory of @emph{XDG_CACHE_HOME}. Each store has a 5% chance of +triggering a cleanup of the cache. When a cleanup occurs, each cached +response has a 5% chance of being dropped, including responses that +are indicated as valid. This way, a malicious cache response that has +a maliciously long validity will not stay too long in the cache. A log +line will indicate which items are dropped. + +The @emph{(webid-oidc cache)} module exports two functions to deal +with the cache. + +@deffn function clean-cache @var{[#percents]} @var{[#dir]} +Drop @var{percents}% of the cache right now, in @var{dir} (defaults to +some place within @emph{XDG_CACHE_HOME}). +@end deffn + +@deffn function with-cache @var{[#current-time]} @var{[#http-get]} @var{[#dir]} +Return a function acting as @emph{http-get} from @emph{(web client)} +(takes an URI as the first parameter, and an optional @var{#:headers} +set, and returns 2 values, the response and its body). + +The cache will be read and written in @var{dir} (defaults to some +place within @emph{XDG_CACHE_HOME}), and the @var{current-time} number +of seconds, SRFI-19 time or date, or time-returning thunk will be used +to check for the validity of responses. + +The back-end function, @var{http-get}, defaults to that of +@emph{(web client)}. +@end deffn + +@node Content negociation +@chapter Content negociation +There are a number of different available syntaxes for RDF, some being +simple and human readable like @emph{turtle}, and others more adapted +to the JavaScript ecosystem like @emph{json-ld}. To help clients both +from and outside of the JS ecosystem, the server needs to perform +@dfn{content negociation}, i.e. convert from one content-type to +another. + +@deffn {function from @code{(webid-oidc serve)}} convert @var{client-accepts} @var{server-name} @var{path} @var{content-type} @var{content} +Convert the resource representation under @var{path} on +@var{server-name}, which has a given @var{content-type} and +@var{content}, to a content-type that the @var{client accepts}. + +Return 2 values: +@enumerate +@item +the accepted content-type; +@item +the content in the given content-type. +@end enumerate + +Currently, the only conversions are from and to @emph{Turtle} and +@emph{N-Quads}. +@end deffn + +@node Running an Identity Provider +@chapter Running an Identity Provider + +This project is packaged with a barebones identity provider. It has an +authorization endpoint and a token endpoint (and it serves its public +keys), but it is only intended for one specific person. + +You can start it by invoking the @code{webid-oidc} program with the +@code{issuer} command, with the following options: + +@table @asis +@item @code{-h}, or @code{--help} +prints a summary of options and exit. +@item @code{-v}, or @code{--version} +prints the version of the program and exits. +@item @code{-n @var{URI}}, or @code{--server-name=@var{URI}} +sets the global server name of the identity provider. It should have +an empty path. +@item @code{-k @var{FILE.jwk}}, or @code{--key-file=@var{FILE.jwk}} +sets the file name where to read or generate a key for the identity +provider. This file should be JSON, containing the representation of a +JWK key pair. +@item @code{-s @var{WEBID}}, or @code{--subject=@var{WEBID}} +sets the webid of the only user of the identity provider. This is an +URI, pointing to a RDF node corresponding to the user’s profile. +@item @code{-w @var{PASSWORD}}, or @code{--password=@var{PASSWORD}} +sets the password that the user must enter to authorize an +application. +@item @code{-j @var{URI}}, or @code{--jwks-uri=@var{URI}} +tells the server that requests to @var{URI} should be responded with +the public key used to sign the tokens. +@item @code{-a @var{URI}}, or @code{--authorization-endpoint-uri=@var{URI}} +tells the server that requests to @var{URI} should be treated as +authorization requests. +@item @code{-t @var{URI}}, or @code{--token-endpoint-uri=@var{URI}} +tells the server that requests to @var{URI} should be treated as token +negociation requests. +@item @code{-p @var{PORT}}, or @code{--port=@var{PORT}} +change the port number used by the server. By default, it is set to +8080. +@item @code{-l @var{FILE.log}}, or @code{--log-file=@var{FILE.log}} +let the server dump all its output to @var{FILE.log}. Since I don’t +know how to deal with syslog, this is the only way to keep logs with a +shepherd service. +@item @code{-e @var{FILE.err}}, or @code{--error-file=@var{FILE.err}} +let the server dump all its errors to @var{FILE.err}. +@end table + +The program is sensitive to the environment variables. The most +important one is @emph{LANG}, which influences how the program is +internationalized to the server administrator (the pages served to the +user use the user agent’s locale). This changes the long form of the +options, and the language in the log files. + +The @emph{XDG_DATA_HOME} should point to some place where the program +will store refresh tokens, under the @code{webid-oidc} directory. For +a system service, you might want to define that environment to +@code{/var/lib}, for instance. + +The @emph{XDG_CACHE_HOME} should point to a directory where to store +the seed of the random number generator (under a @code{webid-oidc} +directory, again). Changing the seed only happens when a program +starts to require the random number generator. You can safely delete +this directory, but you need to restart the program to actually change +the seed. + +@node Running a Resource Server +@chapter Running a Resource Server + +@menu +* The authenticator:: +* The full server:: +* Resources stored on the server:: +@end menu + +A Solid server is the server that manages your data. It needs to check +that the proofs of possession are correct, and the possessed key is +signed by the identity provider. + +@node The authenticator +@section The authenticator + +In @emph{(webid-oidc resource-server)}, the following function gives a +simple API for a web server: + +@deffn function make-authenticator @var{jti-list} @var{[#server-uri]} @var{[#current-time]} @var{[#http-get]} +Create an authenticator, i.e. a function that takes a request and +request body and returns the webid of the authenticated user, or +@code{#f} if it is not authenticated. + +To prevent replay attacks, each request is signed by the client with a +different unique padding value. If such a value has already been seen, +then the request must fail. + +The authenticator expects the client to demonstrate the possession of +a key that the identity provider knows. So the client creates a DPoP +proof, targetted to a specific URI. In order to check that the URI is +correct, the authenticator needs the public URI of the service. + +The JTIs are checked within a small time frame. By default, the system +time will be used. Otherwise, you can customize the +@code{current-time} optional keyword argument, to pass a thunk +returning a time from @emph{(srfi srfi-19)}. + +You may want to customize the @var{http-get} optional keyword argument +to pass a function to replace @code{http-get} from @emph{(http +client)}. This function takes an URI and optional @code{#:headers} +arguments, makes the request, and return two values: the response, and +the response body. + +This function, in @emph{(webid-oidc resource-server)}, returns a web +request handler, taking the request and request body, and returning +the subject of the access token. If an error happens, it is thrown; +the function always returns a valid URI. +@end deffn + +@node The full server +@section The full server + +@deffn {function from @emph{(webid-oidc resource-server)}} make-server @var{[#:server-uri]} @var{[#:owner]} @var{[#:authenticator]} @var{[#:current-time]} @var{[#:http-get]} +Return a server handler, a function taking 2 values, a request and a +request body, and returning 2 values, the response and response body. + +The optional @var{[#:authenticator]} argument defaults to the +webid-oidc authenticator, @var{[#:current-time]} defaults to a thunk +returning the system time and @var{[#:http-get]} to the web client +from @emph{(web client)}. +@end deffn + +@node Resources stored on the server +@section Resources stored on the server + +To store and serve resources, the server has two distinct +mechanisms. A @dfn{content} is a read-only possible value for a +resource, indexed by etags, and a @dfn{path} is a mutable value that +indicates the etag of the resource, and of the auxiliary resources +(description and ACL). With this separation, it is possible to +atomically delete a resource and all associated auxiliary resources, +by unlinking the corresponding @emph{path}. It is also possible to +mutate separately the ACL and the resource itself without writing a +copy for both. + +The @emph{content} API is contained in the +@code{(webid-oidc server resource content)} module. + +@deffn function with-session @var{f} [@var{#:dir}] +Call @var{f} with 5 arguments: +@itemize +@item +a function to get the content-type of a given etag; +@item +a function to list the paths contained within the resource; +@item +a function to load the content of a given etag; +@item +a function to create a new content; +@item +a function to remove a content from the file system. It is still +possible to query it with the first 3 functions, but new sessions will +not see it. +@end itemize + +Since the contents are read-only, it is possible to cache the value of +the content in memory. This is why @var{f} should run within a session +with memoization. + +Resources only store @emph{static} content, because the membership +triples for containers is considered dynamic and not included in the +representation. + +The first 3 functions as well as the last one are called with an etag, +and the function to create a content is called with the content-type, +list of contained paths, and (static) content. + +By default, the contents are stored within @var{XDG_DATA_HOME}, but it +can be overriden by @var{#:dir}. +@end deffn + +The @emph{path} API is defined in +@code{(webid-oidc server resource path)}. + +@deffn function read-path @var{path} +Read the resource at @var{path}, and return 2 values: +@enumerate +@item +the ETag of the main resource; +@item +an alist where keys are auxiliary resource type URIs (the type is from +@code{(web uri)}), and the values are ETags of the corresponding +resource. +@end enumerate + +If the resource is not found, raise an exception with type +@code{&path-not-found}, and maybe @code{&uri-slash-semantics-error} if +a resource with a different ending-in-slash exists. + +This function is safe to call when the path is being modified, either +by another thread, process or else, as the returned values will always +be consistent. However, once the function returns, an updating process +may have deleted the returned ETags. If this is the case, then you +must call this function again to read the updated path. +@end deffn + +@deffn function update-path @var{path} @var{f} @var{content-type} @var{contained} @var{static-content} @var{create} @var{delete} [@var{#:create-intermediate-containers?}=@code{#f}] +Read @var{path}, call @var{f} with two values: the ETag and the +auxiliary ETags (as returned by @var{read-path}), and update the path +accordingly. If @var{path} does not exist, then the first argument is +@code{#f} and the second one is the empty list. + +If @var{f} returns @code{#f}, then the resource is deleted. + +If @var{f} returns an ETag as the first returned value and an alist of +auxiliary resource ETags as the second value, then the resource is +updated. + +The last functions are from the content API. Since creating or +deleting children requires updating the parent, we need them. + +Some operations should create the intermediate containers for a given +path, this is the case for the @code{PUT} HTTP verb. For @code{POST}, +the parent should exist. The @var{#:create-intermediate-containers?} +switch lets you change the behavior. In any case, it is an error to +delete a non-empty container. + +The update is atomic, meaning that at any point in time the file is +fully written out. Concurrent access to the same resource is performed +by locking the lock file named @var{X}/.lock, where @var{X} is the +first character of the base64-url sha-256 hash of the +path. @strong{The lock file is not meant to be removed} when the +resource is unlocked. It should be locked with @code{flock} +instead. @strong{Like other forms of lock-based synchronization, this +function is not composable}. This means that you cannot call this +function within @var{f}, otherwise a deadlock may ensue. + +If the resource is created or deleted, then the parent resource is +updated as well. To avoid deadlocks with other processes, please +follow the following rules: lock the path, then lock the parent path, +then update the parent, then unlock the parent, and finally unlock the +child path. +@end deffn + +The Web Access Control specification defines an RDF vocabulary to +check whether a given user is allowed to perform some operations. The +@code{(webid-oidc server resource wac)} helps you do that. + +@deffn function wac-get-modes @var{server-name} @var{path} @var{user} @var{[#:http-get]} +Return the list of modes that are allowed for @var{user} accessing +@var{path}. The @var{server-name} URI is required to find the relevant +triples in the ACL. If @var{user} is unauthenticated, pass @code{#f}. + +Please note that in any case, the data owner should have all rights +whatsoever, bypassing WAC. Otherwise, it is possible to steal control +away from the data owner. +@end deffn + +@deffn function check-acl-can-read @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} +@deffnx function check-acl-can-write @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} +@deffnx function check-acl-can-append @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} +@deffnx function check-acl-can-control @var{server-name} @var{path} @var{owner} @var{user} @var{[#:http-get]} +Assert that the resource at @var{path} on @var{server-name} is owned +by @var{owner}, and check that @var{user} has the proper +authorization. Otherwise, raise an exception of type +@code{&forbidden}. +@end deffn + +@node Running a client +@chapter Running a client + +To run a client, you need to proceed in two steps. First, acquire an +OIDC ID token and an access token from the identity provider, and then +present the access token and a proof of possession of the linked key +in each request, in a DPoP HTTP header. + +The first operation is performed by the @emph{(webid-oidc client)} +module. + +@deffn function authorize @var{host/webid} @var{#client-id} @var{#redirect-uri} @var{[#state]} @var{[#http-get]} +The user enters a valid webid or a host name, and then this function +will query it (with the @var{http-get} parameter, by default the web +client from @emph{(web client)}) to determine the authorization +endpoint. The function will return an alist of authorization URIs, +indexed by approved identity provider URIs, that the user should +browse with a traditional web browser. + +Each application should have its own webid, or in that case +@var{client-id}, that can be dereferenced by the identity provider. + +Once the user has given authorization, the user’s agent will be +redirected to @var{redirect-uri}, with the authorization code as a GET +parameter. It is possible to pass a @var{state}, but this is optional. +@end deffn + +Once the client gets the authorization code, it is necessary to create +an access token and ID token. + +@deffn function token @var{host} @var{client-key} @var{[#authorization-code]} @var{[#refresh-token]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]} +Trade an @var{authorization-code}, or a @var{refresh-token}, for an ID +token and an access token bound to the @var{client-key} issued by +@var{host}, the identity provider. + +You can override the HTTP client used (@var{http-get} and +@var{http-post}), and how to compute the time (@var{current-time}). +@end deffn + +In an application, you would have a list of profiles in XDG_DATA_HOME, +consisting of triples (webid, issuer, refresh token). + +@deffn function list-profiles @var{[#dir]} +Read the list of available profiles. Returns a list of triples, webid, +issuer, reresh token. + +By default, this function will look for the profiles file in +@var{XDG_DATA_HOME}. You can bypass it by providing the @var{#dir} +optional keyword argument. +@end deffn + +@deffn function setup @var{get-host/webid} @var{choose-provider} @var{browse-authorization-uri} @var{#client-id} @var{#redirect-uri} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]} +Negociate a refresh token, and save it. The function returns 3 values: +the decoded ID token pyload, the encoded access token and the key +pair. + +The @var{get-host/webid} thunk should ask the user’s webid or identity +provider, and return it. @var{choose-provider} is called with a list +of possible identity providers as host names (strings), and the user +should choose one. The chosen one is returned. Finally, +@var{browse-authorization-uri} should ask or let the user browse an +URI as its argument, and return the authorization code taken from the +redirect URI. + +The refresh token is saved to disk, as a profile, in +XDG_DATA_HOME. Pass the optional @var{#dir} keyword argument to +override the location. + +You need to set @var{client-id} to the public webid of the app, and +@var{redirect-uri} to one of the approved redirection URIs for the +application ID. +@end deffn + +@deffn function login @var{webid} @var{issuer} @var{refresh-token} @var{key} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]} +If you have already a known profile, you can use it to automatically +log in. This function might update the refresh token if it changed, so +you can again set @var{#dir}. Please note that the @var{refresh-token} +is bound to the client @var{key} on server side, so you must always +use the same @var{key}. +@end deffn + +@deffn function refresh @var{id-token} @var{key} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]} +If you have an ID token bound to a known profile, this helper function +will look up the associated refresh token and log in. +@end deffn + +@deffn function make-client @var{id-token} @var{access-token} @var{key} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#http-request]} @var{[#current-time]} +Return a replacement of @code{http-request} from @emph{(web client)}, +that automatically signs requests and refresh the tokens when needed. + +@var{#http-get} and @var{#http-post} are only used to refresh the +tokens, while @var{#http-request} is used as a back-end for the +requests. + +@var{#current-time} is set to a thunk that returns the time. It is +used to issue DPoP proofs. +@end deffn + +An example application is provided as the +@code{disfluid-example-app} program. It demonstrates how +authentication is done. It should help you understand how Solid-OIDC +works. + +The identity provider needs to call the application on the web. So, +your client should have a public endpoint on the web. + +@deffn function serve-application @var{id} @var{redirect-uri} @var{[#client-name]} @var{[#client-uri]} +Return a handler for web requests to serve the application manifest +and the redirection to transmit the authorization code. You should set +the @var{client-name} to your application name and @var{client-uri} to +point to where to a presentation of your application. +@end deffn + +@node Exceptional conditions +@chapter Exceptional conditions + +The library will raise an exception whenever something fishy +occurs. For instance, if a signature is invalid, or the expiration +date has passed. All exception types are defined in +@code{(webid-oidc errors)}. + +@deffn function error->str @var{error} @var{[#depth]} +Return a string explaining the @var{error}. You can limit the +@var{depth} of the explanation as an integer. +@end deffn + +@menu +* Invalid data format:: +* Invalid JWT:: +* Cannot fetch data on the web:: +* Other errors in the protocol or from a reasonable implementation:: +* Server-side errors:: +@end menu + +@node Invalid data format +@section Invalid data format +There are a few JSON objects with required fields. This exceptions +usually occur as the cause of a higher-level exception. + +@deftp {exception type} ¬-base64 @var{value} @var{cause} +This exception is raised when the base64 decoding function +failed. @var{value} is the incorrect input, and @var{cause} is a +low-level error. +@end deftp + +@deftp {exception type} ¬-json @var{value} @var{cause} +Cannot decode @var{value} to a JSON object. +@end deftp + +@deftp {exception type} ¬-turtle @var{value} @var{cause} +Cannot decode @var{value} to a RDF graph. +@end deftp + +@deftp {exception type} &incorrect-webid-field @var{value} +The @var{value} of the webid field in the JWT is missing (if +@code{#f}), or not an acceptable value. +@end deftp + +@deftp {exception type} &incorrect-iss-field @var{value} +The @var{value} of the iss field is incorrect. +@end deftp + +@deftp {exception type} &incorrect-aud-field @var{value} +The @var{value} of the aud field is incorrect. +@end deftp + +@deftp {exception type} &incorrect-iat-field @var{value} +The @var{value} of the iat field is incorrect. +@end deftp + +@deftp {exception type} &incorrect-exp-field @var{value} +The @var{value} of the exp field is incorrect. +@end deftp + +@deftp {exception type} &incorrect-cnf/jkt-field @var{value} +The @var{value} of the cnf/jkt field is incorrect. +@end deftp + +@deftp {exception type} &incorrect-client-id-field @var{value} +The @var{value} of the client-id field is incorrect. +@end deftp + +@deftp {exception type} &incorrect-typ-field @var{value} +The @var{value} of the typ field in the DPoP proof header is +incorrect. +@end deftp + +@deftp {exception type} &incorrect-jwk-field @var{value} @var{cause} +The @var{value} of the jwk field in the DPoP proof header is +incorrect. +@end deftp + +@deftp {exception type} &incorrect-jti-field @var{value} +The @var{value} of the jti field in the DPoP proof is incorrect. +@end deftp + +@deftp {exception type} &incorrect-htm-field @var{value} +The @var{value} of the htm field in the DPoP proof is incorrect. +@end deftp + +@deftp {exception type} &incorrect-htu-field @var{value} +The @var{value} of the htu field in the DPoP proof is incorrect. +@end deftp + +@deftp {exception type} &incorrect-ath-field @var{value} +The @var{value} of the ath field is not the hash of the access token. +@end deftp + +@deftp {exception type} &incorrect-redirect-uris-field @var{value} +The @var{value} of the redirect-uris field of a client manifest is +incorrect. +@end deftp + +@deftp {exception type} &incorrect-typ-field @var{value} +The @var{value} of the typ field in the DPoP proof header is +incorrect. +@end deftp + +@deftp {exception type} &incorrect-sub-field @var{value} +The @var{value} of the sub field is incorrect. +@end deftp + +@deftp {exception type} &incorrect-iss-field @var{value} +The @var{value} of the iss field is incorrect. +@end deftp + +@deftp {exception type} &incorrect-nonce-field @var{value} +The @var{value} of the nonce field in the DPoP proof is incorrect. +@end deftp + +@deftp {exception type} &incorrect-htm-field @var{value} +The @var{value} of the htm field in the DPoP proof is incorrect. +@end deftp + +@deftp {exception type} ¬-a-client-manifest @var{value} @var{cause} +The @var{client-manifest} is incorrect. +@end deftp + +@node Invalid JWT +@section Invalid JWT +Each JWT type – access token, DPoP proof, ID token, authorization code +(this is internal to the identity provider) has different validation +rules, and can fail in different ways. + +@deftp {exception type} &unsupported-crv @var{crv} +The identifier @var{crv} does not identify an elliptic curve. +@end deftp + +@deftp {exception type} ¬-a-jwk @var{value} @var{cause} +@var{value} does not identify a JWK. +@end deftp + +@deftp {exception type} ¬-a-public-jwk @var{value} @var{cause} +@var{value} does not identify a public JWK. +@end deftp + +@deftp {exception type} ¬-a-private-jwk @var{value} @var{cause} +@var{value} does not identify a private JWK. +@end deftp + +@deftp {exception type} ¬-a-jwks @var{value} @var{cause} +@var{value} does not identify a set of public keys. +@end deftp + +@deftp {exception type} &unsupported-alg @var{value} +@var{value} does not identify a valid hash algorithm. +@end deftp + +@deftp {exception type} &invalid-signature @var{key} @var{payload} @var{signature} +@var{key} has not signed @var{payload} with @var{signature}. +@end deftp + +@deftp {exception type} &missing-alist-key @var{value} @var{key} +@var{value} isn’t an alist, or is missing a value with @var{key}. +@end deftp + +@deftp {exception type} ¬-a-jws-header @var{value} @var{cause} +@var{value} does not identify a decoded JWS header. +@end deftp + +@deftp {exception type} ¬-a-jws-payload @var{value} @var{cause} +@var{value} does not identify a decoded JWS payload. +@end deftp + +@deftp {exception type} ¬-a-jws @var{value} @var{cause} +@var{value} does not identify a decoded JWS. +@end deftp + +@deftp {exception type} ¬-in-3-parts @var{string} @var{separator} +@var{string} cannot be split into 3 parts with @var{separator}. +@end deftp + +@deftp {exception type} &no-matching-key @var{candidates} @var{alg} @var{payload} @var{signature} +No key among @var{candidates} could verify @var{signature} signed with +@var{alg} for @var{payload}, because the signature mismatched for all +keys. +@end deftp + +@deftp {exception type} &cannot-decode-jws @var{value} @var{cause} +The @var{value} string is not an encoding of a valid JWS. +@end deftp + +@deftp {exception type} &cannot-encode-jws @var{jws} @var{key} @var{cause} +The @var{jws} cannot be signed. +@end deftp + +@deftp {exception type} ¬-an-access-token @var{value} @var{cause} +The @var{value} is not an access token. +@end deftp + +@deftp {exception type} ¬-an-access-token-header @var{value} @var{cause} +The @var{value} is not an access token header. +@end deftp + +@deftp {exception type} ¬-an-access-token-payload @var{value} @var{cause} +The @var{value} is not an access token payload. +@end deftp + +@deftp {exception type} &cannot-decode-access-token @var{value} @var{cause} +The @var{value} string is not an encoding of a valid access token. +@end deftp + +@deftp {exception type} &cannot-encode-access-token @var{access-token} @var{key} @var{cause} +The @var{access-token} cannot be signed. +@end deftp + +@deftp {exception type} ¬-a-dpop-proof @var{value} @var{cause} +The @var{value} is not a DPoP proof. +@end deftp + +@deftp {exception type} ¬-a-dpop-proof-header @var{value} @var{cause} +The @var{value} is not a DPoP proof header. +@end deftp + +@deftp {exception type} ¬-a-dpop-proof-payload @var{value} @var{cause} +The @var{value} is not a DPoP proof payload. +@end deftp + +@deftp {exception type} &cannot-decode-dpop-proof @var{value} @var{cause} +The @var{value} string is not an encoding of a valid DPoP proof. +@end deftp + +@deftp {exception type} &cannot-encode-dpop-proof @var{dpop-proof} @var{key} @var{cause} +The @var{dpop-proof} cannot be signed. +@end deftp + +@deftp {exception type} ¬-an-authorization-code @var{value} @var{cause} +The @var{value} is not an authorization code. +@end deftp + +@deftp {exception type} ¬-an-authorization-code-header @var{value} @var{cause} +The @var{value} is not an authorization code header. +@end deftp + +@deftp {exception type} ¬-an-authorization-code-payload @var{value} @var{cause} +The @var{value} is not an authorization code payload. +@end deftp + +@deftp {exception type} &cannot-decode-authorization-code @var{value} @var{cause} +The @var{value} string is not an encoding of a valid authorization +code. +@end deftp + +@deftp {exception type} &cannot-encode-authorization-code @var{authorization-code} @var{key} @var{cause} +The @var{authorization-code} cannot be signed. +@end deftp + +@deftp {exception type} ¬-an-id-token @var{value} @var{cause} +The @var{value} is not an ID token. +@end deftp + +@deftp {exception type} ¬-an-id-token-header @var{value} @var{cause} +The @var{value} is not an ID token header. +@end deftp + +@deftp {exception type} ¬-an-id-token-payload @var{value} @var{cause} +The @var{value} is not an ID token payload. +@end deftp + +@deftp {exception type} &cannot-decode-id-token @var{value} @var{cause} +The @var{value} string is not an encoding of a valid ID token. +@end deftp + +@deftp {exception type} &cannot-encode-id-token @var{id-token} @var{key} @var{cause} +The @var{id-token} cannot be signed. +@end deftp + +@node Cannot fetch data on the web +@section Cannot fetch data on the web +In the client (local and public parts), resource server and identity +provider, the protocol requires to fetch data on the web. + +@deftp {exception type} &request-failed-unexpectedly @var{response-code} @var{response-reason-phrase} +We expected the request to succeed, but the server sent a non-OK +@var{response-code}. +@end deftp + +@deftp {exception type} &unexpected-header-value @var{header} @var{value} +We did not expect the server to respond with @var{header} set to +@var{value}. +@end deftp + +@deftp {exception type} &unexpected-response @var{response} @var{cause} +The @var{response} (from @emph{(web response)}) is not appropriate. +@end deftp + +@deftp {exception type} ¬-an-oidc-configuration @var{value} @var{cause} +The @var{value} is not appropriate an OIDC configuration. +@end deftp + +@deftp {exception type} &cannot-fetch-issuer-configuration @var{issuer} @var{cause} +It is impossible to fetch the configuration of @var{issuer}. +@end deftp + +@deftp {exception type} &cannot-fetch-jwks @var{issuer} @var{uri} @var{cause} +It is impossible to fetch the keys of @var{issuer} at @var{uri}. +@end deftp + +@deftp {exception type} &cannot-fetch-linked-data @var{uri} @var{cause} +Could not fetch the graph referenced by @var{uri}. +@end deftp + +@deftp {exception type} &cannot-fetch-client-manifest @var{id} @var{cause} +Could not fetch a client manifest at @var{id}. +@end deftp + +@node Other errors in the protocol or from a reasonable implementation +@section Other errors in the protocol or from a reasonable implementation +The protocol does not rely solely on JWT validation, so these errors +may happen too. + +@deftp {exception type} &dpop-method-mismatch @var{signed} @var{requested} +The method value @var{signed} in the DPoP proof does not match the +method that is @var{requested} on the server. +@end deftp + +@deftp {exception type} &dpop-uri-mismatch @var{signed} @var{requested} +The URI value @var{signed} in the DPoP proof does not match the URI +that is @var{requested} on the server. +@end deftp + +@deftp {exception type} &dpop-signed-in-future @var{signed} @var{current} +The proof is @var{signed} for a date which is too much ahead of the +@var{current} time. +@end deftp + +@deftp {exception type} &dpop-too-old @var{signed} @var{current} +The proof was @var{signed} at a past date of @var{current}. +@end deftp + +@deftp {exception type} &dpop-unconfirmed-key @var{key} @var{expected} @var{cause} +The confirmation of @var{key} is not what is @var{expected}, or (if a +function was passed as @var{cnf/check}) the @var{cause} exception +occurred while confirming. +@end deftp + +@deftp {exception type} &dpop-invalid-access-token-hash @var{hash} @var{access-token} +The @var{access-token} passed to the resource server does not match +the @var{hash} provided in the DPoP proof. +@end deftp + +@deftp {exception type} &jti-found @var{jti} @var{cause} +The @var{jti} of the proof has already been issued in a recent past. +@end deftp + +@deftp {exception type} &unauthorized-redirection-uri @var{manifest} @var{uri} +The authorization @var{uri} is not advertised in @var{manifest}. +@end deftp + +@deftp {exception type} &cannot-serve-public-manifest +You cannot serve the public client manifest. +@end deftp + +@deftp {exception type} &no-client-manifest-registration @var{id} +The @var{id} client manifest does not have a registration triple in +its document. +@end deftp + +@deftp {exception type} &inconsistent-client-manifest-id @var{id} @var{advertised-id} +The client @var{manifest} is being fetched at @var{id}, but it is +valid for another client @var{advertised-id}. +@end deftp + +@deftp {exception type} &authorization-code-expired @var{exp} @var{current-time} +The authorization code has expired at @var{exp}, it is now +@var{current-time}. +@end deftp + +@deftp {exception type} &invalid-refresh-token @var{refresh-token} +The @var{refresh-token} is unknown to the identity provider. +@end deftp + +@deftp {exception type} &invalid-key-for-refresh-token @var{key} @var{jkt} +The refresh token was issued for @var{jkt}, but it is used with +@var{key}. +@end deftp + +@deftp {exception type} &unknown-client-locale @var{web-locale} @var{c-locale} +The @var{web-locale} of the client, translated to C as @var{c-locale}, +cannot be set. This exception is always continuable; if the handler +returns, then the page will be served in the english locale. +@end deftp + +@deftp {exception type} &unsupported-grant-type @var{value} +The token request failed to indicate a @var{value} for the grant type, +or indicated an unsupported grant type. +@end deftp + +@deftp {exception type} &no-authorization-code +The token request forgot to put an authorization code. +@end deftp + +@deftp {exception type} &no-refresh-token +The token request forgot to put a refresh token with the request. +@end deftp + +@deftp {exception type} &unconfirmed-provider @var{subject} @var{provider} +@var{provider} is not confirmed by @var{subject} as an identity +provider. +@end deftp + +@deftp {exception type} &no-provider-candidates @var{webid} @var{causes} +The @var{webid} cannot be certified by any identity providers. The +@var{causes} alist indicates an error for each candidates. +@end deftp + +@deftp {exception type} &neither-identity-provider-nor-webid @var{uri} @var{why-not-identity-provider} @var{why-not-webid} +The @var{uri} you passed to get an authorization code is neither an +identity provider (because @var{why-not-identity-provider}) nor a +webid (because @var{why-not-webid}). +@end deftp + +@deftp {exception type} &token-request-failed @var{cause} +The token request failed on the server. +@end deftp + +@deftp {exception type} &profile-not-found @var{webid} @var{iss} @var{dir} +The @var{webid}, as certified by @var{iss}, cannot be refreshed +because we don’t have a refresh token stored in @var{dir}. +@end deftp + +@node Server-side errors +@section Server-side errors +The resource server implementation may encounter some more exceptional +conditions. + +@deftp {exception type} &path-not-found @var{path} +There is no registered resource at @var{path}. +@end deftp + +@deftp {exception type} &auxiliary-resource-absent @var{path} @var{kind} +The auxiliary resource of given @var{kind} is not instanciated on the +server for the base resource @var{path}. +@end deftp + +@deftp {exception type} &uri-slash-semantics-error @var{path} @var{expected-path} +While the resource at @var{path} does not exist, the resource at +@var{expected-path} does, and @var{path} and @var{expected-path} +differ only by a trailing slash. This exception may be raised along +with @code{&path-not-found}. + +Beware that even if it is true at the time when the exception is +created, maybe the resource has been created by the time it is +handled. +@end deftp + +@deftp {exception type} &cannot-delete-root +There was a request to delete the root storage, which is an error. +@end deftp + +@deftp {exception type} &container-not-empty @var{path} +There was a request to delete a non-empty container. +@end deftp + +@deftp {exception type} &cannot-fetch-group @var{group-uri} @var{cause} +The access control could not fetch the group @var{group-uri} (with a +known @var{cause}). This warning is continuable every time it is +raised. If the handler returns, then the group will be considered +empty. +@end deftp + +@deftp {exception type} &incorrect-containment-triples @var{path} +The client wanted to create or update a resource, and by that it tried +to change the containment triples at @var{path}. +@end deftp + +@deftp {exception type} &unsupported-media-type @var{content-type} +The client wanted to create a resource with the given +@var{content-type}, but it is not accepted, because @var{content-type} +is not recognized as an RDF content type. +@end deftp + +@deftp {exception type} &path-is-auxiliary @var{path} +The client wanted to create a resource that targets an auxiliary +resource, at @var{path}. +@end deftp + +@deftp {exception type} &forbidden @var{path} @var{user} @var{owner} @var{mode} +The @var{user} wanted to do something under @var{path} requiring +@var{mode}, but it is not the @var{owner} and it is forbidden by WAC. +@end deftp + +@deftp {exception type} &precondition-failed @var{path} @var{if-match} @var{if-none-match} @var{real-etag} +The resource under @var{path} has a @var{real-etag} that does not +match the request headers @var{if-match} and @var{if-none-match}. + +If the resource does not exist, @var{real-etag} is set to +@code{#f}. In this case, an exception of type @code{&path-not-found} +is also thrown. +@end deftp + +@deftp {exception type} ¬-acceptable @var{client-accepts} @var{path} @var{content-type} +The client wanted a response with a specific set of +@var{client-accept}ed content-types, but the real @var{content-type} +of the resource under @var{path} cannot be converted to one of them. +@end deftp +@node GNU Free Documentation License +@appendix GNU Free Documentation License + +@include fdl.texi + +@node Index +@unnumbered Index + +@printindex cp + +@bye |