[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 = "*"
attrs = "*"
cryptography = "*"
flasgger = "*"
[dev-packages]
mypy = "*"

View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "cd4a56afb09ac033e44f2b8c075a8103f5ee27b31b766508441e34539f654ea1"
"sha256": "7ce3de9caf3a9fcc47859dc03ad9e09db96185bd6be89480c7264ce71f6e80ca"
},
"pipfile-spec": 6,
"requires": {
@ -185,7 +185,7 @@
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
"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"
},
"ecdsa": {
@ -204,6 +204,14 @@
"index": "pypi",
"version": "==0.33.2"
},
"flasgger": {
"hashes": [
"sha256:0603941cf4003626b4ee551ca87331f1d17b8eecce500ccf1a1f1d3a332fc94a",
"sha256:6ebea406b5beecd77e8da42550f380d4d05a6107bc90b69ce9e77aee7612e2d0"
],
"index": "pypi",
"version": "==0.9.5"
},
"flask": {
"hashes": [
"sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b",
@ -310,6 +318,14 @@
"markers": "python_version < '3.10'",
"version": "==5.1.0"
},
"importlib-resources": {
"hashes": [
"sha256:32bb095bda29741f6ef0e5278c42df98d135391bee5f932841efc0041f748dc3",
"sha256:c09b067d82e72c66f4f8eb12332f5efbebc9b007c0b6c40818108c9870adc363"
],
"markers": "python_version < '3.9'",
"version": "==5.10.1"
},
"itsdangerous": {
"hashes": [
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
@ -326,6 +342,14 @@
"markers": "python_version >= '3.7'",
"version": "==3.1.2"
},
"jsonschema": {
"hashes": [
"sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d",
"sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"
],
"markers": "python_version >= '3.7'",
"version": "==4.17.3"
},
"markupsafe": {
"hashes": [
"sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
@ -380,6 +404,13 @@
"index": "pypi",
"version": "==7.1.12"
},
"mistune": {
"hashes": [
"sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d",
"sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808"
],
"version": "==2.0.4"
},
"mysql-connector-python": {
"hashes": [
"sha256:02526f16eacc3961ff681c5c8455d2306a9b45124f2f012ca75a1eac9ceb5165",
@ -479,6 +510,14 @@
"index": "pypi",
"version": "==9.3.0"
},
"pkgutil-resolve-name": {
"hashes": [
"sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174",
"sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"
],
"markers": "python_version < '3.9'",
"version": "==1.3.10"
},
"protobuf": {
"hashes": [
"sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf",
@ -553,6 +592,34 @@
],
"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": {
"hashes": [
"sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c",
@ -651,7 +718,7 @@
"sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7",
"sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"
],
"markers": "python_version >= '3.6' and python_version < '4'",
"markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==4.9"
},
"schema": {

View File

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

View File

@ -21,10 +21,13 @@
import copy
import json
import logging as log
import os
import traceback
from operator import itemgetter
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional
from flasgger import Swagger
from flasgger.utils import swag_from
from flask import request
if TYPE_CHECKING:
@ -66,8 +69,34 @@ ERR_412 = (
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
@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
def ddapi_users() -> OptionalJsonResponse:
try:
@ -84,7 +113,10 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return ERR_500
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
def ddapi_users_search() -> OptionalJsonResponse:
try:
@ -110,7 +142,8 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return ERR_500
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
def ddapi_groups() -> OptionalJsonResponse:
try:
@ -127,7 +160,8 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return ERR_500
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
def ddapi_roles() -> OptionalJsonResponse:
try:
@ -141,7 +175,8 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return ERR_500
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
def ddapi_role_users() -> OptionalJsonResponse:
try:
@ -174,8 +209,16 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
return None
# INDIVIDUAL ACTIONS
@app.json_route("/ddapi/user", methods=["POST"])
@app.json_route("/ddapi/user/<user_ddid>", methods=["PUT", "GET", "DELETE"])
@app.json_route("/ddapi/user", methods=["POST"], endpoint="api_user_new")
@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
def ddapi_user(user_ddid: Optional[str] = None) -> OptionalJsonResponse:
try:
@ -284,8 +327,18 @@ def setup_api_views(app: "AdminFlaskApp") -> None:
log.error(traceback.format_exc())
return ERR_500
@app.json_route("/ddapi/group", methods=["POST"])
@app.json_route("/ddapi/group/<group_id>", methods=["GET", "POST", "DELETE"])
@app.json_route("/ddapi/group", methods=["POST"], endpoint="api_group_new")
@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"])
@has_token
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")
# print(rv)
# 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
> vostra revisar els canvis al tema `dd` i al directori `dd-custom.sample`
> 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:
- search
#- enumerate-headings
- render_swagger
- i18n:
languages:
ca: "Català"