Skip to content

Commit

Permalink
Merge pull request #1723 from folio-org/FOLIO-873-improve-docs-280
Browse files Browse the repository at this point in the history
FOLIO-873 improve docs
  • Loading branch information
dcrossleyau authored Aug 23, 2024
2 parents e6ca908 + b85f13d commit 14d1ba5
Show file tree
Hide file tree
Showing 5 changed files with 310 additions and 2 deletions.
2 changes: 2 additions & 0 deletions _data/remote-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"https://raw.githubusercontent.com/folio-org/mod-ldp/master/doc/running-in-dev.md",
"https://raw.githubusercontent.com/folio-org/mod-search/master/doc/browsing.md",
"https://raw.githubusercontent.com/folio-org/mod-search/master/doc/development.md",
"https://raw.githubusercontent.com/folio-org/mod-settings/master/README.md",
"https://raw.githubusercontent.com/folio-org/mod-settings/master/doc/porting-guide.md",
"https://raw.githubusercontent.com/folio-org/mod-settings/master/doc/HOWTO.md",
"https://raw.githubusercontent.com/folio-org/mod-spring-template/master/README.md",
"https://raw.githubusercontent.com/folio-org/okapi/master/doc/guide.md",
"https://raw.githubusercontent.com/folio-org/okapi/master/doc/security.md",
Expand Down
224 changes: 224 additions & 0 deletions _remote/mod-settings/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
---
layout: null
---

# mod-settings

Copyright (C) 2022-2023 The Open Library Foundation

This software is distributed under the terms of the Apache License,
Version 2.0. See the file "[LICENSE](LICENSE)" for more information.

## Introduction

mod-settings is a key-value store that provides a more secure
configuration system than the old mod-configuration.

