[dd-sso] Add API documentation

The API spec file can be generated with:

python -m admin.views.test.test_ApiViews --generate-spec

From the admin development environment.

A simple testing ground that serves the Swagger UI can also be started with:

python -m admin.views.test.test_ApiViews
merge-requests/55/head
Evilham 2022-12-11 19:02:27 +01:00
parent 10e6afe351
commit d37b4dfa6a
No known key found for this signature in database
GPG Key ID: AE3EE30D970886BF
21 changed files with 1174 additions and 12 deletions

View File

@ -21,6 +21,7 @@ requests = "*"
python-keycloak = "*" python-keycloak = "*"
attrs = "*" attrs = "*"
cryptography = "*" cryptography = "*"
flasgger = "*"
[dev-packages] [dev-packages]
mypy = "*" mypy = "*"

View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "cd4a56afb09ac033e44f2b8c075a8103f5ee27b31b766508441e34539f654ea1" "sha256": "7ce3de9caf3a9fcc47859dc03ad9e09db96185bd6be89480c7264ce71f6e80ca"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -185,7 +185,7 @@
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
], ],
"markers": "python_version >= '3.6' and python_version < '4'", "markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==2.2.1" "version": "==2.2.1"
}, },
"ecdsa": { "ecdsa": {
@ -204,6 +204,14 @@
"index": "pypi", "index": "pypi",
"version": "==0.33.2" "version": "==0.33.2"
}, },
"flasgger": {
"hashes": [
"sha256:0603941cf4003626b4ee551ca87331f1d17b8eecce500ccf1a1f1d3a332fc94a",
"sha256:6ebea406b5beecd77e8da42550f380d4d05a6107bc90b69ce9e77aee7612e2d0"
],
"index": "pypi",
"version": "==0.9.5"
},
"flask": { "flask": {
"hashes": [ "hashes": [
"sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b",
@ -310,6 +318,14 @@
"markers": "python_version < '3.10'", "markers": "python_version < '3.10'",
"version": "==5.1.0" "version": "==5.1.0"
}, },
"importlib-resources": {
"hashes": [
"sha256:32bb095bda29741f6ef0e5278c42df98d135391bee5f932841efc0041f748dc3",
"sha256:c09b067d82e72c66f4f8eb12332f5efbebc9b007c0b6c40818108c9870adc363"
],
"markers": "python_version < '3.9'",
"version": "==5.10.1"
},
"itsdangerous": { "itsdangerous": {
"hashes": [ "hashes": [
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
@ -326,6 +342,14 @@
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==3.1.2" "version": "==3.1.2"
}, },
"jsonschema": {
"hashes": [
"sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d",
"sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"
],
"markers": "python_version >= '3.7'",
"version": "==4.17.3"
},
"markupsafe": { "markupsafe": {
"hashes": [ "hashes": [
"sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
@ -380,6 +404,13 @@
"index": "pypi", "index": "pypi",
"version": "==7.1.12" "version": "==7.1.12"
}, },
"mistune": {
"hashes": [
"sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d",
"sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808"
],
"version": "==2.0.4"
},
"mysql-connector-python": { "mysql-connector-python": {
"hashes": [ "hashes": [
"sha256:02526f16eacc3961ff681c5c8455d2306a9b45124f2f012ca75a1eac9ceb5165", "sha256:02526f16eacc3961ff681c5c8455d2306a9b45124f2f012ca75a1eac9ceb5165",
@ -479,6 +510,14 @@
"index": "pypi", "index": "pypi",
"version": "==9.3.0" "version": "==9.3.0"
}, },
"pkgutil-resolve-name": {
"hashes": [
"sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174",
"sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"
],
"markers": "python_version < '3.9'",
"version": "==1.3.10"
},
"protobuf": { "protobuf": {
"hashes": [ "hashes": [
"sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf", "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf",
@ -553,6 +592,34 @@
], ],
"version": "==2.21" "version": "==2.21"
}, },
"pyrsistent": {
"hashes": [
"sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed",
"sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb",
"sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a",
"sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95",
"sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712",
"sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73",
"sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41",
"sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b",
"sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78",
"sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab",
"sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308",
"sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425",
"sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2",
"sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e",
"sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6",
"sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2",
"sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a",
"sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291",
"sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584",
"sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a",
"sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0",
"sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770"
],
"markers": "python_version >= '3.7'",
"version": "==0.19.2"
},
"python-engineio": { "python-engineio": {
"hashes": [ "hashes": [
"sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c", "sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c",
@ -651,7 +718,7 @@
"sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7",
"sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"
], ],
"markers": "python_version >= '3.6' and python_version < '4'", "markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==4.9" "version": "==4.9"
}, },
"schema": { "schema": {

View File

@ -23,6 +23,7 @@ Flask==2.1.3
Flask-Login==0.6.2 Flask-Login==0.6.2
eventlet==0.33.1 eventlet==0.33.1
Flask-SocketIO==5.2.0 Flask-SocketIO==5.2.0
flasgger==0.9.5
bcrypt==3.2.2 bcrypt==3.2.2
# diceware can't be upgraded without issues # diceware can't be upgraded without issues
diceware==0.9.6 diceware==0.9.6

View File

@ -21,10 +21,13 @@
import copy import copy
import json import json
import logging as log import logging as log
import os
import traceback import traceback
from operator import itemgetter from operator import itemgetter
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional
from flasgger import Swagger
from flasgger.utils import swag_from
from flask import request from flask import request
if TYPE_CHECKING: if TYPE_CHECKING:
@ -66,8 +69,34 @@ ERR_412 = (
def setup_api_views(app: "AdminFlaskApp") -> None: def setup_api_views(app: "AdminFlaskApp") -> None:
swagger = Swagger(
app,
template={
"info": {
"title": "DD API",
"description": "DD API for external integrations",
"version": "2022.11.0",
"termsOfService": "",
},
"externalDocs": {
"description": "Online Documentation",
"url": "https://dd.digitalitzacio-democratica.xnet-x.net/docs/",
},
"securityDefinitions": {
"dd_jwt": {
"type": "apiKey",
"in": "header",
"name": "Authorization",
"description": "JWS token using API_SECRET (e.g. 'bearer X.Y')",
}
},
"security": {"dd_jwt": {"$ref": "#/securityDefinitions/dd_jwt"}},
"swagger_ui": bool(os.environ.get("SWAGGER_UI", "")),
},
)
# LISTS # LISTS
@app.json_route("/ddapi/users", methods=["GET"]) @app.json_route("/ddapi/users", methods=["GET"], endpoint="api_users")
@swag_from("api_docs/users.yml", endpoint="api_users")
@has_token @has_token
def ddapi_users() -> OptionalJsonResponse: def ddapi_users() -> OptionalJsonResponse:
try: try:
@ -84,7 +113,10 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return ERR_500 return ERR_500
return None return None
@app.json_route("/ddapi/users/filter", methods=["POST"]) @app.json_route(
"/ddapi/users/filter", methods=["POST"], endpoint="api_users_filter"
)
@swag_from("api_docs/users_filter.yml", endpoint="api_users_filter")
@has_token @has_token
def ddapi_users_search() -> OptionalJsonResponse: def ddapi_users_search() -> OptionalJsonResponse:
try: try:
@ -110,7 +142,8 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return ERR_500 return ERR_500
return None return None
@app.json_route("/ddapi/groups", methods=["GET"]) @app.json_route("/ddapi/groups", methods=["GET"], endpoint="api_groups")
@swag_from("api_docs/groups.yml", endpoint="api_groups")
@has_token @has_token
def ddapi_groups() -> OptionalJsonResponse: def ddapi_groups() -> OptionalJsonResponse:
try: try:
@ -127,7 +160,8 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return ERR_500 return ERR_500
return None return None
@app.json_route("/ddapi/roles", methods=["GET"]) @app.json_route("/ddapi/roles", methods=["GET"], endpoint="api_roles")
@swag_from("api_docs/roles.yml", endpoint="api_roles")
@has_token @has_token
def ddapi_roles() -> OptionalJsonResponse: def ddapi_roles() -> OptionalJsonResponse:
try: try:
@ -141,7 +175,8 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return ERR_500 return ERR_500
return None return None
@app.json_route("/ddapi/role/users", methods=["POST"]) @app.json_route("/ddapi/role/users", methods=["POST"], endpoint="api_role_users")
@swag_from("api_docs/role_users.yml", endpoint="api_role_users")
@has_token @has_token
def ddapi_role_users() -> OptionalJsonResponse: def ddapi_role_users() -> OptionalJsonResponse:
try: try:
@ -174,8 +209,16 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return None return None
# INDIVIDUAL ACTIONS # INDIVIDUAL ACTIONS
@app.json_route("/ddapi/user", methods=["POST"]) @app.json_route("/ddapi/user", methods=["POST"], endpoint="api_user_new")
@app.json_route("/ddapi/user/<user_ddid>", methods=["PUT", "GET", "DELETE"]) @app.json_route(
"/ddapi/user/<user_ddid>",
methods=["PUT", "GET", "DELETE"],
endpoint="api_user_ddid",
)
@swag_from("api_docs/user_new.yml", endpoint="api_user_new")
@swag_from("api_docs/user_get.yml", endpoint="api_user_ddid", methods=["GET"])
@swag_from("api_docs/user_put.yml", endpoint="api_user_ddid", methods=["PUT"])
@swag_from("api_docs/user_delete.yml", endpoint="api_user_ddid", methods=["DELETE"])
@has_token @has_token
def ddapi_user(user_ddid: Optional[str] = None) -> OptionalJsonResponse: def ddapi_user(user_ddid: Optional[str] = None) -> OptionalJsonResponse:
try: try:
@ -284,8 +327,18 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
log.error(traceback.format_exc()) log.error(traceback.format_exc())
return ERR_500 return ERR_500
@app.json_route("/ddapi/group", methods=["POST"]) @app.json_route("/ddapi/group", methods=["POST"], endpoint="api_group_new")
@app.json_route("/ddapi/group/<group_id>", methods=["GET", "POST", "DELETE"]) @app.json_route(
"/ddapi/group/<group_id>",
methods=["GET", "POST", "DELETE"],
endpoint="api_group_group_id",
)
@swag_from("api_docs/group_new.yml", endpoint="api_group_new")
@swag_from("api_docs/group_get.yml", endpoint="api_group_group_id", methods=["GET"])
# @swag_from('api_docs/group_put.yml', endpoint='api_group_group_id', methods=["PUT"])
@swag_from(
"api_docs/group_delete.yml", endpoint="api_group_group_id", methods=["DELETE"]
)
# @app.json_route("/api/group/<group_id>", methods=["PUT", "GET", "DELETE"]) # @app.json_route("/api/group/<group_id>", methods=["PUT", "GET", "DELETE"])
@has_token @has_token
def ddapi_group(group_id: Optional[str] = None) -> OptionalJsonResponse: def ddapi_group(group_id: Optional[str] = None) -> OptionalJsonResponse:

View File

@ -0,0 +1,18 @@
Delete a registered group in DD
---
consumes:
- application/json
parameters:
- in: url
name: group_id
description: |
The group to delete from DD
schema:
type: string
responses:
200:
schema:
type: object
404:
description: |
The group does not exist

View File

@ -0,0 +1,20 @@
Get a registered group in DD
---
consumes:
- application/json
parameters:
- in: url
name: group_id
description: |
The group to retrieve from DD
schema:
type: string
responses:
200:
description: |
The group as it exists on DD
schema:
$ref: '#/definitions/Group'
404:
description: |
The group does not exist

View File

@ -0,0 +1,30 @@
Register a new group in DD
---
consumes:
- application/json
parameters:
- in: body
name: group
description: |
The group to be registered on DD.
schema:
type: object
properties:
name:
required: True
type: string
description:
required: False
type: string
parent:
required: False
type: string
responses:
200:
description: |
The keycloak_id of the newly registered group
schema:
$ref: '#/definitions/KeycloakId'
409:
description: |
The group already exists

View File

@ -0,0 +1,34 @@
List all registered groups on DD.
---
definitions:
Group:
type: object
properties:
keycloak_id:
type: string
format: uuid
id:
type: string
name:
type: string
path:
type: string
description:
type: string
Groups:
type: array
items:
$ref: '#/definitions/Group'
responses:
200:
description: The list of groups registered on DD
schema:
$ref: '#/definitions/Groups'
examples: |
[{
"keycloak_id": "f6ec2bda-bec9-415f-bcb7-f5ae644bfec5",
"id": "ID",
"name": "NAME",
"path": "PATH",
"description": "DESCRIPITON",
}]

View File

@ -0,0 +1,31 @@
List registered users on DD with a the given role.
---
consumes:
- application/json
parameters:
- in: body
name: role
description: |
The role to search users registered on DD.
One of 'id', 'name' and 'keycloak_id' must be provided.
This is also the order in which the parameters are checked,
in case multiple are provided.
schema:
type: object
properties:
id:
type: string
required: False
name:
type: string
required: False
keycloak_id:
type: string
format: uuid
required: False
responses:
200:
description: |
The list of users registered on DD with the filter applied.
schema:
$ref: '#/definitions/Users'

View File

@ -0,0 +1,31 @@
List all roles configured on DD.
---
definitions:
Role:
type: object
properties:
keycloak_id:
type: string
format: uuid
id:
type: string
name:
type: string
description:
type: string
Roles:
type: array
items:
$ref: '#/definitions/Role'
responses:
200:
description: The list of roles configured on DD
schema:
$ref: '#/definitions/Roles'
examples: |
[{
"keycloak_id": "f6ec2bda-bec9-415f-bcb7-f5ae644bfec5",
"id": "ID",
"name": "NAME",
"description": "DESCRIPITON",
}]

View File

@ -0,0 +1,18 @@
Delete a registered user in DD
---
consumes:
- application/json
parameters:
- in: url
name: user_ddid
description: |
The user to delete from DD
schema:
type: string
responses:
200:
schema:
type: object
404:
description: |
The user does not exist

View File

@ -0,0 +1,20 @@
Get a registered user in DD
---
consumes:
- application/json
parameters:
- in: url
name: user_ddid
description: |
The user to retrieve from DD
schema:
type: string
responses:
200:
description: |
The user as it exists on DD
schema:
$ref: '#/definitions/User'
404:
description: |
The user does not exist

View File

@ -0,0 +1,63 @@
Register a new user in DD
---
definitions:
KeycloakId:
type: object
properties:
keycloak_id:
required: True
type: string
consumes:
- application/json
parameters:
- in: body
name: user
description: |
The user to be registered on DD.
schema:
type: object
properties:
username:
required: True
type: string
first:
required: True
type: string
last:
required: True
type: string
email:
required: True
type: string
format: email
password:
required: True
type: string
format: email
password_temporary:
required: False
type: bool
quota:
required: True
type: string
enabled:
required: True
type: bool
role:
required: True
groups:
required: True
type: array
items:
type: string
responses:
200:
description: |
The keycloak_id of the newly registered user
schema:
$ref: '#/definitions/KeycloakId'
examples: |
{ "keycloak_id": "f6ec2bda-bec9-415f-bcb7-f5ae644bfec5" }
409:
description: |
The user already exists

View File

@ -0,0 +1,49 @@
Modify a user in DD
---
consumes:
- application/json
parameters:
- in: body
name: user
description: |
The user to be modified on DD.
schema:
type: object
properties:
first:
required: False
type: string
last:
required: False
type: string
email:
required: False
type: string
format: email
password:
required: False
type: string
format: email
password_temporary:
required: False
type: bool
quota:
required: False
type: string
enabled:
required: False
type: bool
role:
required: False
groups:
required: False
type: array
items:
type: string
responses:
200:
schema:
type: object
404:
description: |
The user does not exist

View File

@ -0,0 +1,58 @@
List all registered users on DD.
---
definitions:
User:
type: object
properties:
keycloak_id:
type: string
format: uuid
id:
type: string
username:
type: string
enabled:
type: boolean
first:
type: string
last:
type: string
role:
type: string
email:
type: string
fomat: email
groups:
type: array
items:
type: string
quota:
type: string
quota_used_bytes:
type: string
Users:
type: array
items:
$ref: '#/definitions/User'
responses:
200:
description: The list of users registered on DD
schema:
$ref: '#/definitions/Users'
examples: |
[{
"keycloak_id": "a773d249-a113-4542-8101-3a50f4cd28c2",
"id": "ID",
"username": "ID",
"enabled": true,
"first": "NAME",
"last": "LASTNAME",
"role": "student",
"email": "ID@DOMAIN",
"groups": [
"GRUP",
"student"
],
"quota": "500 MB",
"quota_used_bytes": "0 MB"
}]

View File

@ -0,0 +1,21 @@
List registered users on DD with a filter applied.
---
consumes:
- application/json
parameters:
- in: body
name: filter
description: The filter to apply to users registered on DD
schema:
type: object
required:
- filter
properties:
text:
type: string
responses:
200:
description: |
The list of users registered on DD with the filter applied.
schema:
$ref: '#/definitions/Users'

View File

@ -553,3 +553,27 @@ class ApiViewsTests(flask_unittest.ClientTestCase):
# rv = self._r(client, "/ddapi/role/users", method="POST") # rv = self._r(client, "/ddapi/role/users", method="POST")
# print(rv) # print(rv)
# print(rv.json) # print(rv.json)
if __name__ == "__main__":
import sys
if "DOMAIN" not in os.environ:
os.environ["DOMAIN"] = "localhost"
os.environ["SWAGGER_UI"] = "TRUE"
app = _testApp()
if "--generate-spec" in sys.argv:
with app.app_context():
ep = app.swag.config["specs"][0]["endpoint"]
spec = app.swag.get_apispecs(ep)
import json
print(json.dumps(spec, indent=4))
else:
# Start a simple testing server
app.socketio.run(
app,
host=os.environ.get("ADMIN_LISTEN_ADDESS", "::"),
port=int(os.environ.get("ADMIN_LISTEN_PORT", "9000")),
debug=True,
)

View File

@ -77,3 +77,8 @@ Un cop fet això, a la interfície d'administració de Keycloak haurem de triar
> **Nota:** el directori dd-custom no s'actualitzarà mai, és responsabilitat > **Nota:** el directori dd-custom no s'actualitzarà mai, és responsabilitat
> vostra revisar els canvis al tema `dd` i al directori `dd-custom.sample` > vostra revisar els canvis al tema `dd` i al directori `dd-custom.sample`
> per tal de mantenir la compatibilitat amb els vostres canvis. > per tal de mantenir la compatibilitat amb els vostres canvis.
## Integració amb altres eines
És possible integrar el DD amb altres eines, vegeu la secció
d'[integracions](integrations.ca.md).

554
docs/ddapi.json Normal file
View File

@ -0,0 +1,554 @@
{
"info": {
"title": "DD API",
"description": "DD API for external integrations",
"version": "2022.11.0",
"termsOfService": ""
},
"paths": {
"/ddapi/users": {
"get": {
"summary": "List all registered users on DD.",
"responses": {
"200": {
"description": "The list of users registered on DD",
"schema": {
"$ref": "#/definitions/Users"
},
"examples": "[{\n \"keycloak_id\": \"a773d249-a113-4542-8101-3a50f4cd28c2\",\n \"id\": \"ID\",\n \"username\": \"ID\",\n \"enabled\": true,\n \"first\": \"NAME\",\n \"last\": \"LASTNAME\",\n \"role\": \"student\",\n \"email\": \"ID@DOMAIN\",\n \"groups\": [\n \"GRUP\",\n \"student\"\n ],\n \"quota\": \"500 MB\",\n \"quota_used_bytes\": \"0 MB\"\n}]\n"
}
}
}
},
"/ddapi/users/filter": {
"post": {
"summary": "List registered users on DD with a filter applied.",
"responses": {
"200": {
"description": "The list of users registered on DD with the filter applied.\n",
"schema": {
"$ref": "#/definitions/Users"
}
}
},
"parameters": [
{
"in": "body",
"name": "filter",
"description": "The filter to apply to users registered on DD",
"schema": {
"type": "object",
"required": [
"filter"
],
"properties": {
"text": {
"type": "string"
}
}
}
}
],
"consumes": [
"application/json"
]
}
},
"/ddapi/groups": {
"get": {
"summary": "List all registered groups on DD.",
"responses": {
"200": {
"description": "The list of groups registered on DD",
"schema": {
"$ref": "#/definitions/Groups"
},
"examples": "[{\n \"keycloak_id\": \"f6ec2bda-bec9-415f-bcb7-f5ae644bfec5\",\n \"id\": \"ID\",\n \"name\": \"NAME\",\n \"path\": \"PATH\",\n \"description\": \"DESCRIPITON\",\n}]\n"
}
}
}
},
"/ddapi/roles": {
"get": {
"summary": "List all roles configured on DD.",
"responses": {
"200": {
"description": "The list of roles configured on DD",
"schema": {
"$ref": "#/definitions/Roles"
},
"examples": "[{\n \"keycloak_id\": \"f6ec2bda-bec9-415f-bcb7-f5ae644bfec5\",\n \"id\": \"ID\",\n \"name\": \"NAME\",\n \"description\": \"DESCRIPITON\",\n}]\n"
}
}
}
},
"/ddapi/role/users": {
"post": {
"summary": "List registered users on DD with a the given role.",
"responses": {
"200": {
"description": "The list of users registered on DD with the filter applied.\n",
"schema": {
"$ref": "#/definitions/Users"
}
}
},
"parameters": [
{
"in": "body",
"name": "role",
"description": "The role to search users registered on DD.\nOne of 'id', 'name' and 'keycloak_id' must be provided.\nThis is also the order in which the parameters are checked,\nin case multiple are provided.\n",
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"required": false
},
"name": {
"type": "string",
"required": false
},
"keycloak_id": {
"type": "string",
"format": "uuid",
"required": false
}
}
}
}
],
"consumes": [
"application/json"
]
}
},
"/ddapi/user/{user_ddid}": {
"get": {
"summary": "Get a registered user in DD",
"responses": {
"200": {
"description": "The user as it exists on DD\n",
"schema": {
"$ref": "#/definitions/User"
}
},
"404": {
"description": "The user does not exist\n"
}
},
"parameters": [
{
"in": "url",
"name": "user_ddid",
"description": "The user to retrieve from DD\n",
"schema": {
"type": "string"
}
}
],
"consumes": [
"application/json"
]
},
"delete": {
"summary": "Delete a registered user in DD",
"responses": {
"200": {
"schema": {
"type": "object"
}
},
"404": {
"description": "The user does not exist\n"
}
},
"parameters": [
{
"in": "url",
"name": "user_ddid",
"description": "The user to delete from DD\n",
"schema": {
"type": "string"
}
}
],
"consumes": [
"application/json"
]
},
"put": {
"summary": "Modify a user in DD",
"responses": {
"200": {
"schema": {
"type": "object"
}
},
"404": {
"description": "The user does not exist\n"
}
},
"parameters": [
{
"in": "body",
"name": "user",
"description": "The user to be modified on DD.\n",
"schema": {
"type": "object",
"properties": {
"first": {
"required": false,
"type": "string"
},
"last": {
"required": false,
"type": "string"
},
"email": {
"required": false,
"type": "string",
"format": "email"
},
"password": {
"required": false,
"type": "string",
"format": "email"
},
"password_temporary": {
"required": false,
"type": "bool"
},
"quota": {
"required": false,
"type": "string"
},
"enabled": {
"required": false,
"type": "bool"
},
"role": {
"required": false
},
"groups": {
"required": false,
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
],
"consumes": [
"application/json"
]
}
},
"/ddapi/user": {
"post": {
"summary": "Register a new user in DD",
"responses": {
"200": {
"description": "The keycloak_id of the newly registered user\n",
"schema": {
"$ref": "#/definitions/KeycloakId"
},
"examples": "{ \"keycloak_id\": \"f6ec2bda-bec9-415f-bcb7-f5ae644bfec5\" }\n"
},
"409": {
"description": "The user already exists\n"
}
},
"parameters": [
{
"in": "body",
"name": "user",
"description": "The user to be registered on DD.\n",
"schema": {
"type": "object",
"properties": {
"username": {
"required": true,
"type": "string"
},
"first": {
"required": true,
"type": "string"
},
"last": {
"required": true,
"type": "string"
},
"email": {
"required": true,
"type": "string",
"format": "email"
},
"password": {
"required": true,
"type": "string",
"format": "email"
},
"password_temporary": {
"required": false,
"type": "bool"
},
"quota": {
"required": true,
"type": "string"
},
"enabled": {
"required": true,
"type": "bool"
},
"role": {
"required": true
},
"groups": {
"required": true,
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
],
"consumes": [
"application/json"
]
}
},
"/ddapi/group/{group_id}": {
"get": {
"summary": "Get a registered group in DD",
"responses": {
"200": {
"description": "The group as it exists on DD\n",
"schema": {
"$ref": "#/definitions/Group"
}
},
"404": {
"description": "The group does not exist\n"
}
},
"parameters": [
{
"in": "url",
"name": "group_id",
"description": "The group to retrieve from DD\n",
"schema": {
"type": "string"
}
}
],
"consumes": [
"application/json"
]
},
"delete": {
"summary": "Delete a registered group in DD",
"responses": {
"200": {
"schema": {
"type": "object"
}
},
"404": {
"description": "The group does not exist\n"
}
},
"parameters": [
{
"in": "url",
"name": "group_id",
"description": "The group to delete from DD\n",
"schema": {
"type": "string"
}
}
],
"consumes": [
"application/json"
]
}
},
"/ddapi/group": {
"post": {
"summary": "Register a new group in DD",
"responses": {
"200": {
"description": "The keycloak_id of the newly registered group\n",
"schema": {
"$ref": "#/definitions/KeycloakId"
}
},
"409": {
"description": "The group already exists\n"
}
},
"parameters": [
{
"in": "body",
"name": "group",
"description": "The group to be registered on DD.\n",
"schema": {
"type": "object",
"properties": {
"name": {
"required": true,
"type": "string"
},
"description": {
"required": false,
"type": "string"
},
"parent": {
"required": false,
"type": "string"
}
}
}
}
],
"consumes": [
"application/json"
]
}
}
},
"definitions": {
"User": {
"type": "object",
"properties": {
"keycloak_id": {
"type": "string",
"format": "uuid"
},
"id": {
"type": "string"
},
"username": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"first": {
"type": "string"
},
"last": {
"type": "string"
},
"role": {
"type": "string"
},
"email": {
"type": "string",
"fomat": "email"
},
"groups": {
"type": "array",
"items": {
"type": "string"
}
},
"quota": {
"type": "string"
},
"quota_used_bytes": {
"type": "string"
}
}
},
"Users": {
"type": "array",
"items": {
"$ref": "#/definitions/User"
}
},
"Group": {
"type": "object",
"properties": {
"keycloak_id": {
"type": "string",
"format": "uuid"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"description": {
"type": "string"
}
}
},
"Groups": {
"type": "array",
"items": {
"$ref": "#/definitions/Group"
}
},
"Role": {
"type": "object",
"properties": {
"keycloak_id": {
"type": "string",
"format": "uuid"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
}
}
},
"Roles": {
"type": "array",
"items": {
"$ref": "#/definitions/Role"
}
},
"KeycloakId": {
"type": "object",
"properties": {
"keycloak_id": {
"required": true,
"type": "string"
}
}
}
},
"swagger": "2.0",
"externalDocs": {
"description": "Online Documentation",
"url": "https://dd.digitalitzacio-democratica.xnet-x.net/docs/"
},
"securityDefinitions": {
"dd_jwt": {
"type": "apiKey",
"in": "header",
"name": "Authorization",
"description": "JWS token using API_SECRET (e.g. 'bearer X.Y')"
}
},
"security": {
"dd_jwt": {
"$ref": "#/securityDefinitions/dd_jwt"
}
},
"swagger_ui": true
}

63
docs/integrations.ca.md Normal file
View File

@ -0,0 +1,63 @@
# Integracions
El DD es pot integrar amb altres sistemes a través de les seves APIs.
## Autenticació
Totes les peticions han d'estar autenticades amb un [Json Web Token (JWT)][jwt],
que estigui signat per l'`API_SECRET` (present al fitxer `dd.conf`).
Aquesta autenticació es fa mitjançant la capcelera HTTP `Authentication`.
<details>
<summary>Vegeu-ne els detalls</summary>
```sh
> curl -H "Authorization: bearer ${jwt}" https://admin.DOMAIN/ddapi/roles
[
{
"keycloak_id": "9325ad99-7e04-4c31-9768-5512e1564160",
"id": "admin",
"name": "admin",
"description": "${role_admin}"
},
{
"keycloak_id": "c6c8a73e-51fc-4716-831d-1dfc0e0b62b0",
"id": "manager",
"name": "manager",
"description": "Realm managers"
},
{
"keycloak_id": "24d7977e-da83-4591-8e13-0fac3126afa1",
"id": "student",
"name": "student",
"description": "Realm students"
},
{
"keycloak_id": "d6699c41-13d5-4623-bdca-e5f2775474ed",
"id": "teacher",
"name": "teacher",
"description": "Realm teachers"
}
]
```
On el <code>JWT</code> es pot generar, per exemple fent servir <code>python-jose</code>, de la
següent manera:
```python
import os
from jose import jws
t = jws.sign({}, os.environ["API_SECRET"], algorithm="HS256")
print(t)
```
Altres llenguatges de programació i llibreries tindran una manera anàloga de
generar aquests tokens.
</details>
[jwt]: https://jwt.io/
[jose]: https://python-jose.readthedocs.io/
## API
!!swagger ddapi.json!!

View File

@ -42,6 +42,7 @@ markdown_extensions:
plugins: plugins:
- search - search
#- enumerate-headings #- enumerate-headings
- render_swagger
- i18n: - i18n:
languages: languages:
ca: "Català" ca: "Català"