mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
docs: Add architecture diagrams and documentation
This commit is contained in:
parent
dd9781b5f2
commit
528823725a
@ -38,7 +38,7 @@ the [changelog](https://github.com/CommunitySolidServer/CommunitySolidServer/blo
|
|||||||
## What the internals look like
|
## What the internals look like
|
||||||
|
|
||||||
* [How the server uses dependency injection](architecture/dependency-injection.md)
|
* [How the server uses dependency injection](architecture/dependency-injection.md)
|
||||||
* [What the architecture looks like](architecture/architecture.md)
|
* [What the architecture looks like](architecture/overview.md)
|
||||||
|
|
||||||
## Making changes
|
## Making changes
|
||||||
|
|
||||||
|
@ -1,15 +1,7 @@
|
|||||||
# Architecture overview
|
# Core building blocks
|
||||||
|
|
||||||
The initial architecture document the project was started from can be found [here](https://rubenverborgh.github.io/solid-server-architecture/solid-architecture-v1-3-0.pdf).
|
There are several core building blocks used in many places of the server.
|
||||||
Many things have been added since the original inception of the project,
|
These are described here.
|
||||||
but the core ideas within that document are still valid.
|
|
||||||
|
|
||||||
As can be seen from the architecture, an important idea is the modularity of all components.
|
|
||||||
No actual implementations are defined there, only their interfaces.
|
|
||||||
Making all the components independent of each other in such a way provides us with an enormous flexibility:
|
|
||||||
they can all be replaced by a different implementation, without impacting anything else.
|
|
||||||
This is how we can provide many different configurations for the server,
|
|
||||||
and why it is impossible to provide ready solutions for all possible combinations.
|
|
||||||
|
|
||||||
## Handlers
|
## Handlers
|
||||||
A very important building block that gets reused in many places is the `AsyncHandler`.
|
A very important building block that gets reused in many places is the `AsyncHandler`.
|
||||||
@ -48,26 +40,3 @@ Internally this means we are mostly handling data as `Readable` objects.
|
|||||||
We actually use `Guarded<Readable>` which is an internal format we created to help us with error handling.
|
We actually use `Guarded<Readable>` which is an internal format we created to help us with error handling.
|
||||||
Such streams can be created using utility functions such as `guardStream` and `guardedStreamFrom`.
|
Such streams can be created using utility functions such as `guardStream` and `guardedStreamFrom`.
|
||||||
Similarly, we have a `pipeSafely` to pipe streams in such a way that also helps with errors.
|
Similarly, we have a `pipeSafely` to pipe streams in such a way that also helps with errors.
|
||||||
|
|
||||||
## Example request
|
|
||||||
In this section we will give a high level overview of all the components
|
|
||||||
a request passes through when it enters the server.
|
|
||||||
This is specifically an LDP request, e.g. a POST request to create a new resource.
|
|
||||||
|
|
||||||
1. The correct `HttpHandler` gets found, responsible for LDP requests.
|
|
||||||
2. The HTTP request gets parsed into a manageable format, both body and metadata such as headers.
|
|
||||||
3. The identification credentials of the request, if any, are extracted and parsed to authenticate the calling agent.
|
|
||||||
4. The request gets authorized or rejected, based on the credentials from step 3
|
|
||||||
and the authorization rules of the target resource.
|
|
||||||
5. Based on the HTTP method, the corresponding method from the `ResourceStore` gets called,
|
|
||||||
which in the case of a POST request will return the location of the newly created error.
|
|
||||||
6. The returned data and metadata get converted to an HTTP response and sent back in the `ResponseWriter`.
|
|
||||||
|
|
||||||
In case any of the steps above error, an error will be thrown.
|
|
||||||
The `ErrorHandler` will convert the error to an HTTP response to be returned.
|
|
||||||
|
|
||||||
Below are sections that go deeper into the specific steps.
|
|
||||||
Not all steps are covered yet and will be added in the future.
|
|
||||||
|
|
||||||
* [How authentication and authorization work](features/authorization.md)
|
|
||||||
* [What the `ResourceStore` looks like](features/resource-store.md)
|
|
@ -1,58 +0,0 @@
|
|||||||
# Authorization
|
|
||||||
|
|
||||||
Authorization is usually handled by the `AuthorizingHttpHandler`,
|
|
||||||
and goes in the following steps:
|
|
||||||
|
|
||||||
1. Identify the credentials of the agent making the call.
|
|
||||||
2. Extract which access modes are needed for which resources.
|
|
||||||
3. Reading the permissions the agent has.
|
|
||||||
4. Compare the above results to see if the request is allowed.
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
There are multiple `CredentialsExtractor`s that each determine identity in a different way.
|
|
||||||
Potentially multiple extractors can apply,
|
|
||||||
making a requesting agent have multiple credentials.
|
|
||||||
The `DPoPWebIdExtractor` is most relevant for the [Solid-OIDC specification](https://solid.github.io/solid-oidc/),
|
|
||||||
as it parses the access token generated by a Solid Identity Provider.
|
|
||||||
Besides that there are always the public credentials, which everyone has.
|
|
||||||
There are also some debug extractors that can be used to simulate credentials,
|
|
||||||
which can be enabled as different options through the `config/ldp/authentication` imports.
|
|
||||||
|
|
||||||
If successful, a `CredentialsExtractor` will return a key/value map
|
|
||||||
linking the type of credentials to their specific values.
|
|
||||||
|
|
||||||
## Modes extraction
|
|
||||||
Access modes are a predefined list of `read`, `write`, `append`, `create` and `delete`.
|
|
||||||
The `ModesExtractor`s determine which modes will be necessary and for which resources,
|
|
||||||
based on the request contents.
|
|
||||||
The `MethodModesExtractor` determines modes based on the HTTP method.
|
|
||||||
A GET request will always need the `read` mode for example.
|
|
||||||
Specifically for PATCH requests there are extractors for each supported PATCH type,
|
|
||||||
such as the `N3PatchModesExtractor`,
|
|
||||||
which parses the N3 Patch body to know if it will add new data or only delete data.
|
|
||||||
|
|
||||||
## Permission reading
|
|
||||||
`PermissionReaders` take the input of the above to determine which permissions are available for which credentials.
|
|
||||||
The modes from the previous step are not yet needed,
|
|
||||||
but can be used as optimization as we only need to know if we have permission on those modes.
|
|
||||||
Each reader returns all the information it can find based on the resources and modes it receives.
|
|
||||||
Those results then get combined in the `UnionPermissionReader`.
|
|
||||||
In the default configuration the following readers are combined.
|
|
||||||
|
|
||||||
* `PathBasedReader` rejects all permissions for certain paths, to prevent access to internal data.
|
|
||||||
* `OwnerPermissionReader` grants control permissions to agents that are trying to access data in a pod that they own.
|
|
||||||
* `AuxiliaryReader` handles all permissions for auxiliary resources by requesting those of the subject resource if necessary.
|
|
||||||
* `ParentContainerReader` checks the necessary permissions on a parent container when creating or deleting a resource.
|
|
||||||
* `WebAclAuxiliaryReader` determines permissions on ACL resources by requesting if the subject resource has control permissions.
|
|
||||||
* `WebAclReader` reads out the relevant ACL resource to read out the defined permissions.
|
|
||||||
|
|
||||||
All of the above is if you have WebACL enabled.
|
|
||||||
It is also possible to always grant all permissions for debugging reasons
|
|
||||||
by changing the authorization import to `config/ldp/authorization/allow-all.json`.
|
|
||||||
|
|
||||||
## Authorization
|
|
||||||
All the results of the previous steps then get combined to either allow or reject a request.
|
|
||||||
If no permissions are found for a requested mode,
|
|
||||||
or they are explicitly forbidden,
|
|
||||||
a 401/403 will be returned,
|
|
||||||
depending on if the agent was logged in or not.
|
|
52
documentation/markdown/architecture/features/cli.md
Normal file
52
documentation/markdown/architecture/features/cli.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Parsing Command line arguments
|
||||||
|
|
||||||
|
When starting the server, the application actually uses Components.js twice to instantiate components.
|
||||||
|
The first instantiation is used to parse the command line arguments.
|
||||||
|
These then get converted into Components.js variables and are used to instantiate the actual server.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
CliResolver("<strong>CliResolver</strong><br>CliResolver")
|
||||||
|
CliResolver --> CliResolverArgs
|
||||||
|
|
||||||
|
subgraph CliResolverArgs[" "]
|
||||||
|
CliExtractor("<strong>CliExtractor</strong><br>YargsCliExtractor")
|
||||||
|
ShorthandResolver("<strong>ShorthandResolver</strong><br>CombinedShorthandResolver")
|
||||||
|
end
|
||||||
|
|
||||||
|
ShorthandResolver --> ShorthandResolverArgs
|
||||||
|
subgraph ShorthandResolverArgs[" "]
|
||||||
|
BaseUrlExtractor("<br>BaseUrlExtractor")
|
||||||
|
KeyExtractor("<br>KeyExtractor")
|
||||||
|
AssetPathExtractor("<br>AssetPathExtractor")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The `CliResolver` (`urn:solid-server-app-setup:default:CliResolver`) is simply a way
|
||||||
|
to combine both the `CliExtractor` (`urn:solid-server-app-setup:default:CliExtractor`)
|
||||||
|
and `ShorthandResolver` (`urn:solid-server-app-setup:default:ShorthandResolver`)
|
||||||
|
into a single object and has no other function.
|
||||||
|
|
||||||
|
Which arguments are supported and which Components.js variables are generated
|
||||||
|
can depend on the configuration that is being used.
|
||||||
|
For example, for an HTTPS server additional arguments will be needed to specify the necessary key/cert files.
|
||||||
|
|
||||||
|
## CliResolver
|
||||||
|
The `CliResolver` converts the incoming string of arguments into a key/value object.
|
||||||
|
By default, a `YargsCliExtractor` is used, which makes use of the `yargs` library and is configured similarly.
|
||||||
|
|
||||||
|
## ShorthandResolver
|
||||||
|
The `ShorthandResolver` uses the key/value object that was generated above to generate Components.js variable bindings.
|
||||||
|
A `CombinedShorthandResolver` combines the results of multiple `ShorthandExtractor`
|
||||||
|
by mapping their values to specific variables.
|
||||||
|
For example, a `BaseUrlExtractor` will be used to extract the value for `baseUrl`,
|
||||||
|
or `port` if no `baseUrl` value is provided,
|
||||||
|
and use it to generate the value for the variable `urn:solid-server:default:variable:baseUrl`.
|
||||||
|
|
||||||
|
These extractors are also where the default values for the server are defined.
|
||||||
|
For example, BaseUrlExtractor will be instantiated with a default port of `3000`
|
||||||
|
which will be used if no port is provided.
|
||||||
|
|
||||||
|
The variables generated here will be used to [initialize the server](initialization.md).
|
86
documentation/markdown/architecture/features/http-handler.md
Normal file
86
documentation/markdown/architecture/features/http-handler.md
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# Handling HTTP requests
|
||||||
|
The direction of the arrows was changed slightly here to make the graph readable.
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
HttpHandler("<strong>HttpHandler</strong><br>SequenceHandler")
|
||||||
|
HttpHandler --> HttpHandlerArgs
|
||||||
|
|
||||||
|
subgraph HttpHandlerArgs[" "]
|
||||||
|
direction LR
|
||||||
|
Middleware("<strong>Middleware</strong><br><i>HttpHandler</i>")
|
||||||
|
WaterfallHandler("<br>WaterfallHandler")
|
||||||
|
end
|
||||||
|
|
||||||
|
Middleware --> WaterfallHandler
|
||||||
|
WaterfallHandler --> WaterfallHandlerArgs
|
||||||
|
|
||||||
|
subgraph WaterfallHandlerArgs[" "]
|
||||||
|
direction TB
|
||||||
|
StaticAssetHandler("<strong>StaticAssetHandler</strong><br>StaticAssetHandler")
|
||||||
|
SetupHandler("<strong>SetupHandler</strong><br><i>HttpHandler</i>")
|
||||||
|
OidcHandler("<strong>OidcHandler</strong><br><i>HttpHandler</i>")
|
||||||
|
AuthResourceHttpHandler("<strong>AuthResourceHttpHandler</strong><br><i>HttpHandler</i>")
|
||||||
|
IdentityProviderHttpHandler("<strong>IdentityProviderHttpHandler</strong><br><i>HttpHandler</i>")
|
||||||
|
LdpHandler("<strong>LdpHandler</strong><br><i>HttpHandler</i>")
|
||||||
|
end
|
||||||
|
|
||||||
|
StaticAssetHandler --> SetupHandler
|
||||||
|
SetupHandler --> OidcHandler
|
||||||
|
OidcHandler --> AuthResourceHttpHandler
|
||||||
|
AuthResourceHttpHandler --> IdentityProviderHttpHandler
|
||||||
|
IdentityProviderHttpHandler --> LdpHandler
|
||||||
|
```
|
||||||
|
|
||||||
|
The `HttpHandler` is responsible for handling an incoming HTTP request.
|
||||||
|
The request will always first go through the `Middleware`,
|
||||||
|
where certain required headers will be added such as CORS headers.
|
||||||
|
|
||||||
|
After that it will go through the list in the `WaterfallHandler`
|
||||||
|
to find the first handler that understands the request,
|
||||||
|
with the `LdpHandler` at the bottom being the catch-all default.
|
||||||
|
|
||||||
|
## StaticAssetHandler
|
||||||
|
The `urn:solid-server:default:StaticAssetHandler` matches exact URLs to static assets which require no further logic.
|
||||||
|
An example of this is the favicon, where the `/favicon.ico` URL
|
||||||
|
is directed to the favicon file at `/templates/images/favicon.ico`.
|
||||||
|
It can also map entire folders to a specific path, such as `/.well-known/css/styles/` which contains all stylesheets.
|
||||||
|
|
||||||
|
## SetupHandler
|
||||||
|
The `urn:solid-server:default:SetupHandler` is responsible
|
||||||
|
for redirecting all requests to `/setup` until setup is finished,
|
||||||
|
thereby ensuring that setup needs to be finished before anything else can be done on the server,
|
||||||
|
and handling the actual setup request that is sent to `/setup`.
|
||||||
|
Once setup is finished, this handler will reject all requests and thus no longer be relevant.
|
||||||
|
|
||||||
|
If the server is configured to not have setup enabled,
|
||||||
|
the corresponding identifier will point to a handler that always rejects all requests.
|
||||||
|
|
||||||
|
## OidcHandler
|
||||||
|
The `urn:solid-server:default:OidcHandler` handles all requests related
|
||||||
|
to the Solid-OIDC [specification](https://solid.github.io/solid-oidc/).
|
||||||
|
The OIDC component is configured to work on the `/.oidc/` subpath,
|
||||||
|
so this handler catches all those requests and sends them to the internal OIDC library that is used.
|
||||||
|
|
||||||
|
## AuthResourceHttpHandler
|
||||||
|
The `urn:solid-server:default:AuthResourceHttpHandler` is identical
|
||||||
|
to the `urn:solid-server:default:LdpHandler` which will be discussed below,
|
||||||
|
but only handles resources relevant for authorization.
|
||||||
|
|
||||||
|
In practice this means that is your server is configured
|
||||||
|
to use [Web Access Control](https://solidproject.org/TR/wac) for authorization,
|
||||||
|
this handler will catch all requests targeting `.acl` resources.
|
||||||
|
|
||||||
|
The reason these already need to be handled here is so these can also be used
|
||||||
|
to allow authorization on the following handler(s).
|
||||||
|
More on this can be found in the [identity provider](../../../usage/identity-provider/#access) documentation
|
||||||
|
|
||||||
|
## IdentityProviderHttpHandler
|
||||||
|
The `urn:solid-server:default:IdentityProviderHttpHandler` handles everything
|
||||||
|
related to our custom identity provider API, such as registering, logging in, returning the relevant HTML pages, etc.
|
||||||
|
All these requests are identified by being on the `/idp/` subpath.
|
||||||
|
More information on the API can be found in the [identity provider](../../../usage/identity-provider) documentation
|
||||||
|
|
||||||
|
## LdpHandler
|
||||||
|
Once a request reaches the `urn:solid-server:default:LdpHandler`,
|
||||||
|
the server assumes this is a standard Solid request according to the Solid protocol.
|
||||||
|
A detailed description of what happens then can be found [here](protocol/overview.md)
|
124
documentation/markdown/architecture/features/initialization.md
Normal file
124
documentation/markdown/architecture/features/initialization.md
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
# Server initialization
|
||||||
|
|
||||||
|
When starting the server, multiple Initializers trigger to set up everything correctly,
|
||||||
|
the last one of which starts listening to the specified port.
|
||||||
|
Similarly, when stopping the server several Finalizers trigger to clean up where necessary,
|
||||||
|
although the latter only happens when starting the server through code.
|
||||||
|
|
||||||
|
## App
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
App("<strong>App</strong><br>App")
|
||||||
|
App --> AppArgs
|
||||||
|
|
||||||
|
subgraph AppArgs[" "]
|
||||||
|
Initializer("<strong>Initializer</strong><br><i>Initializer</i>")
|
||||||
|
AppFinalizer("<strong>Finalizer</strong><br><i>Finalizer</i>")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
`App` (`urn:solid-server:default:App`) is the main component that gets instantiated by Components.js.
|
||||||
|
Every other component should be able to trace an instantiation path back to it if it also wants to be instantiated.
|
||||||
|
|
||||||
|
It's only function is to contain an `Initializer` and `Finalizer`
|
||||||
|
which get called by calling `start`/`stop` respectively.
|
||||||
|
|
||||||
|
## Initializer
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Initializer("<strong>Initializer</strong><br>SequenceHandler")
|
||||||
|
Initializer --> InitializerArgs
|
||||||
|
|
||||||
|
subgraph InitializerArgs[" "]
|
||||||
|
direction LR
|
||||||
|
LoggerInitializer("<strong>LoggerInitializer</strong><br>LoggerInitializer")
|
||||||
|
PrimaryInitializer("<strong>PrimaryInitializer</strong><br>ProcessHandler")
|
||||||
|
WorkerInitializer("<strong>WorkerInitializer</strong><br>ProcessHandler")
|
||||||
|
end
|
||||||
|
|
||||||
|
LoggerInitializer --> PrimaryInitializer
|
||||||
|
PrimaryInitializer --> WorkerInitializer
|
||||||
|
```
|
||||||
|
|
||||||
|
The very first thing that needs to happen is initializing the logger.
|
||||||
|
Before this other classes will be unable to use logging.
|
||||||
|
|
||||||
|
The `PrimaryInitializer` will only trigger once, in the primary worker thread,
|
||||||
|
while the `WorkerInitializer` will trigger for every worker thread.
|
||||||
|
Although if your server setup is single-threaded, which is the default,
|
||||||
|
there is no relevant difference between those two.
|
||||||
|
|
||||||
|
### PrimaryInitializer
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
PrimaryInitializer("<strong>PrimaryInitializer</strong><br>ProcessHandler")
|
||||||
|
PrimaryInitializer --> PrimarySequenceInitializer("<strong>PrimarySequenceInitializer</strong><br>SequenceHandler")
|
||||||
|
PrimarySequenceInitializer --> PrimarySequenceInitializerArgs
|
||||||
|
|
||||||
|
subgraph PrimarySequenceInitializerArgs[" "]
|
||||||
|
direction LR
|
||||||
|
CleanupInitializer("<strong>CleanupInitializer</strong><br>SequenceHandler")
|
||||||
|
PrimaryParallelInitializer("<strong>PrimaryParallelInitializer</strong><br>ParallelHandler")
|
||||||
|
WorkerManager("<strong>WorkerManager</strong><br>WorkerManager")
|
||||||
|
end
|
||||||
|
|
||||||
|
CleanupInitializer --> PrimaryParallelInitializer
|
||||||
|
PrimaryParallelInitializer --> WorkerManager
|
||||||
|
```
|
||||||
|
The above is a simplification of all the initializers that are present in the `PrimaryInitializer`
|
||||||
|
as there are several smaller initializers that also trigger but are less relevant here.
|
||||||
|
|
||||||
|
The `CleanupInitializer` is an initializer that cleans up anything
|
||||||
|
that might have remained from a previous server start
|
||||||
|
and could impact behaviour.
|
||||||
|
Relevant components in other parts of the configuration are responsible for adding themselves to this array if needed.
|
||||||
|
An example of this is file-based locking components which might need to remove any dangling locking files.
|
||||||
|
|
||||||
|
The `PrimaryParallelInitializer` can be used to add any initializers to that have to happen in the primary process.
|
||||||
|
This makes it easier for users to add initializers by being able to append to its handlers.
|
||||||
|
|
||||||
|
The `WorkerManager` is responsible for setting up the worker threads, if any.
|
||||||
|
|
||||||
|
### WorkerInitializer
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
WorkerInitializer("<strong>WorkerInitializer</strong><br>ProcessHandler")
|
||||||
|
WorkerInitializer --> WorkerSequenceInitializer("<strong>WorkerSequenceInitializer</strong><br>SequenceHandler")
|
||||||
|
WorkerSequenceInitializer --> WorkerSequenceInitializerArgs
|
||||||
|
|
||||||
|
subgraph WorkerSequenceInitializerArgs[" "]
|
||||||
|
direction LR
|
||||||
|
WorkerParallelInitializer("<strong>WorkerParallelInitializer</strong><br>ParallelHandler")
|
||||||
|
ServerInitializer("<strong>ServerInitializer</strong><br>ServerInitializer")
|
||||||
|
end
|
||||||
|
|
||||||
|
WorkerParallelInitializer --> ServerInitializer
|
||||||
|
```
|
||||||
|
The `WorkerInitializer` is quite similar to the `PrimaryInitializer` but triggers once per worker thread.
|
||||||
|
Like the `PrimaryParallelInitializer`, the `WorkerParallelInitializer` can be used
|
||||||
|
to add any custom initializers that need to run.
|
||||||
|
|
||||||
|
### ServerInitializer
|
||||||
|
The `ServerInitializer` is the initializer that finally starts up the server by listening to the relevant port,
|
||||||
|
once all the initialization described above is finished.
|
||||||
|
This is an example of a component that differs based on some of the choices made during configuration.
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
ServerInitializer("<strong>ServerInitializer</strong><br>ServerInitializer")
|
||||||
|
ServerInitializer --> WebSocketServerFactory("<strong>ServerFactory</strong><br>WebSocketServerFactory")
|
||||||
|
WebSocketServerFactory --> BaseHttpServerFactory("<br>BaseHttpServerFactory")
|
||||||
|
BaseHttpServerFactory --> HttpHandler("<strong>HttpHandler</strong><br><i>HttpHandler</i>")
|
||||||
|
|
||||||
|
ServerInitializer2("<strong>ServerInitializer</strong><br>ServerInitializer")
|
||||||
|
ServerInitializer2 ---> BaseHttpServerFactory2("<strong>ServerFactory</strong><br>BaseHttpServerFactory")
|
||||||
|
BaseHttpServerFactory2 --> HttpHandler2("<strong>HttpHandler</strong><br><i>HttpHandler</i>")
|
||||||
|
```
|
||||||
|
|
||||||
|
Depending on if the configurations necessary for websockets are imported or not,
|
||||||
|
the `urn:solid-server:default:ServerFactory` identifier will point to a different resource.
|
||||||
|
There will always be a `BaseHttpServerFactory` that starts the HTTP(S) server,
|
||||||
|
but there might also be a `WebSocketServerFactory` wrapped around it to handle websocket support.
|
||||||
|
Although not indicated here, the parameters for initializing the `BaseHttpServerFactory`
|
||||||
|
might also differ in case an HTTPS configuration is imported.
|
||||||
|
|
||||||
|
The `HttpHandler` it takes as input is responsible for how [HTTP requests get resolved](http-handler.md).
|
@ -0,0 +1,163 @@
|
|||||||
|
# Authorization
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
AuthorizingHttpHandler("<br>AuthorizingHttpHandler")
|
||||||
|
AuthorizingHttpHandler --> AuthorizingHttpHandlerArgs
|
||||||
|
|
||||||
|
subgraph AuthorizingHttpHandlerArgs[" "]
|
||||||
|
CredentialsExtractor("<strong>CredentialsExtractor</strong><br><i>CredentialsExtractor</i>")
|
||||||
|
ModesExtractor("<strong>ModesExtractor</strong><br><i>ModesExtractor</i>")
|
||||||
|
PermissionReader("<strong>PermissionReader</strong><br><i>PermissionReader</i>")
|
||||||
|
Authorizer("<strong>Authorizer</strong><br>PermissionBasedAuthorizer")
|
||||||
|
OperationHttpHandler("<br><i>OperationHttpHandler</i>")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Authorization is usually handled by the `AuthorizingHttpHandler`,
|
||||||
|
which receives a parsed HTTP request in the form of an `Operation`.
|
||||||
|
It goes through the following steps:
|
||||||
|
|
||||||
|
1. A `CredentialsExtractor` identifies the credentials of the agent making the call.
|
||||||
|
2. A `ModesExtractor` finds which access modes are needed for which resources.
|
||||||
|
3. A `PermissionReader` determines the permissions the agent has on the targeted resources.
|
||||||
|
4. The above results are compared in an `Authorizer`.
|
||||||
|
5. If the request is allowed, call the `OperationHttpHandler`, otherwise throw an error.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
There are multiple `CredentialsExtractor`s that each determine identity in a different way.
|
||||||
|
Potentially multiple extractors can apply,
|
||||||
|
making a requesting agent have multiple credentials.
|
||||||
|
|
||||||
|
The diagram below shows the default configuration if authentication is enabled.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
CredentialsExtractor("<strong>CredentialsExtractor</strong><br>UnionCredentialsExtractor")
|
||||||
|
CredentialsExtractor --> CredentialsExtractorArgs
|
||||||
|
|
||||||
|
subgraph CredentialsExtractorArgs[" "]
|
||||||
|
WaterfallHandler("<br>WaterfallHandler")
|
||||||
|
PublicCredentialsExtractor("<br>PublicCredentialsExtractor")
|
||||||
|
end
|
||||||
|
|
||||||
|
WaterfallHandler --> WaterfallHandlerArgs
|
||||||
|
subgraph WaterfallHandlerArgs[" "]
|
||||||
|
direction LR
|
||||||
|
DPoPWebIdExtractor("<br>DPoPWebIdExtractor") --> BearerWebIdExtractor("<br>BearerWebIdExtractor")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Both of the WebID extractors make use of
|
||||||
|
the (`access-token-verifier`)[https://github.com/CommunitySolidServer/access-token-verifier] library
|
||||||
|
to parse incoming tokens based on the [Solid-OIDC specification](https://solid.github.io/solid-oidc/).
|
||||||
|
Besides those there are always the public credentials, which everyone has.
|
||||||
|
All these credentials then get combined into a single union object.
|
||||||
|
|
||||||
|
If successful, a `CredentialsExtractor` will return a key/value map
|
||||||
|
linking the type of credentials to their specific values.
|
||||||
|
|
||||||
|
There are also debug configuration options available that can be used to simulate credentials.
|
||||||
|
These can be enabled as different options through the `config/ldp/authentication` imports.
|
||||||
|
|
||||||
|
## Modes extraction
|
||||||
|
Access modes are a predefined list of `read`, `write`, `append`, `create` and `delete`.
|
||||||
|
The `ModesExtractor` determine which modes will be necessary and for which resources,
|
||||||
|
based on the request contents.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
ModesExtractor("<strong>ModesExtractor</strong><br>IntermediateCreateExtractor")
|
||||||
|
ModesExtractor --> HttpModesExtractor("<strong>HttpModesExtractor</strong><br>WaterfallHandler")
|
||||||
|
|
||||||
|
HttpModesExtractor --> HttpModesExtractorArgs
|
||||||
|
|
||||||
|
subgraph HttpModesExtractorArgs[" "]
|
||||||
|
direction LR
|
||||||
|
PatchModesExtractor("<strong>PatchModesExtractor</strong><br><i>ModesExtractor</i>") --> MethodModesExtractor("<br>MethodModesExtractor")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The `IntermediateCreateExtractor` is responsible if requests try to create intermediate containers with a single request.
|
||||||
|
E.g., a PUT request to `/foo/bar/baz` should create both the `/foo/` and `/foo/bar/` containers in case they do not exist yet.
|
||||||
|
This extractor makes sure that `create` permissions are also checked on those containers.
|
||||||
|
|
||||||
|
Modes can usually be determined based on just the HTTP methods,
|
||||||
|
which is what the `MethodModesExtractor` does.
|
||||||
|
A GET request will always need the `read` mode for example.
|
||||||
|
|
||||||
|
The only exception are PATCH requests,
|
||||||
|
where the necessary modes depend on the body and the PATCH type.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
PatchModesExtractor("<strong>PatchModesExtractor</strong><br>WaterfallHandler") --> PatchModesExtractorArgs
|
||||||
|
subgraph PatchModesExtractorArgs[" "]
|
||||||
|
N3PatchModesExtractor("<br>N3PatchModesExtractor")
|
||||||
|
SparqlUpdateModesExtractor("<br>SparqlUpdateModesExtractor")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The server supports both N3 Patch and SPARQL Update PATCH requests.
|
||||||
|
In both cases it will parse the bodies to determine what the impact would be of the request and what modes it requires.
|
||||||
|
|
||||||
|
## Permission reading
|
||||||
|
`PermissionReaders` take the input of the above to determine which permissions are available for which credentials.
|
||||||
|
The modes from the previous step are not yet needed,
|
||||||
|
but can be used as optimization as we only need to know if we have permission on those modes.
|
||||||
|
Each reader returns all the information it can find based on the resources and modes it receives.
|
||||||
|
In the default configuration the following readers are combined when WebACL is enabled as authorization method.
|
||||||
|
In case authorization is disabled by changing the authorization import to `config/ldp/authorization/allow-all.json`,
|
||||||
|
this diagram is just a class that always returns all permissions.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
PermissionReader("<strong>PermissionReader</strong><br>AuxiliaryReader")
|
||||||
|
PermissionReader --> UnionPermissionReader("<br>UnionPermissionReader")
|
||||||
|
UnionPermissionReader --> UnionPermissionReaderArgs
|
||||||
|
|
||||||
|
subgraph UnionPermissionReaderArgs[" "]
|
||||||
|
PathBasedReader("<strong>PathBasedReader</strong><br>PathBasedReader")
|
||||||
|
OwnerPermissionReader("<strong>OwnerPermissionReader</strong><br>OwnerPermissionReader")
|
||||||
|
WrappedWebAclReader("<strong>WrappedWebAclReader</strong><br>ParentContainerReader")
|
||||||
|
end
|
||||||
|
|
||||||
|
WrappedWebAclReader --> WebAclAuxiliaryReader("<strong>WebAclAuxiliaryReader</strong><br>WebAclAuxiliaryReader")
|
||||||
|
WebAclAuxiliaryReader --> WebAclReader("<strong>WebAclReader</strong><br>WebAclReader")
|
||||||
|
```
|
||||||
|
|
||||||
|
The first thing that happens is that if the target is an auxiliary resource that uses the authorization of its subject resource,
|
||||||
|
the `AuxiliaryReader` inserts that identifier instead.
|
||||||
|
An example of this is if the requests targets the metadata of a resource.
|
||||||
|
|
||||||
|
The `UnionPermissionReader` then combines the results of its readers into a single permission object.
|
||||||
|
If one reader rejects a specific mode and another allows it, the rejection takes priority.
|
||||||
|
|
||||||
|
The `PathBasedReader` rejects all permissions for certain paths.
|
||||||
|
This is used to prevent access to the internal data of the server.
|
||||||
|
|
||||||
|
The `OwnerPermissionReader` makes sure owners always have control access
|
||||||
|
to the [pods they created on the server](../../../../usage/identity-provider/#pod).
|
||||||
|
Users will always be able to modify the ACL resources in their pod,
|
||||||
|
even if they accidentally removed their own access.
|
||||||
|
|
||||||
|
The final readers are specifically relevant for the WebACL algorithm.
|
||||||
|
The `ParentContainerReader` checks the permissions on a parent resource if required:
|
||||||
|
creating a resource requires `append` permissions on the parent container,
|
||||||
|
while deleting a resource requires `write` permissions there.
|
||||||
|
|
||||||
|
In case the target is an ACL resource, `control` permissions need to be checked,
|
||||||
|
no matter what mode was generated by the `ModesExtractor`.
|
||||||
|
The `WebAclAuxiliaryReader` makes sure this conversion happens.
|
||||||
|
|
||||||
|
Finally, the `WebAclReader` implements
|
||||||
|
the [efffective ACL resource algorithm](https://solidproject.org/TR/2021/wac-20210711#effective-acl-resource)
|
||||||
|
and returns the permissions it finds in that resource.
|
||||||
|
In case no ACL resource is found this indicates a configuration error and no permissions will be granted.
|
||||||
|
|
||||||
|
## Authorization
|
||||||
|
All the results of the previous steps then get combined in the `PermissionBasedAuthorizer` to either allow or reject a request.
|
||||||
|
If no permissions are found for a requested mode,
|
||||||
|
or they are explicitly forbidden,
|
||||||
|
a 401/403 will be returned,
|
||||||
|
depending on if the agent was logged in or not.
|
@ -0,0 +1,30 @@
|
|||||||
|
# Solid protocol
|
||||||
|
The `LdpHandler`, named as a reference to the Linked Data Platform specification,
|
||||||
|
chains several handlers together, each with their own specific purpose, to fully resolve the HTTP request.
|
||||||
|
It specifically handles Solid requests as described
|
||||||
|
in the protocol [specification](https://solidproject.org/TR/protocol),
|
||||||
|
e.g. a POST request to create a new resource.
|
||||||
|
|
||||||
|
Below is a simplified view of how these handlers are linked.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
LdpHandler("<strong>LdpHandler</strong><br>ParsingHttphandler")
|
||||||
|
LdpHandler --> AuthorizingHttpHandler("<br>AuthorizingHttpHandler")
|
||||||
|
AuthorizingHttpHandler --> OperationHandler("<strong>OperationHandler</strong><br><i>OperationHandler</i>")
|
||||||
|
OperationHandler --> ResourceStore("<strong>ResourceStore</strong><br><i>ResourceStore</i>")
|
||||||
|
```
|
||||||
|
|
||||||
|
A standard request would go through the following steps:
|
||||||
|
|
||||||
|
1. The `ParsingHttphandler` parses the HTTP request into a manageable format, both body and metadata such as headers.
|
||||||
|
2. The `AuthorizingHttpHandler` verifies if the request is authorized to access the targeted resource.
|
||||||
|
3. The `OperationHandler` determines which action is required based on the HTTP method.
|
||||||
|
4. The `ResourceStore` does all the relevant data work.
|
||||||
|
5. The `ParsingHttphandler` eventually receives the response data, or an error, and handles the output.
|
||||||
|
|
||||||
|
Below are sections that go deeper into the specific steps.
|
||||||
|
|
||||||
|
* [How input gets parsed and output gets returned](parsing.md)
|
||||||
|
* [How authentication and authorization work](authorization.md)
|
||||||
|
* [What the `ResourceStore` looks like](resource-store.md)
|
102
documentation/markdown/architecture/features/protocol/parsing.md
Normal file
102
documentation/markdown/architecture/features/protocol/parsing.md
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# Parsing and responding to HTTP requests
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
ParsingHttphandler("<br>ParsingHttphandler")
|
||||||
|
ParsingHttphandler --> ParsingHttphandlerArgs
|
||||||
|
|
||||||
|
subgraph ParsingHttphandlerArgs[" "]
|
||||||
|
RequestParser("<strong>RequestParser</strong><br>BasicRequestParser")
|
||||||
|
AuthorizingHttpHandler("<strong></strong><br>AuthorizingHttpHandler")
|
||||||
|
ErrorHandler("<strong>ErrorHandler</strong><br><i>ErrorHandler</i>")
|
||||||
|
ResponseWriter("<strong>ResponseWriter</strong><br>BasicResponseWriter")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
A `ParsingHttpHandler` handles both the parsing of the input data, and the serializing of the output data.
|
||||||
|
It follows these 3 steps:
|
||||||
|
|
||||||
|
1. Use the `RequestParser` to convert the incoming data into an `Operation`.
|
||||||
|
2. Send the `Operation` to the `AuthorizingHttpHandler` to receive either a `Representation` if the operation was a success,
|
||||||
|
or an `Error` in case something went wrong.
|
||||||
|
* In case of an error the `ErrorHandler` will convert the `Error` into a `ResponseDescription`.
|
||||||
|
3. Use the `ResponseWriter` to output the `ResponseDescription` as an HTTP response.
|
||||||
|
|
||||||
|
## Parsing the request
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
RequestParser("<strong>RequestParser</strong><br>BasicRequestParser") --> RequestParserArgs
|
||||||
|
subgraph RequestParserArgs[" "]
|
||||||
|
TargetExtractor("<strong>TargetExtractor</strong><br>OriginalUrlExtractor")
|
||||||
|
PreferenceParser("<strong>PreferenceParser</strong><br>AcceptPreferenceParser")
|
||||||
|
MetadataParser("<strong>MetadataParser</strong><br><i>MetadataParser</i>")
|
||||||
|
BodyParser("<br><i>Bodyparser</i>")
|
||||||
|
Conditions("<br>BasicConditionsParser")
|
||||||
|
end
|
||||||
|
|
||||||
|
OriginalUrlExtractor --> IdentifierStrategy("<strong>IdentifierStrategy</strong><br><i>IdentifierStrategy</i>")
|
||||||
|
```
|
||||||
|
The `BasicRequestParser` is mostly an aggregator of multiple smaller parsers that each handle a very specific part.
|
||||||
|
|
||||||
|
### URL
|
||||||
|
This is a single class, the `OriginalUrlExtractor`, but fulfills the very important role
|
||||||
|
of making sure input URLs are handled consistently.
|
||||||
|
|
||||||
|
The query parameters will always be completely removed from the URL.
|
||||||
|
|
||||||
|
There is also an algorithm to make sure all URLs have a "canonical" version as for example both `&` and `%26`
|
||||||
|
can be interpreted in the same way.
|
||||||
|
Specifically all special characters will be encoded into their percent encoding.
|
||||||
|
|
||||||
|
The `IdentifierStrategy` it gets as input is used to determine if the resulting URL is within the scope of the server.
|
||||||
|
This can differ depending on if the server uses subdomains or not.
|
||||||
|
|
||||||
|
The resulting identifier will be stored in the `target` field of an `Operation` object.
|
||||||
|
|
||||||
|
### Preferences
|
||||||
|
The `AcceptPreferenceParser` parses the `Accept` header and all the relevant `Accept-*` headers.
|
||||||
|
These will all be put into the `preferences` field of an `Operation` object.
|
||||||
|
These will later be used to handle the content negotiation.
|
||||||
|
|
||||||
|
For example, when sending an `Accept: text/turtle; q=0.9` header,
|
||||||
|
this wil result in the preferences object `{ type: { 'text/turtle': 0.9 } }`.
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
Several other headers can have relevant metadata,
|
||||||
|
such as the `Content-Type` header,
|
||||||
|
or the `Link: <http://www.w3.org/ns/ldp#Container>; rel="type"` header
|
||||||
|
which is used to indicate to the server that a request intends to create a container.
|
||||||
|
|
||||||
|
Such headers are converted to RDF triples and stored in the `RepresentationMetadata` object,
|
||||||
|
which will be part of the `body` field in the `Operation`.
|
||||||
|
|
||||||
|
The default `MetadataParser` is a `ParallelHandler` that contains several smaller parsers,
|
||||||
|
each looking at a specific header.
|
||||||
|
|
||||||
|
### Body
|
||||||
|
In case of most requests, the input data stream is used directly in the `body` field of the `Operation`,
|
||||||
|
with a few minor checks to make sure the HTTP specification is being followed.
|
||||||
|
|
||||||
|
In the case of PATCH requests though,
|
||||||
|
there are several specific body parsers that will convert the request
|
||||||
|
into a JavaScript object containing all the necessary information to execute such a PATCH.
|
||||||
|
Several validation checks will already take place there as well.
|
||||||
|
|
||||||
|
### Conditions
|
||||||
|
The `BasicConditionsParser` parses everything related to conditions headers,
|
||||||
|
such as `if-none-match` or `if-modified-since`,
|
||||||
|
and stores the relevant information in the `conditions` field of the `Operation`.
|
||||||
|
These will later be used to make sure the request should be aborted or not.
|
||||||
|
|
||||||
|
## Sending the response
|
||||||
|
In case a request is successful, the `AuthorizingHttpHandler` will return a `ResponseDescription`,
|
||||||
|
and if not it will throw an error.
|
||||||
|
|
||||||
|
In case an error gets thrown, this will be caught by the `ErrorHandler` and converted into a `ResponseDescription`.
|
||||||
|
The request preferences will be used to make sure the serialization is one that is preferred.
|
||||||
|
|
||||||
|
Either way we will have a `ResponseDescription`,
|
||||||
|
which will be sent to the `BasicResponseWriter` to convert into output headers, data and a status code.
|
||||||
|
|
||||||
|
To convert the metadata into headers, it uses a `MetadataWriter`,
|
||||||
|
which functions as the reverse of the `MetadataParser` mentioned above:
|
||||||
|
it has multiple writers which each convert certain metadata into a specific header.
|
@ -1,6 +1,4 @@
|
|||||||
# Resource store
|
# Resource store
|
||||||
Once an LDP request passes authorization, it will be passed to the `ResourceStore`.
|
|
||||||
|
|
||||||
The interface of a `ResourceStore` is mostly a 1-to-1 mapping of the HTTP methods:
|
The interface of a `ResourceStore` is mostly a 1-to-1 mapping of the HTTP methods:
|
||||||
|
|
||||||
* GET: `getRepresentation`
|
* GET: `getRepresentation`
|
62
documentation/markdown/architecture/overview.md
Normal file
62
documentation/markdown/architecture/overview.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Architecture overview
|
||||||
|
|
||||||
|
The initial architecture document the project was started from can be found
|
||||||
|
[here](https://rubenverborgh.github.io/solid-server-architecture/solid-architecture-v1-3-0.pdf).
|
||||||
|
Many things have been added since the original inception of the project,
|
||||||
|
but the core ideas within that document are still valid.
|
||||||
|
|
||||||
|
As can be seen from the architecture, an important idea is the modularity of all components.
|
||||||
|
No actual implementations are defined there, only their interfaces.
|
||||||
|
Making all the components independent of each other in such a way provides us with an enormous flexibility:
|
||||||
|
they can all be replaced by a different implementation, without impacting anything else.
|
||||||
|
This is how we can provide many different configurations for the server,
|
||||||
|
and why it is impossible to provide ready solutions for all possible combinations.
|
||||||
|
|
||||||
|
## Architecture diagrams
|
||||||
|
|
||||||
|
Having a modular architecture makes it more difficult to give a complete architecture overview.
|
||||||
|
We will limit ourselves to the more commonly used default configurations we provide,
|
||||||
|
and in certain cases we might give examples of what differences there are
|
||||||
|
based on what configurations are being imported.
|
||||||
|
|
||||||
|
To do this we will make use of architecture diagrams.
|
||||||
|
We will use an example below to explain the formatting used throughout the architecture documentation:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
LdpHandler("<strong>LdpHandler</strong><br>ParsingHttphandler")
|
||||||
|
LdpHandler --> LdpHandlerArgs
|
||||||
|
|
||||||
|
subgraph LdpHandlerArgs[" "]
|
||||||
|
RequestParser("<strong>RequestParser</strong><br>BasicRequestParser")
|
||||||
|
Auth("<br>AuthorizingHttpHandler")
|
||||||
|
ErrorHandler("<strong>ErrorHandler</strong><br><i>ErrorHandler</>")
|
||||||
|
ResponseWriter("<strong>ResponseWriter</strong><br>BasicResponseWriter")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Below is a summary of how to interpret such diagrams:
|
||||||
|
|
||||||
|
* Rounded red box: component instantiated in the Components.js [configuration](dependency-injection.md).
|
||||||
|
* First line:
|
||||||
|
* **Bold text**: shorthand of the instance identifier. In case the full URI is not specified,
|
||||||
|
it can usually be found by prepending `urn:solid-server:default:` to the shorthand identifier.
|
||||||
|
* (empty): this instance has no identifier and is defined in the same place as its parent.
|
||||||
|
* Second line:
|
||||||
|
* Regular text: The class of this instance.
|
||||||
|
* _Italic text_: The interface of this instance.
|
||||||
|
Will be used if the actual class is not relevant for the explanation or can differ.
|
||||||
|
* Square grey box: the parameters of the linked instance.
|
||||||
|
* Arrow: links an instance to its parameters. Can also be used to indicate the order of parameters if relevant.
|
||||||
|
|
||||||
|
For example, in the above, **LdpHandler** is a shorthand for the actual identifier `urn:solid-server:default:LdpHandler`
|
||||||
|
and is an instance of `ParsingHttpHandler`. It has 4 parameters,
|
||||||
|
one of which has no identifier but is an instance of `AuthorizingHttpHandler`.
|
||||||
|
|
||||||
|
# Features
|
||||||
|
Below are the sections that go deeper into the features of the server and how those work.
|
||||||
|
|
||||||
|
* [How Command Line Arguments are parsed and used](features/cli.md)
|
||||||
|
* [How the server is initialized and started](features/initialization.md)
|
||||||
|
* [How HTTP requests are handled](features/http-handler.md)
|
||||||
|
* [How the server handles a standard Solid request](features/protocol/overview.md)
|
@ -14,6 +14,7 @@ The links here assume the server is hosted at `http://localhost:3000/`.
|
|||||||
To register an account, you can go to `http://localhost:3000/idp/register/` if this feature is enabled,
|
To register an account, you can go to `http://localhost:3000/idp/register/` if this feature is enabled,
|
||||||
which it is on all configurations we provide.
|
which it is on all configurations we provide.
|
||||||
Currently our registration page ties 3 features together on the same page:
|
Currently our registration page ties 3 features together on the same page:
|
||||||
|
|
||||||
* Creating an account on the server.
|
* Creating an account on the server.
|
||||||
* Creating or linking a WebID to your account.
|
* Creating or linking a WebID to your account.
|
||||||
* Creating a pod on the server.
|
* Creating a pod on the server.
|
||||||
|
@ -54,7 +54,12 @@ markdown_extensions:
|
|||||||
- pymdownx.highlight
|
- pymdownx.highlight
|
||||||
- pymdownx.superfences
|
- pymdownx.superfences
|
||||||
- pymdownx.smartsymbols
|
- pymdownx.smartsymbols
|
||||||
|
- pymdownx.superfences:
|
||||||
|
custom_fences:
|
||||||
|
# need to fork the theme to make changes https://github.com/squidfunk/mkdocs-material/issues/3665#issuecomment-1060019924
|
||||||
|
- name: mermaid
|
||||||
|
class: mermaid
|
||||||
|
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||||
|
|
||||||
extra:
|
extra:
|
||||||
version:
|
version:
|
||||||
@ -79,11 +84,18 @@ nav:
|
|||||||
- Client credentials: usage/client-credentials.md
|
- Client credentials: usage/client-credentials.md
|
||||||
- Seeding pods: usage/seeding-pods.md
|
- Seeding pods: usage/seeding-pods.md
|
||||||
- Architecture:
|
- Architecture:
|
||||||
- Architecture: architecture/architecture.md
|
- Overview: architecture/overview.md
|
||||||
- Dependency injection: architecture/dependency-injection.md
|
- Dependency injection: architecture/dependency-injection.md
|
||||||
|
- Core: architecture/core.md
|
||||||
- Features:
|
- Features:
|
||||||
- Authorization: architecture/features/authorization.md
|
- Command line arguments: architecture/features/cli.md
|
||||||
- Resource Store: architecture/features/resource-store.md
|
- Server initialization: architecture/features/initialization.md
|
||||||
|
- HTTP requests: architecture/features/http-handler.md
|
||||||
|
- Solid protocol:
|
||||||
|
- Overview: architecture/features/protocol/overview.md
|
||||||
|
- Parsing: architecture/features/protocol/parsing.md
|
||||||
|
- Authorization: architecture/features/protocol/authorization.md
|
||||||
|
- Resource Store: architecture/features/protocol/resource-store.md
|
||||||
- Contributing:
|
- Contributing:
|
||||||
- Pull requests: contributing/making-changes.md
|
- Pull requests: contributing/making-changes.md
|
||||||
- Releases: contributing/release.md
|
- Releases: contributing/release.md
|
||||||
@ -91,3 +103,4 @@ nav:
|
|||||||
|
|
||||||
# To write documentation locally, execute the next line and browse to http://localhost:8000
|
# To write documentation locally, execute the next line and browse to http://localhost:8000
|
||||||
# docker run --rm -it -p 8000:8000 -v ${PWD}/documentation:/docs squidfunk/mkdocs-material
|
# docker run --rm -it -p 8000:8000 -v ${PWD}/documentation:/docs squidfunk/mkdocs-material
|
||||||
|
# Alternatively, install `mkdocs` and `mkdocs-material` using `pip`, browse to the documentation folder and run `mkdocs serve`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user