See [Fixing the security problem in mod-configuration](https://github.com/MikeTaylor/folio-docs/blob/main/doc/fixing-mod-configuration.md)
for more information.

It is currently implemented with PostgresQL as storage.

A setting consists of these required properties

* `id`: unique identifier (UUID)
* `scope`: it is a namespace for the setting. This could be module
* `key`: a readable identifier; not necessarily for display
* `value`: any type (integer, string, object, array)

And optionally:

* `userId`: the owner of the setting

We call settings without userId 'global'. Global settings must be unique
for scope and key. Non-global settings must be unique for scope, key
and userId.

Settings are protected by permissions.

In order to write to a global setting, the client must have permission
`mod-settings.global.write.`scope, where scope is used in the setting.
To read this type of setting, the client must have
`mod-settings.global.read.`scope.

In order to write to a setting with any userId, which we call a user
setting, the client must have permission `mod-settings.users.write.`scope ,
where scope is used in the setting. To read this type of setting,
the client must have `mod-settings.users.read.`scope.

In order to write to user's own setting, the client must have permission
`mod-settings.owner.write.`scope, where scope is used in the setting.
To read this type of setting, the client must have
`mod-settings.owner.read.`scope.

With 'read' in this context we mean able to read the content of
the setting. With 'write' in this context we modify storage
using POST, PUT and DELETE.

We expect that most users will have read-write permission on their own
settings, and read-only permission on global entries.

The API is CRUD-like, but with some important changes for some.

Create a setting [with](https://s3.amazonaws.com/foliodocs/api/mod-settings/settings.html#operation/postSetting):

POST /settings/entries

This is write operation and returns 204 if successful. The
`id` property is required and must be supplied by the client.

Fetch a particular setting [with](https://s3.amazonaws.com/foliodocs/api/mod-settings/settings.html#operation/getSetting):

GET /settings/entries/{id}

This fetch is protected by permissions. If the client does not
have permission to read the setting with the scope or if the setting
does not exist, then mod-settings will return a 404 failure. It is a
deliberate choice to not distinguish between these two cases.

Get a list of settings [with](https://s3.amazonaws.com/foliodocs/api/mod-settings/settings.html#operation/getSettings):

GET /settings/entries

The latter takes optional `query`, `limit` (default 10), `offset` (default 0) parameters.
Query is expressed in Contextual Query Language
([CQL](https://dev.folio.org/reference/glossary/#cql))
and supports queries on the `id`, `scope`,
`key` and `userId` fields. Query terms can mostly only be used in
exact-value matching: the exception is that the key field supports
right-truncated searches, e.g. `scope=foo and key=bar*` to find all
entries in the `foo` scope that begin with `bar`.

The GET operations are "read" operations. The entries returned
are limited by client permissions.

Update a setting
[with](https://s3.amazonaws.com/foliodocs/api/mod-settings/settings.html#operation/putSetting):

PUT /settings/entries/{id}

This returns 204 if the setting was updated. This is strictly "write", i.e.
does not return the newly modified setting.

Delete a setting
[with](https://s3.amazonaws.com/foliodocs/api/mod-settings/settings.html#operation/deleteSetting):

DELETE /settings/entries/{id}

This is a write operation and returns 204 if the setting was deleted.

It's also possible to upload (or "import") settings
[with](https://s3.amazonaws.com/foliodocs/api/mod-settings/settings.html#operation/uploadSettings):

PUT /settings/upload

Settings are created/updated with this service. The provided settings must
not include an identifier. An identifier will be assigned by the server when
necessary.

## Compilation

Requirements:

* Java 17 or later
* Maven 3.6.3 or later
* Docker (unless `-DskipTests` is used)

Note: Debian package maven-3.6.3-1
[does not work with Java16/Java17](https://bugs.launchpad.net/ubuntu/+source/maven/+bug/1930541)


You need `JAVA_HOME` set, e.g.:

* Linux: `export JAVA_HOME=$(readlink -f /usr/bin/javac | sed "s:bin/javac::")`
* macOS: `export JAVA_HOME=$(/usr/libexec/java_home -v 17)`

Build all components with: `mvn install`

## Server

You will need Postgres 12 or later.

You can create an empty database and a user with, e.g:

```
CREATE DATABASE folio_modules;
CREATE USER folio WITH CREATEROLE PASSWORD 'folio';
GRANT ALL PRIVILEGES ON DATABASE folio_modules TO folio;
```

The module's database connection is then configured by setting environment
variables:
`DB_HOST`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD`, `DB_DATABASE`,
`DB_MAXPOOLSIZE`, `DB_SERVER_PEM`.

Once configured, start the module with:

```
java -Dport=8081 -jar target/mod-settings-fat.jar
```

## Running with Docker

If you feel adventurous and want to run mod-settings in a docker container, build the container first:

```
docker build -t mod-settings:latest .
```

And run with the module port exposed (`8081` by default):

```
docker run -e DB_HOST=host.docker.internal \
-e DB_USERNAME=folio \
-e DB_PASSWORD=folio \
-e DB_DATABASE=folio_modules \
-p 8081:8081 --name settings mod-settings:latest
```

**Note**: The magic host `host.docker.internal` is required to access
the DB and may be only available in Docker Desktop.
If it's not defined you can specify it by passing
`--add-host=host.docker.internal:<docker bridge net IP>` to the run command.

**Note**: Those docker build and run commands do work as-is with
[Colima](https://github.com/abiosoft/colima).

## Additional information

### Issue tracker

See project [MODSET](https://issues.folio.org/browse/MODSET)
at the [FOLIO issue tracker](https://dev.folio.org/guidelines/issue-tracker).

### Code of Conduct

Refer to the Wiki
[FOLIO Code of Conduct](https://wiki.folio.org/display/COMMUNITY/FOLIO+Code+of+Conduct).

### ModuleDescriptor

See the [ModuleDescriptor](descriptors/ModuleDescriptor-template.json)
for the interfaces that this module requires and provides, the permissions,
and the additional module metadata.

### API documentation

API descriptions:

* [OpenAPI](src/main/resources/openapi/settings.yaml)
* [Schemas](src/main/resources/openapi/schemas/)

Generated [API documentation](https://dev.folio.org/reference/api/#mod-settings).

### Code analysis

[SonarQube analysis](https://sonarcloud.io/dashboard?id=org.folio%3Amod-settings).

### Download and configuration

The built artifacts for this module are available.
See [configuration](https://dev.folio.org/download/artifacts) for repository access,
and the Docker images for [released versions](https://hub.docker.com/r/folioorg/mod-settings/)
and for [snapshot versions](https://hub.docker.com/r/folioci/mod-settings/).

82 changes: 82 additions & 0 deletions _remote/mod-settings/doc/HOWTO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
layout: null
---

# How to add new settings to the mod-settings module

Mike Taylor, Index Data. [email protected]


<!-- md2toc -l 2 HOWTO.md -->
* [Introduction](#introduction)
* [What to do](#what-to-do)
* [1. Define one or more scopes](#1-define-one-or-more-scopes)
* [2. Define relevant permissions for the scopes](#2-define-relevant-permissions-for-the-scopes)
* [3. Assign necessary permissions to users](#3-assign-necessary-permissions-to-users)
* [Summary](#summary)



## Introduction

When FOLIO client code (whether a UI module or a back-end module) wants to store settings, the simplest and most secure way to do this is in mod-settings. To do so requires establishing one or more scopes and creating some permissions. This document explains what is required of the client module.



## What to do


### 1. Define one or more scopes

Settings in the mod-settings module belong to a scope, which is named by a short string. Within a given scope, each setting has a unique key, which is also a short string.

There is no conept of defining a machine-readable object that is a scope -- for example, in a module descriptor. Instead, a scope exists when there are settings within that scope and permissions that provide access to them.

In order to avoid multiple modules defining scopes with the same names, it is conventional for scope names to begin with the name of the module that is defining them. For example, the `mod-inventory` module might define a scope called mod-inventory` which contains all the settings used by the Inventory app.

When all of a module's settings are within the same scope, it follows that a user who has permissions to access to any one setting has permission to access them all. It is almost always better for a module to define several scopes, using the module name as the first of multiple `.`-separated facets. For example, the `mod-inventory` module might define scopes `mod-inventory.admin` and `mod-inventory.prefs`, For adminstrative settings and preferences respectively, and assign permissions to allow only a few inventory users access to the administrative settings, but allow a broader group access to the pereferences.

In the example above, the `mod-inventory.admin` scope might contain settings with keys like `max-loans` and `max-holds` (which should only be set by administrators); and `mod-inventory.prefs` might contain settings with keys like `search-default-language` and `search-default-resource-type`.

Nothing prevents one module from naming a scope that properly belongs to another -- for example, `mod-inventory` naming the scope `mod-users.prefs`. Nothing, that is, but common sense and basic decency. **Do not do this.**

One a scope has been defined, it is laborious and error-prone to change its name, as it appears in permissions, in code, and in extant settings objects. So it is worth taking some time to think about a future-proof name for a scope when starting to use it. In particular, naming scopes _only_ after the modules that define them (e.g. the `mod-inventory` scope) is likely to lead to problems when it subsequently becomes necessary for the same module to define a second scope. So it is generally best to use a multi-facet scope name even when it is (initially) the only scope defined by a module.


### 2. Define relevant permissions for the scopes

A scope's only manifestation in a module descriptor is in the permissions that allow user to access keys in that scope. Typically two permissions are defined:

`mod-settings.global.read.SCOPE` allows a user to read settings in the named scope, and `mod-settings.global.write.SCOPE` allows a user to write settings in the named scope. For example, `mod-settings.global.read.mod-inventory.prefs` allows a user to read settings from the scope `mod-inventory.prefs`. The settings module itself enforces this requirement.

Note that, although these permissions are in the `mod-settings` namespace, they are defined by the client module (e.g. in the present example `mod-inventory`). This is a unique situation in FOLIO, required by the need for `mod-settings` to determine the name of the permission to check when all it knows is the scope and the operation (read or write).

(There are two more pairs of permissions that can be defined for a scope: read and write for "user", meaning a user-specific value of a setting; and for "self", meaning the current user's own user-specific value. These permissions are named `mod-settings.user.read.SCOPE`/`mod-settings.user.read.SCOPE` and `mod-settings.self.read.SCOPE`/`mod-settings.self.read.SCOPE`. These have not yet been used in real code, but are available when needed.)


### 3. Assign necessary permissions to users

Obviously, users must be assigned whatever permissions defined by the client module are needed for the settings access they are intended to have: in the example above, including `mod-settings.global.write.mod-inventory.prefs` to set preferences, or `mod-settings.global.read.mod-inventory.admin` to read the administrative settings.

In addition to these, every user must be assigned basic permissions to use the settings modules. These include:

* `mod-settings.entries.item.get` to fetch a known setting
* `mod-settings.entries.item.post` to create a new setting
* `mod-settings.entries.item.put` to update an existing setting
* `mod-settings.entries.item.delete` to delete an existing setting
* `mod-settings.entries.collection.get` to search available settings

(These are of course defined by `mod-settings` and do not need to be defined by the client module.)

In practice, fetching a known setting is rarely used, as the opaque ID is not usually known. Instead, users must be granted the `mod-settings.entries.collection.get` permission, which governs the `/settings/entries` entry point.This is generally used with a `query` parameter such as `scope=mod-inventory.admin and key=max-loans`.


## Summary

If we want a module `mod-foo` to support a domain `mod-foo.bar`, then:

* It will be necessary for this module to define the permissions `mod-settings.global.read` and `mod-settings.global.write`.
* Users who are to read settings in this scope will need the `mod-settings.global.read.mod-foo.bar` permission as well as `mod-settings.entries.collection.get` (and they may as well have `mod-settings.entries.item.get` too).
* Users who are to write settings in this scope will need the `mod-settings.global.write.mod-foo.bar` permission as well as `mod-settings.entries.item.post`, `mod-settings.entries.item.put` and `mod-settings.entries.item.delete`.


2 changes: 1 addition & 1 deletion guidelines/create-new-repo.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ See the folio-infrastructure lokalise-push [procedure](https://github.com/folio-

## Next steps

When a new module has been fully established and its artifacts are being deployed, follow the guides to [install](/faqs/how-to-install-new-module/) it to platform and reference environments for snapshot builds.
When the above-mentioned steps are completed, and when the new module has been fully established and its artifacts are being deployed, follow the guides to [install](/faqs/how-to-install-new-module/) it to platform and reference environments for snapshot builds.

<div class="folio-spacer-content"></div>

Expand Down
2 changes: 1 addition & 1 deletion guides/install-backend-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Note that this is not an exhaustive list.
* For Spring Way modules, the default port is 8080. FOLIO does not care which port. However if the module is going to use a different port, then be sure to also declare that `server.port` in its `application.yaml` file.
* For Spring Way modules, the replacement tokens in Descriptors use delimiters "@" rather than the normal "$".
* Ensure that the ModuleDescriptor is generated from its template and that tokens are replaced. For Maven-based modules, the POM will have configuration to "filter-descriptor-inputs" and "rename-descriptor-outputs".
* If the module uses the `_tenant` interface, then ensure that it is implemented and responding properly.
* Other ...

## Verify MD and required interfaces
Expand All @@ -44,7 +45,6 @@ curl -s -S -w'\n' \
That shows that it requires various interfaces, including "`users 15.1`" and "`configuration 2.0`".

Now ensure that each needed interface version is [available](/faqs/how-to-which-module-which-interface-endpoint/).
One method is to visit the [https://folio-snapshot.dev.folio.org/settings/about](https://folio-snapshot.dev.folio.org/settings/about) page.

## Ensure LaunchDescriptor

Expand Down

0 comments on commit 14d1ba5

Please sign in to comment.