Compare commits

...

18 Commits

Author SHA1 Message Date
424273360d verify audience in jwt 2020-01-30 15:46:18 +01:00
ae6dfb2644 add audience claim data to jwt 2020-01-24 15:07:30 +01:00
d78d581c65 implement basicauth and jwt token 2020-01-24 13:43:14 +01:00
52c878b0ab replace all zephir occurence 2020-01-23 14:05:07 +01:00
5666c01bdc update docker 2020-01-23 11:01:38 +01:00
3a9243bfb8 add docker environment 2020-01-20 15:58:04 +01:00
bcd17e1038 set docker 2020-01-20 11:34:16 +01:00
47d5ed77d5 set requirements 2020-01-17 15:59:48 +01:00
02c38589d4 add role server_rw + role example + deploy 2020-01-15 09:15:15 +01:00
cb2dbe135e add message 2020-01-14 20:05:28 +01:00
cb0e4b5d5d create risotto admin and set rights 2020-01-14 14:11:41 +01:00
722d4894a1 simplify 2020-01-13 19:53:09 +01:00
4020f97db0 insert log in database 2019-12-28 12:29:11 +01:00
a6383f0c2c check_role for event type message 2019-12-27 16:17:34 +01:00
1e223e7b57 add default uri roles 2019-12-27 16:07:29 +01:00
24e5f78668 new users are in 'all' role 2019-12-27 16:06:57 +01:00
5b1cae1567 create a permanent pool instead of 1 per use 2019-12-27 16:03:42 +01:00
1ed86e035b remove public information in message, it's remplace by role 2019-12-27 15:25:44 +01:00
112 changed files with 1638 additions and 600 deletions

View File

@ -20,13 +20,14 @@ docker run -d --add-host reload.example.com:127.0.0.1 -p 80:80 coudot/lemonldap-
Démarrer un serveur postgresql de test
```
podman pull docker.io/library/postgres:11-alpine
podman run -dt -p 5432:5432 postgres:11-alpine
docker run -dt -p 5432:5432 --name postgres postgres:11-alpine
docker exec -ti postgres bash
psql -U postgres -h localhost -c "CREATE ROLE risotto WITH LOGIN PASSWORD 'risotto';"
psql -U postgres -h localhost -c "CREATE DATABASE risotto;"
psql -U postgres -h localhost -c "GRANT ALL ON DATABASE risotto TO risotto;"
psql -U postgres -h localhost -c "CREATE EXTENSION hstore;" risotto
psql -U postgres -h localhost -c "CREATE EXTENSION hstore;"
psql -U postgres -h localhost -c "CREATE EXTENSION pgcrypto;"
```
Gestion de la base de données avec Sqitch
@ -35,3 +36,38 @@ Gestion de la base de données avec Sqitch
cpanm --quiet --notest App::Sqitch
sqitch init risotto --uri https://forge.cadoles.com/Infra/risotto --engine pg
```
Commande :
# Empty database:
su - postgres
psql -U postgres risotto
drop table log; drop table userrole; drop table release; drop table source; drop table server; drop table servermodel; drop table applicationservice; drop table roleuri; drop table risottouser; drop table uri;
# Import EOLE
./script/cucchiaiata source.create -n eole -u http://localhost
./script/cucchiaiata source.release.create -s eole -n 2.7.1.1 -d last
./script/cucchiaiata applicationservice.dataset.updated -s eole -r last
./script/cucchiaiata servermodel.dataset.updated -s eole -r last
# Create a server
./script/cucchiaiata server.create -s test -d description -m eolebase -n eole -r last
# Configure the server
./script/cucchiaiata session.server.start -s test
S=xxxxxxxxxxxxxxxxxxxxxx
./script/cucchiaiata session.server.configure -s $S --creole.reseau.unbound_ip_address_cidr 192.168.1.1/24 --creole.reseau.unbound_route_address 192.168.1.2 --creole.serveur_dns.unbound_allowed_client_cidr 192.168.1.0/24 --creole.serveur_dns.unbound_local_zones cadoles.com
./script/cucchiaiata session.server.configure -s $S --creole.reseau.unbound_domain_name test.cadoles.com
./script/cucchiaiata session.server.filter -s $S -n unbound
./script/cucchiaiata session.server.configure -s $S --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.hostname_cadoles_com toto titi --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.ip_cadoles_com 0 192.168.1.25 --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.type_cadoles_com 1 CNAME --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.cname_cadoles_com 1 toto.cadoles.com
./script/cucchiaiata session.server.validate -s $S
./script/cucchiaiata session.server.stop -s $S -a
# Generate configuration
./script/cucchiaiata config.configuration.server.deploy -s test
./script/cucchiaiata template.generate -s test
# Create a new user and set role 'server_rw' for this server
./script/cucchiaiata user.create -l gnunux -n gnunux -s gnunux
./script/cucchiaiata user.role.create -u gnunux -n 'server_rw' -a 'Server.ServerName' -v test

29
docker/Dockerfile Normal file
View File

@ -0,0 +1,29 @@
FROM python:3.7
# Requirements
ARG TIRAMISU_REPO_URL=https://framagit.org/tiramisu/tiramisu.git
ARG RISOTTO_REPO_URL=https://forge.cadoles.com/Infra/risotto.git
ARG ROUGAIL_REPO_URL=https://forge.cadoles.com/Infra/rougail.git
RUN apt-get update && apt-get install -y \
vim \
curl \
git \
jq \
&& apt-get clean
RUN git clone --branch develop ${TIRAMISU_REPO_URL} /srv/src/tiramisu
RUN git clone --branch docker ${RISOTTO_REPO_URL} /srv/src/risotto
RUN git clone --branch master ${ROUGAIL_REPO_URL} /srv/src/rougail
RUN ln -s /srv/src/tiramisu/tiramisu /usr/local/lib/python3.7
RUN ln -s /srv/src/rougail/src/rougail /usr/local/lib/python3.7
RUN ln -s /srv/src/risotto/src/risotto /usr/local/lib/python3.7
RUN pip install Cheetah3 PyJWT
RUN cd /srv/src/risotto && pip install -r requirements.txt
# Installation
RUN cp -r /srv/src/risotto/messages/ /usr/local/lib/
RUN mkdir -p /var/cache/risotto/servermodel
RUN mkdir -p /var/cache/risotto/database

14
docker/README.md Normal file
View File

@ -0,0 +1,14 @@
Docker
```
cd docker
docker build -t cadoles/risotto .
docker run -t -d --name risotto cadoles/risotto
docker exec -ti risotto bash
```
Docker-Compose
```
cd docker
docker-compose up
```

View File

@ -0,0 +1,29 @@
version: '2.2'
services:
risotto:
build:
context: ../
dockerfile: docker/Dockerfile
volumes:
- ../.:/srv/src/risotto
- ../messages:/usr/local/lib/messages
ports:
- "8080:8080"
depends_on:
- postgres
links:
- postgres
#command: tail -F /var/log
command: python -u /srv/src/risotto/script/server.py
restart: on-failure
postgres:
image: postgres:11-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
PGDATA: /data/postgres
volumes:
- ./postgres-init/:/docker-entrypoint-initdb.d/
ports:
- "5432:5432"
restart: unless-stopped

View File

@ -0,0 +1,105 @@
#!/bin/bash
set -e
psql --username "$POSTGRES_USER" <<-EOSQL
CREATE ROLE risotto WITH LOGIN PASSWORD 'risotto';
CREATE DATABASE risotto;
GRANT ALL ON DATABASE risotto TO risotto;
\c risotto
CREATE EXTENSION hstore;
CREATE EXTENSION pgcrypto;
EOSQL
psql --username "risotto" --password "risotto" <<-EOSQL
-- Création de la table Source
CREATE TABLE Source (
SourceId SERIAL PRIMARY KEY,
SourceName VARCHAR(255) NOT NULL UNIQUE,
SourceURL TEXT
);
-- Création de la table Release
CREATE TABLE Release (
ReleaseId SERIAL PRIMARY KEY,
ReleaseName VARCHAR(255) NOT NULL,
ReleaseSourceId INTEGER NOT NULL,
ReleaseDistribution VARCHAR(20) CONSTRAINT releasedistribution_choice CHECK (ReleaseDistribution IN ('last', 'n-1', 'n-2')),
UNIQUE (ReleaseName, ReleaseSourceId),
UNIQUE (ReleaseDistribution, ReleaseSourceId),
FOREIGN KEY (ReleaseSourceId) REFERENCES Source(SourceId)
);
-- Création de la table Servermodel
CREATE TABLE Servermodel (
ServermodelId SERIAL PRIMARY KEY,
ServermodelName VARCHAR(255) NOT NULL,
ServermodelDescription VARCHAR(255) NOT NULL,
ServermodelParentsId INTEGER [] DEFAULT '{}',
ServermodelReleaseId INTEGER NOT NULL,
ServermodelApplicationServiceId INTEGER NOT NULL,
ServermodelUsers hstore,
UNIQUE (ServermodelName, ServermodelReleaseId)
);
-- Création de la table ApplicationService
CREATE TABLE ApplicationService (
ApplicationServiceId SERIAL PRIMARY KEY,
ApplicationServiceName VARCHAR(255) NOT NULL,
ApplicationServiceDescription VARCHAR(255) NOT NULL,
ApplicationServiceReleaseId INTEGER NOT NULL,
ApplicationServiceDependencies JSON,
UNIQUE (ApplicationServiceName, ApplicationServiceReleaseId)
);
-- Server table creation
CREATE TABLE Server (
ServerId SERIAL PRIMARY KEY,
ServerName VARCHAR(255) NOT NULL UNIQUE,
ServerDescription VARCHAR(255) NOT NULL,
ServerServermodelId INTEGER NOT NULL
);
-- User, Role and ACL table creation
CREATE TABLE RisottoUser (
UserId SERIAL PRIMARY KEY,
UserLogin VARCHAR(100) NOT NULL UNIQUE,
UserPassword TEXT NOT NULL,
UserName VARCHAR(100) NOT NULL,
UserSurname VARCHAR(100) NOT NULL
);
CREATE TABLE UserRole (
RoleId SERIAL PRIMARY KEY,
RoleUserId INTEGER NOT NULL,
RoleName VARCHAR(255) NOT NULL,
RoleAttribute VARCHAR(255),
RoleAttributeValue VARCHAR(255),
FOREIGN KEY (RoleUserId) REFERENCES RisottoUser(UserId)
);
CREATE TABLE URI (
URIId SERIAL PRIMARY KEY,
URIName VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE RoleURI (
RoleName VARCHAR(255) NOT NULL,
URIId INTEGER NOT NULL,
FOREIGN KEY (URIId) REFERENCES URI(URIId),
PRIMARY KEY (RoleName, URIId)
);
-- Log table creation
CREATE TABLE log(
Msg VARCHAR(255) NOT NULL,
Level VARCHAR(10) NOT NULL,
Path VARCHAR(255),
Username VARCHAR(100) NOT NULL,
Data JSON,
Date timestamp DEFAULT current_timestamp
);
EOSQL

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
applicationservice_name:
type: String

View File

@ -5,8 +5,6 @@ description: |
pattern: rpc
public: true
parameters:
source_name:
type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
applicationservice_name:
type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
applicationservice_id:
type: Number

View File

@ -1,15 +1,17 @@
---
uri: config.configuration.server.deploy
description: |
Déployer la configuration d'un serveur.
description: Déployer la configuration d'un serveur.
pattern: event
public: false
pattern: rpc
parameters:
server_id:
type: Number
description: |
Identifiant du serveur.
server_name:
type: String
ref: Server.ServerName
shortarg: s
description: Nom du serveur.
response:
type: Deploy
description: La configuration du serveur est déployée.

View File

@ -6,14 +6,11 @@ description: |
pattern: rpc
public: false
parameters:
server_id:
type: Number
ref: Server.ServerId
description: |
Identifiant du serveur.
server_name:
type: String
ref: Server.ServerName
description: Nom du serveur.
deployed:
type: Boolean
description: Configuration de type déployée.

View File

@ -6,13 +6,16 @@ description: |
pattern: event
public: false
parameters:
server_id:
type: Number
description: |
Identifiant du serveur.
server_name:
type: String
ref: Server.ServerName
shortarg: s
description: Nom du serveur.
deployed:
type: Boolean
description: Configuration de type déployée.

View File

@ -5,7 +5,7 @@ description: |
Retourne des informations sur la session HTTP courante de l'utilisateur.
sampleuse: |
zephir-client identity.session-user.get
cucchiaiata identity.session-user.get
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne les préférences de l'utilisateur spécifié.
sampleuse: |
zephir-client identity.settings.get -u yo
cucchiaiata identity.settings.get -u yo
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Enregistre des préférences pour l'utilisateur spécifié.
sampleuse: |
zephir-client identity.settings.set -u yo
cucchiaiata identity.settings.set -u yo
pattern: rpc

View File

@ -11,7 +11,7 @@ pattern: rpc
domain: server-domain
sampleuse: |
zephir-client server.delete -s 1
cucchiaiata server.delete -s 1
parameters:
serverid:

View File

@ -5,7 +5,7 @@ description: |
Transmet une commande à exécuter sur un serveur donné.
sampleuse: |
zephir-client server.exec.command -s 1 -c reconfigure
cucchiaiata server.exec.command -s 1 -c reconfigure
domain: server-domain

View File

@ -5,7 +5,7 @@ description: |
Déploie la configuration sur un serveur donné.
sampleuse: |
zephir-client server.exec.deploy -s 1
cucchiaiata server.exec.deploy -s 1
domain: server-domain

View File

@ -5,7 +5,7 @@ description: |
Liste les commandes exécuté pour un identifiant de tâche.
sampleuse: |
zephir-client server.exec.list -j 1
cucchiaiata server.exec.list -j 1
domain: execution-domain

View File

@ -5,7 +5,7 @@ description: |
Liste les commandes exécutées sur un serveur donné.
sampleuse: |
zephir-client server.exec.list -s 1
cucchiaiata server.exec.list -s 1
domain: execution-domain

View File

@ -5,7 +5,7 @@ description: |
Retourne la liste des sélections de serveurs d'un serveur
sampleuse: |
zephir-client server.serverselection.list
cucchiaiata server.serverselection.list
pattern: rpc

View File

@ -7,7 +7,7 @@ description: |
public: true
sampleuse: |
zephir-client server.update -s 1 -n toto -d "server description"
cucchiaiata server.update -s 1 -n toto -d "server description"
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Associe un service applicatif à un modèle de serveur.
sampleuse: |
zephir-client servermodel.applicationservice.join -m 1 -s 1
cucchiaiata servermodel.applicationservice.join -m 1 -s 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne la liste des applications service.
sampleuse: |
zephir-client servermodel.applicationservice.list -s 6
cucchiaiata servermodel.applicationservice.list -s 6
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Crée un modèle de serveur.
sampleuse: |
zephir-client servermodel.create -p 1 -n "MonServeurModele" -d "Ma description" -s 1
cucchiaiata servermodel.create -p 1 -n "MonServeurModele" -d "Ma description" -s 1
pattern: rpc

View File

@ -1,32 +0,0 @@
---
uri: servermodel.describe
description: |
Retourne les attributs détaillés d'un modèle de serveur.
pattern: rpc
public: true
parameters:
servermodel_id:
type: Number
shortarg: s
description: Identifiant du modèle de serveur à récupérer.
ref: Servermodel.ServermodelId
response:
type: Servermodel
description: Description du modèle de serveur.
errors:
- uri: servermodel.describe.error.database_not_available
- uri: servermodel.describe.error.invalid_servermodel_id
- uri: servermodel.describe.error.unknown_servermodel_id
related:
- servermodel.list
- servermodel.create
- servermodel.update
- servermodel.delete
- servermodel.event

View File

@ -5,7 +5,7 @@ description: |
Retourne la liste des subreleases.
sampleuse: |
zephir-client servermodel.subrelease.list
cucchiaiata servermodel.subrelease.list
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Crée un sélection de serveurs.
sampleuse: |
zephir-client serverselection.create -n Select1 -d "Ma description"
cucchiaiata serverselection.create -n Select1 -d "Ma description"
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Supprime une sélection de serveurs.
sampleuse: |
zephir-client serverselection.delete -s 1
cucchiaiata serverselection.delete -s 1
pattern: rpc

View File

@ -6,7 +6,7 @@ description: |
sampleuse: |
zephir-client serverselection.describe -s 1
cucchiaiata serverselection.describe -s 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Transmet une commande à exécuter sur une sélection de serveurs.
sampleuse: |
zephir-client serverselection.exec.command -s 1 -c reconfigure
cucchiaiata serverselection.exec.command -s 1 -c reconfigure
domain: server-domain

View File

@ -5,7 +5,7 @@ description: |
Déploie la configuration sur les serveurs d'une sélection de serveurs.
sampleuse: |
zephir-client serverselection.exec.deploy -s 1
cucchiaiata serverselection.exec.deploy -s 1
domain: server-domain

View File

@ -5,7 +5,7 @@ description: |
Retourne la liste des sélections de serveurs.
sampleuse: |
zephir-client serverselection.list
cucchiaiata serverselection.list
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Ajoute un serveur à une sélection de serveurs.
sampleuse: |
zephir-client serverselection.add.server -s 1 -i 1
cucchiaiata serverselection.add.server -s 1 -i 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Supprime un serveur d'une sélection de serveurs.
sampleuse: |
zephir-client serverselection.remove.server -s 1 -i 1
cucchiaiata serverselection.remove.server -s 1 -i 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Renseigne une liste de serveur dans une sélection de serveurs.
sampleuse: |
zephir-client serverselection.server.set -s 1 -i 1
cucchiaiata serverselection.server.set -s 1 -i 1
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Modifie une sélection de serveur.
sampleuse: |
zephir-client serverselection.update -s 1 -n Select1 -d "Ma description"
cucchiaiata serverselection.update -s 1 -n Select1 -d "Ma description"
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Associe un utilisateur à une sélection de serveurs.
sampleuse: |
zephir-client serverselection.add.user -s 1 -u yo -r admin
cucchiaiata serverselection.add.user -s 1 -u yo -r admin
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne la sélection de serveurs par défaut de l'utilisateur.
sampleuse: |
zephir-client serverselection.user.default
cucchiaiata serverselection.user.default
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne les sélections de serveurs dont l'utilisateur fait parti.
sampleuse: |
zephir-client serverselection.user.list
cucchiaiata serverselection.user.list
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Dissocie un utilisateur d'une sélection de serveurs.
sampleuse: |
zephir-client serverselection.remove.user -s 1 -u yo
cucchiaiata serverselection.remove.user -s 1 -u yo
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne le rôle d'utilisateur sur une selection de serveurs.
sampleuse: |
zephir-client serverselection.user.role.get -d '{}'
cucchiaiata serverselection.user.role.get -d '{}'
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Retourne le rôle d'un utlisateur sur un serveur.
sampleuse: |
zephir-client serverselection.user.role.server.get -d '{}'
cucchiaiata serverselection.user.role.server.get -d '{}'
pattern: rpc

View File

@ -5,7 +5,7 @@ description: |
Modifie le rôle d'un utilisateur pour une sélection de serveurs.
sampleuse: |
zephir-client serverselection.update.user -s 1 -u yo -r admin
cucchiaiata serverselection.update.user -s 1 -u yo -r admin
pattern: rpc

View File

@ -5,8 +5,6 @@ description: Crée un serveur.
pattern: rpc
public: true
parameters:
server_name:
type: String
@ -16,11 +14,21 @@ parameters:
type: String
shortarg: d
description: Description du serveur.
server_servermodel_id:
type: Number
servermodel_name:
type: String
shortarg: m
ref: Servermodel.ServermodelId
description: Identifiant du modèle de serveur.
ref: Servermodel.ServermodelName
description: Nom du modèle de serveur.
source_name:
type: String
shortarg: n
ref: Source.SourceName
description: Nom de la source.
release_distribution:
type: String
shortarg: r
ref: Source.ReleaseDistribution
description: Nom de la sous-version.
response:
type: Server

View File

@ -5,8 +5,6 @@ description: Un serveur a été créé.
pattern: event
public: false
parameters:
type: Server
description: Description du serveur.

View File

@ -6,8 +6,6 @@ description: |
pattern: event
public: false
parameters:
server_id:
type: Number

View File

@ -5,11 +5,10 @@ description: Retourne les attributs détaillés dun serveur.
pattern: rpc
public: true
parameters:
server_name:
type: String
shortarg: s
ref: Server.ServerName
description: Nom du serveur.

View File

@ -4,8 +4,6 @@ uri: server.list
description: |
Liste les serveurs disponibles.
public: true
pattern: rpc
response:

View File

@ -5,8 +5,6 @@ description: Des modèles de serveur ont été créés.
pattern: event
public: false
parameters:
type: Servermodel
description: Informations sur les modèles de serveur créés.

View File

@ -5,8 +5,6 @@ description: |
pattern: rpc
public: true
parameters:
source_name:
type: String

View File

@ -0,0 +1,28 @@
---
uri: servermodel.describe
description: |
Retourne les attributs détaillés d'un modèle de serveur.
pattern: rpc
parameters:
servermodel_name:
type: String
shortarg: s
description: Identifiant du modèle de serveur à récupérer.
ref: Servermodel.ServermodelId
source_name:
type: String
shortarg: n
description: Nom de la source.
ref: Source.SourceName
release_distribution:
type: String
shortarg: r
description: Nom de la distribution.
ref: Source.ReleaseDistribution
response:
type: Servermodel
description: Description du modèle de serveur.

View File

@ -5,8 +5,6 @@ description: Retourne les attributs détaillés d'un modèle de serveur suivant
pattern: rpc
public: false
parameters:
servermodel_id:
type: Number

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
source_id:
type: Number
@ -19,13 +17,3 @@ parameters:
response:
type: '[]Servermodel'
description: Liste des modèles de serveur disponibles.
errors:
- uri: servermodel.list.error.database_not_available
related:
- servermodel.describe
- servermodel.create
- servermodel.update
- servermodel.delete
- servermodel.event

View File

@ -5,8 +5,6 @@ description: Des modèles de serveur ont été modifiés.
pattern: event
public: false
parameters:
type: 'Servermodel'
description: Informations sur les modèles de serveur modifiés.

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
response:
type: '[]Session'
description: |

View File

@ -6,15 +6,12 @@ description: |
pattern: rpc
public: true
parameters:
id:
type: Number
ref: Server.ServerId
shortarg: c
description: |
Identifiant de la configuration.
server_name:
type: String
ref: Server.ServerName
shortarg: s
description: Nom du serveur.
response:
type: Session

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
ref: Config.SessionId

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
ref: Config.SessionId

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
type: String

View File

@ -5,8 +5,6 @@ description: |
pattern: rpc
public: true
response:
type: '[]Session'
description: |

View File

@ -5,15 +5,22 @@ description: |
pattern: rpc
public: true
parameters:
id:
type: Number
ref: Servermodel.ServermodelId
shortarg: c
description: |
Identifiant de la configuration.
servermodel_name:
type: String
ref: Servermodel.ServermodelName
shortarg: s
description: Nom du serveurmodel.
source_name:
type: String
shortarg: n
description: Nom de la source.
ref: Source.SourceName
release_distribution:
type: String
shortarg: r
description: Nom de la distribution.
ref: Source.ReleaseDistribution
response:
type: Session

View File

@ -5,8 +5,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
ref: Config.SessionId

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
session_id:
ref: Config.SessionId

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
source_name:
type: String

View File

@ -5,8 +5,6 @@ description: |
pattern: rpc
public: true
parameters:
source_id:
type: Number

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
source_name:
type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
response:
type: '[]Source'
description: Liste des sources disponibles.

View File

@ -6,13 +6,11 @@ description: |
pattern: rpc
public: true
parameters:
source_id:
type: Number
shortarg: i
description: ID de la source.
source_name:
type: String
shortarg: s
description: Nom de la source.
release_name:
type: String
shortarg: n

View File

@ -0,0 +1,21 @@
---
uri: source.release.describe
description: |
Retourne la sous-version.
pattern: rpc
parameters:
source_name:
type: String
shortarg: s
description: Nom de la source.
release_distribution:
type: String
shortarg: r
description: Nom de la sous-version
response:
type: 'Release'
description: Sous-version.

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
source_id:
type: Number

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
release_id:
type: Number

View File

@ -6,7 +6,11 @@ description: |
pattern: rpc
public: true
parameters:
source_name:
type: String
shortarg: s
description: Nom de la source.
response:
type: '[]Release'

View File

@ -6,15 +6,12 @@ description: |
pattern: rpc
public: true
parameters:
server_name:
type: String
ref: Server.ServerName
shortarg: s
description: |
Nom du serveur.
description: Nom du serveur.
response:
type: Template

View File

@ -0,0 +1,22 @@
---
uri: uri.role.join
description: Crée un rôle utilisateur.
pattern: rpc
parameters:
role_name:
type: String
shortarg: r
description: Nom du rôle.
ref: User.RoleName
uri_name:
type: String
shortarg: u
description: Nom du message.
response:
type: URIRole
description: Association de rôle créé.

View File

@ -0,0 +1,11 @@
---
uri: uri.role.list
description: Liste des associations d'URI et de rôle.
pattern: rpc
response:
type: '[]URIRole'
description: Liste des associations.

View File

@ -0,0 +1,29 @@
---
uri: user.create
description: Crée un utilisateur.
pattern: rpc
parameters:
user_login:
type: String
shortarg: l
description: Login de l'utilisateur.
ref: User.Login
user_password:
type: String
shortarg: p
description: Password de l'utilisateur.
user_name:
type: String
shortarg: n
description: Nom de l'utilisateur.
user_surname:
type: String
shortarg: s
description: Nom de famille de l'utilisateur.
response:
type: User
description: Description de l'utilisateur créé.

View File

@ -0,0 +1,18 @@
---
uri: user.delete
description: Supprimer un utilisateur.
pattern: rpc
parameters:
user_login:
type: String
shortarg: l
description: Login de l'utilisateur.
ref: User.Login
response:
type: User
description: Description de l'utilisateur supprimé.

View File

@ -0,0 +1,11 @@
---
uri: user.list
description: |
Liste les utilisateurs disponibles.
pattern: rpc
response:
type: '[]User'
description: Retourne la liste des utilisateurs.

View File

@ -0,0 +1,32 @@
---
uri: user.role.create
description: Crée un rôle utilisateur.
pattern: rpc
parameters:
user_login:
type: String
shortarg: u
description: Login de l'utilisateur.
ref: User.UserLogin
role_name:
type: String
shortarg: n
description: Nom du rôle.
ref: User.RoleName
role_attribute:
type: String
shortarg: a
description: Attribut contrôlé.
default: null
role_attribute_value:
type: String
shortarg: v
description: Valeur de l'attribut contrôlé.
default: null
response:
type: Role
description: Description du rôle créé.

View File

@ -0,0 +1,19 @@
---
uri: user.role.list
description: |
Liste les rôles disponibles.
pattern: rpc
parameters:
user_login:
type: String
shortarg: l
description: Login de l'utilisateur associé.
ref: User.RoleName
default: null
response:
type: '[]Role'
description: Retourne la liste des rôles.

View File

@ -3,10 +3,10 @@ title: ConfigConfiguration
type: object
description: Description de la configuration.
properties:
server_id:
type: number
description: Identifiant du serveur.
ref: Server.ServerId
server_name:
type: string
description: Nom du serveur.
ref: Server.ServerName
deployed:
type: boolean
description: La configuration est déployée.

View File

@ -0,0 +1,20 @@
---
title: Deploy
type: object
description: État de déploiement de la configuration.
properties:
server_id:
type: number
description: ID du serveur.
ref: Server.ServerID
server_name:
type: string
ref: Server.ServerName
description: Nom du server.
deployed:
type: boolean
description: État de déploiement.
required:
- server_id
- server_name
- deployed

View File

@ -0,0 +1,26 @@
---
title: Role
type: object
description: Description du rôle.
properties:
role_id:
type: number
description: Identifiant de l'utilisateur.
ref: User.RoleUserId
user_login:
type: string
description: Login du l'utilisateur.
ref: User.Login
role_name:
type: string
description: Nom du rôle.
role_attribute:
type: string
description: Nom de l'utilisateur.
role_attribute_value:
type: string
description: Valeur de l'attribut contrôlé.
required:
- role_id
- role_name

View File

@ -3,12 +3,13 @@ title: Template
type: object
description: Les fichiers de configuration générés.
properties:
server_id:
type: Number
description: Identifiant du serveur.
server_name:
type: String
description: Nom du serveur.
ref: Server.ServerName
template_dir:
type: String
description: Nom du répertoire avec les fichiers de configuration générés.
required:
- server_id
- server_name
- template_dir

View File

@ -0,0 +1,15 @@
---
title: URIRole
type: object
description: Description de l'assication du message et du rôle.
properties:
role_name:
type: string
description: Nom du rôle
ref: User.RoleName
uri_name:
type: string
description: Nom du message
required:
- role_name
- uri_name

View File

@ -0,0 +1,24 @@
---
title: User
type: object
description: Description de l'utilisateur.
properties:
user_id:
type: number
description: Identifiant de l'utilisateur.
ref: User.UserId
user_login:
type: string
description: Login de l'utilisateur.
user_name:
type: string
description: Nom de l'utilisateur.
user_surname:
type: string
description: Nom de famille de l'utilisateur.
required:
- user_id
- user_login
- user_name
- user_surname

View File

@ -0,0 +1,20 @@
---
title: UserRole
type: object
description: Description de l'association du rôle et de l'utilisateur.
properties:
user_role_id:
type: number
description: Identifiant de l'association.
ref: User.UserRoleId
user_login:
type: string
description: Login de l'utilisateur.
role_name:
type: string
description: Nom du rôle.
required:
- user_role_id
- user_login
- role_name

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
asyncpg==0.20.1
lxml==4.4.2
requests==2.22.0
aiohttp==3.6.2
pytest==5.3.3
PyYAML==5.3
tiramisu==3.0rc15

View File

@ -57,6 +57,7 @@ CREATE TABLE Server (
CREATE TABLE RisottoUser (
UserId SERIAL PRIMARY KEY,
UserLogin VARCHAR(100) NOT NULL UNIQUE,
UserPassword TEXT NOT NULL,
UserName VARCHAR(100) NOT NULL,
UserSurname VARCHAR(100) NOT NULL
);
@ -82,11 +83,30 @@ CREATE TABLE RoleURI (
PRIMARY KEY (RoleName, URIId)
);
-- Log table creation
CREATE TABLE log(
Msg VARCHAR(255) NOT NULL,
Level VARCHAR(10) NOT NULL,
Path VARCHAR(255),
Username VARCHAR(100) NOT NULL,
Data JSON,
Date timestamp DEFAULT current_timestamp
);
"""
async def main():
db_conf = get_config().get('database')
pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user'))
#asyncpg.connect('postgresql://postgres@localhost/test')
engine = db_conf.get('engine')
host = db_conf.get('host')
dbname = db_conf.get('dbname')
dbuser = db_conf.get('user')
dbpassword = db_conf.get('password')
dbport = db_conf.get('port')
cfg = "{}://{}:{}@{}:{}/{}".format(engine, dbuser, dbpassword, host, dbport, dbname)
pool = await asyncpg.create_pool(cfg)
async with pool.acquire() as connection:
async with connection.transaction():
returns = await connection.execute(VERSION_INIT)
@ -95,3 +115,4 @@ if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# asyncio.run(main())

View File

@ -1,32 +1,45 @@
HTTP_PORT = 8080
MESSAGE_ROOT_PATH = 'messages'
DEBUG = True
DATABASE_DIR = 'database'
DATABASE_DIR = '/var/cache/risotto/database'
INTERNAL_USER = 'internal'
CONFIGURATION_DIR = 'configurations'
TEMPLATE_DIR = 'templates'
TMP_DIR = 'tmp'
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'
POSTGRESQL_ADDRESS = '192.168.56.106'
POSTGRESQL_PORT = 5432
DEFAULT_USER = 'gnunux'
DEFAULT_USER_PASSWORD = 'gnunux'
URI = 'http://localhost'
PORT = 8080
JWT_SECRET = 'MY_SUPER_SECRET'
JWT_TOKEN_EXPIRE = 3600
JWT_TOKEN_AUDIENCE = "Risotto"
import os
from pathlib import PurePosixPath
CURRENT_PATH = PurePosixPath(__file__)
def get_config():
return {'database': {'host': 'localhost',
return {'database': {'engine': 'postgres',
'host': 'postgres',
'port': 5432,
'dbname': 'risotto',
'user': 'risotto',
'password': 'risotto',
},
'http_server': {'port': 8080},
'http_server': {'port': PORT,
'default_user': DEFAULT_USER},
'global': {'message_root_path': CURRENT_PATH.parents[2] / 'messages',
'debug': DEBUG,
'debug': True,
'internal_user': 'internal',
'check_role': False,
'rougail_dtd_path': '../rougail/data/creole.dtd'},
'check_role': True,
'rougail_dtd_path': '../rougail/data/creole.dtd',
'admin_user': DEFAULT_USER,
'admin_user_password': DEFAULT_USER_PASSWORD},
'source': {'root_path': '/srv/seed'},
'cache': {'root_path': '/var/cache/risotto'}
'cache': {'root_path': '/var/cache/risotto'},
'jwt': {
'secret': JWT_SECRET,
'token_expire': JWT_TOKEN_EXPIRE,
'issuer': URI,
'audience': JWT_TOKEN_AUDIENCE}
}

View File

@ -1,13 +1,12 @@
from tiramisu import Config
from traceback import print_exc
from copy import copy
from typing import Dict, Callable
from typing import Dict, Callable, List, Optional
from json import dumps, loads
from .utils import _
from .error import CallError, NotAllowedError
from .logger import log
from .config import DEBUG
from .config import get_config
from .context import Context
from . import register
@ -17,21 +16,22 @@ import asyncpg
class CallDispatcher:
async def valid_call_returns(self,
risotto_context: Context,
function,
returns: Dict,
kwargs: Dict):
response = self.messages[risotto_context.version][risotto_context.message]['response']
module_name = risotto_context.function.__module__.split('.')[-2]
function_name = risotto_context.function.__name__
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
if response.impl_get_information('multi'):
if not isinstance(returns, list):
err = _(f'function {module_name}.{function_name} has to return a list')
log.error_msg(risotto_context, kwargs, err)
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
else:
if not isinstance(returns, dict):
log.error_msg(risotto_context, kwargs, returns)
await log.error_msg(risotto_context, kwargs, returns)
err = _(f'function {module_name}.{function_name} has to return a dict')
log.error_msg(risotto_context, kwargs, err)
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
returns = [returns]
if response is None:
@ -46,11 +46,11 @@ class CallDispatcher:
await config.option(key).value.set(value)
except AttributeError:
err = _(f'function {module_name}.{function_name} return the unknown parameter "{key}"')
log.error_msg(risotto_context, kwargs, err)
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
except ValueError:
err = _(f'function {module_name}.{function_name} return the parameter "{key}" with an unvalid value "{value}"')
log.error_msg(risotto_context, kwargs, err)
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
await config.property.read_only()
mandatories = await config.value.mandatory()
@ -61,7 +61,7 @@ class CallDispatcher:
await config.value.dict()
except Exception as err:
err = _(f'function {module_name}.{function_name} return an invalid response {err}')
log.error_msg(risotto_context, kwargs, err)
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
async def call(self,
@ -77,24 +77,18 @@ class CallDispatcher:
version,
message,
'rpc')
self.check_message_type(risotto_context,
kwargs)
try:
kw = await self.load_kwargs_to_config(risotto_context,
f'{version}.{message}',
kwargs,
check_role)
function_obj = self.messages[version][message]
risotto_context.function = function_obj['function']
if function_obj['risotto_context']:
kw['risotto_context'] = risotto_context
function_objs = [self.messages[version][message]]
# do not start a new database connection
if function_obj['database'] and hasattr(old_risotto_context, 'connection'):
if hasattr(old_risotto_context, 'connection'):
risotto_context.connection = old_risotto_context.connection
if function_obj['database'] and not hasattr(risotto_context, 'connection'):
db_conf = get_config().get('database')
pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user'))
async with pool.acquire() as connection:
return await self.launch(version,
message,
risotto_context,
check_role,
kwargs,
function_objs)
else:
async with self.pool.acquire() as connection:
await connection.set_type_codec(
'json',
encoder=dumps,
@ -103,39 +97,12 @@ class CallDispatcher:
)
risotto_context.connection = connection
async with connection.transaction():
returns = await risotto_context.function(self.injected_self[function_obj['module']], **kw)
else:
returns = await risotto_context.function(self.injected_self[function_obj['module']], **kw)
except CallError as err:
raise err
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
log.error_msg(risotto_context,
kwargs,
err)
raise CallError(str(err))
# valid returns
await self.valid_call_returns(risotto_context,
returns,
kwargs)
# log the success
log.info_msg(risotto_context,
kwargs,
_(f'returns {returns}'))
# notification
if function_obj.get('notification'):
notif_version, notif_message = function_obj['notification'].split('.', 1)
if not isinstance(returns, list):
send_returns = [returns]
else:
send_returns = returns
for ret in send_returns:
await self.publish(notif_version,
notif_message,
return await self.launch(version,
message,
risotto_context,
**ret)
return returns
check_role,
kwargs,
function_objs)
class PublishDispatcher:
@ -149,41 +116,18 @@ class PublishDispatcher:
version,
message,
'event')
self.check_message_type(risotto_context,
kwargs)
try:
config_arguments = await self.load_kwargs_to_config(risotto_context,
kwargs)
except CallError as err:
return
except Exception as err:
# if there is a problem with arguments, just send an error et do nothing
if DEBUG:
print_exc()
log.error_msg(risotto_context, kwargs, err)
return
# config is ok, so publish the message
for function_obj in self.messages[version][message].get('functions', []):
function = function_obj['function']
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
info_msg = _(f'in module {module_name}.{function_name}')
try:
# build argument for this function
kw = {}
for key, value in config_arguments.items():
if key in function_obj['arguments']:
kw[key] = value
if function_obj['risotto_context']:
kw['risotto_context'] = risotto_context
# send event
if function_obj['database'] and hasattr(old_risotto_context, 'connection'):
function_objs = self.messages[version][message].get('functions', [])
# do not start a new database connection
if hasattr(old_risotto_context, 'connection'):
risotto_context.connection = old_risotto_context.connection
if function_obj['database'] and not hasattr(risotto_context, 'connection'):
db_conf = get_config().get('database')
pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user'))
async with pool.acquire() as connection:
return await self.launch(version,
message,
risotto_context,
check_role,
kwargs,
function_objs)
else:
async with self.pool.acquire() as connection:
await connection.set_type_codec(
'json',
encoder=dumps,
@ -192,23 +136,12 @@ class PublishDispatcher:
)
risotto_context.connection = connection
async with connection.transaction():
returns = await function(self.injected_self[function_obj['module']], **kw)
else:
returns = await function(self.injected_self[function_obj['module']], **kw)
except Exception as err:
if DEBUG:
print_exc()
log.error_msg(risotto_context, kwargs, err, info_msg)
continue
else:
log.info_msg(risotto_context, kwargs, info_msg)
# notification
if function_obj.get('notification'):
notif_version, notif_message = function_obj['notification'].split('.', 1)
await self.publish(notif_version,
notif_message,
return await self.launch(version,
message,
risotto_context,
**returns)
check_role,
kwargs,
function_objs)
class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher):
@ -233,12 +166,12 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
risotto_context.version = version
return risotto_context
def check_message_type(self,
async def check_message_type(self,
risotto_context: Context,
kwargs: Dict):
if self.messages[risotto_context.version][risotto_context.message]['pattern'] != risotto_context.type:
msg = _(f'{risotto_context.uri} is not a {risotto_context.type} message')
log.error_msg(risotto_context, kwargs, msg)
await log.error_msg(risotto_context, kwargs, msg)
raise CallError(msg)
async def load_kwargs_to_config(self,
@ -259,7 +192,7 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
try:
await subconfig.option(key).value.set(value)
except AttributeError:
if DEBUG:
if get_config()['global']['debug']:
print_exc()
raise ValueError(_(f'unknown parameter in "{uri}": "{key}"'))
# check mandatories options
@ -283,9 +216,7 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
config: Config,
user_login: str,
uri: str) -> None:
db_conf = get_config().get('database')
pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user'))
async with pool.acquire() as connection:
async with self.pool.acquire() as connection:
async with connection.transaction():
# Verify if user exists and get ID
sql = '''
@ -328,6 +259,88 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
return
raise NotAllowedError(_(f'You ({user_login}) don\'t have any authorisation to access to "{uri}"'))
async def launch(self,
version: str,
message: str,
risotto_context: Context,
check_role: bool,
kwargs: Dict,
function_objs: List) -> Optional[Dict]:
await self.check_message_type(risotto_context,
kwargs)
try:
config_arguments = await self.load_kwargs_to_config(risotto_context,
f'{version}.{message}',
kwargs,
check_role)
except Exception as err:
# if there is a problem with arguments, just send an error and do nothing
if get_config()['global']['debug']:
print_exc()
await log.error_msg(risotto_context, kwargs, err)
if risotto_context.type == 'rpc':
raise err
return
# config is ok, so send the message
for function_obj in function_objs:
function = function_obj['function']
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
info_msg = _(f'in module {module_name}.{function_name}')
try:
# build argument for this function
if risotto_context.type == 'rpc':
kw = config_arguments
else:
kw = {}
for key, value in config_arguments.items():
if key in function_obj['arguments']:
kw[key] = value
kw['risotto_context'] = risotto_context
returns = await function(self.injected_self[function_obj['module']], **kw)
except CallError as err:
if risotto_context.type == 'rpc':
raise err
continue
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
await log.error_msg(risotto_context,
kwargs,
err)
if risotto_context.type == 'rpc':
raise CallError(str(err))
continue
else:
if risotto_context.type == 'rpc':
# valid returns
await self.valid_call_returns(risotto_context,
function,
returns,
kwargs)
# log the success
await log.info_msg(risotto_context,
{'arguments': kwargs,
'returns': returns},
info_msg)
# notification
if function_obj.get('notification'):
notif_version, notif_message = function_obj['notification'].split('.', 1)
if not isinstance(returns, list):
send_returns = [returns]
else:
send_returns = returns
for ret in send_returns:
await self.publish(notif_version,
notif_message,
risotto_context,
**ret)
if risotto_context.type == 'rpc':
return returns
dispatcher = Dispatcher()
register.dispatcher = dispatcher

View File

@ -1,8 +1,10 @@
from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound, HTTPUnauthorized
from aiohttp import BasicAuth, RequestInfo
from json import dumps
from traceback import print_exc
from tiramisu import Config
import datetime
import jwt
from .dispatcher import dispatcher
from .utils import _
@ -10,13 +12,24 @@ from .context import Context
from .error import CallError, NotAllowedError, RegistrationError
from .message import get_messages
from .logger import log
from .config import DEBUG, HTTP_PORT
from .config import get_config
from .services import load_services
def create_context(request):
risotto_context = Context()
risotto_context.username = request.match_info.get('username', "Anonymous")
if 'Authorization' in request.headers:
token = request.headers['Authorization']
if not token.startswith("Bearer "):
raise HTTPBadRequest(reason='Unexpected bearer format')
token = token[7:]
decoded = verify_token(token)
if 'user' in decoded:
risotto_context.username = decoded['user']
return risotto_context
else:
risotto_context.username = request.match_info.get('username',
get_config()['http_server']['default_user'])
return risotto_context
@ -48,15 +61,15 @@ class extra_route_handler:
try:
returns = await cls.function(**kwargs)
except NotAllowedError as err:
raise HTTPNotFound(reason=str(err))
raise HTTPUnauthorized(reason=str(err))
except CallError as err:
raise HTTPBadRequest(reason=str(err))
except Exception as err:
if DEBUG:
if get_config()['global']['debug']:
print_exc()
raise HTTPInternalServerError(reason=str(err))
log.info_msg(kwargs['risotto_context'],
dict(request.match_info))
# await log.info_msg(kwargs['risotto_context'],
# dict(request.match_info))
return Response(text=dumps(returns))
@ -76,11 +89,11 @@ async def handle(request):
check_role=True,
**kwargs)
except NotAllowedError as err:
raise HTTPNotFound(reason=str(err))
raise HTTPUnauthorized(reason=str(err))
except CallError as err:
raise HTTPBadRequest(reason=str(err).replace('\n', ' '))
except Exception as err:
if DEBUG:
if get_config()['global']['debug']:
print_exc()
raise HTTPInternalServerError(reason=str(err))
return Response(text=dumps({'response': text}))
@ -89,8 +102,19 @@ async def handle(request):
async def api(request, risotto_context):
global tiramisu
if not tiramisu:
config = await Config(get_messages(load_shortarg=True,
only_public=True)[1])
# check all URI that have an associated role
# all URI without role is concidered has a private URI
uris = []
async with dispatcher.pool.acquire() as connection:
async with connection.transaction():
# Check role with ACL
sql = '''
SELECT URI.URIName
FROM URI, RoleURI
WHERE RoleURI.URIId = URI.URIId
'''
uris = [uri['uriname'] for uri in await connection.fetch(sql)]
config = await Config(get_messages(load_shortarg=True, uris=uris)[1])
await config.property.read_write()
tiramisu = await config.option.dict(remotable='none')
return tiramisu
@ -113,11 +137,8 @@ async def get_app(loop):
print(_('======== Registered messages ========'))
for message in messages:
web_message = f'/api/{version}/{message}'
if dispatcher.messages[version][message]['public']:
print(f' - {web_message}')
else:
pattern = dispatcher.messages[version][message]['pattern']
print(f' - {web_message} (private {pattern})')
print(f' - {web_message} ({pattern})')
routes.append(post(web_message, handle))
print()
print(_('======== Registered extra routes ========'))
@ -132,8 +153,76 @@ async def get_app(loop):
print()
del extra_routes
app.add_routes(routes)
app.router.add_post('/auth', auth)
app.router.add_post('/access_token', access_token)
await dispatcher.on_join()
return await loop.create_server(app.make_handler(), '*', HTTP_PORT)
return await loop.create_server(app.make_handler(), '*', get_config()['http_server']['port'])
async def auth(request):
auth_code = request.headers['Authorization']
if not auth_code.startswith("Basic "):
raise HTTPBadRequest(reason='Unexpected bearer format')
auth = BasicAuth.decode(auth_code)
async with dispatcher.pool.acquire() as connection:
async with connection.transaction():
# Check role with ACL
sql = '''
SELECT UserName
FROM RisottoUser
WHERE UserLogin = $1
AND UserPassword = crypt($2, UserPassword);
'''
res = await connection.fetch(sql, auth.login, auth.password)
if res:
res = gen_token(auth)
if verify_token(res):
return Response(text=str(res.decode('utf-8')))
else:
return HTTPInternalServerError(reason='Token could not be verified just after creation')
else:
raise HTTPUnauthorized(reason='Unauthorized')
def gen_token(auth):
secret = get_config()['jwt']['secret']
expire = get_config()['jwt']['token_expire']
issuer = get_config()['jwt']['issuer']
audience = get_config()['jwt']['audience']
payload = {
'user': auth.login,
'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=expire),
'iss': issuer,
'aud': audience
}
token = jwt.encode(payload, secret, algorithm='HS256')
return token
def access_token(request):
expire = get_config()['jwt']['token_expire']
secret = get_config()['jwt']['secret']
token = request.headers['Authorization']
if not token.startswith("Bearer "):
raise HTTPBadRequest(reason='Unexpected bearer format')
token = token[7:]
decoded = verify_token(token)
if decoded:
decoded['exp'] = datetime.datetime.utcnow() + datetime.timedelta(seconds=expire)
token = jwt.encode(decoded, secret, algorithm='HS256')
return Response(text=str(token.decode('utf-8')))
else:
return HTTPUnauthorized(reason='Token could not be verified')
def verify_token(token):
secret = get_config()['jwt']['secret']
issuer = get_config()['jwt']['issuer']
audience = get_config()['jwt']['audience']
try:
decoded = jwt.decode(token, secret, issuer=issuer, audience=audience, algorithms=['HS256'])
except jwt.ExpiredSignatureError:
raise HTTPUnauthorized(reason='Token Expired')
except jwt.InvalidIssuerError:
raise HTTPUnauthorized(reason='Token could not be verified')
except jwt.InvalidAudienceError:
raise HTTPUnauthorized(reason='Token audience not match')
return decoded
tiramisu = None

View File

@ -1,13 +1,30 @@
from typing import Dict
from typing import Dict, Any
from json import dumps
from .context import Context
from .utils import _
from .config import DEBUG
from .config import get_config
class Logger:
""" An object to manager log
FIXME should add event to a database
"""
async def insert(self,
msg: str,
path: str,
risotto_context: str,
level: str,
data: Any= None) -> None:
insert = 'INSERT INTO log(Msg, Path, Username, Level'
values = 'VALUES($1,$2,$3,$4'
args = [msg, path, risotto_context.username, level]
if data:
insert += ', Data'
values += ',$5'
args.append(dumps(data))
sql = insert + ') ' + values + ')'
await risotto_context.connection.fetch(sql, *args)
def _get_message_paths(self,
risotto_context: Context):
paths = risotto_context.paths
@ -20,10 +37,9 @@ class Logger:
else:
paths_msg += f'sub-messages: '
paths_msg += ' > '.join(paths)
paths_msg += ':'
return paths_msg
def error_msg(self,
async def error_msg(self,
risotto_context: Context,
arguments,
error: str,
@ -31,10 +47,14 @@ class Logger:
""" send message when an error append
"""
paths_msg = self._get_message_paths(risotto_context)
# if DEBUG:
print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}": {msg})'))
await self.insert(msg,
paths_msg,
risotto_context,
'Error',
arguments)
def info_msg(self,
async def info_msg(self,
risotto_context: Context,
arguments: Dict,
msg: str=''):
@ -44,19 +64,23 @@ class Logger:
paths_msg = self._get_message_paths(risotto_context)
else:
paths_msg = ''
tmsg = _(f'{risotto_context.username}: INFO:{paths_msg}')
if arguments:
tmsg += _(f' with arguments "{arguments}"')
if msg:
tmsg += f' {msg}'
if get_config()['global']['debug']:
print(_(f'{risotto_context.username}: INFO:{paths_msg}'))
await self.insert(msg,
paths_msg,
risotto_context,
'Info',
arguments)
if DEBUG:
print(tmsg)
def info(self,
async def info(self,
risotto_context,
msg):
if DEBUG:
if get_config()['global']['debug']:
print(msg)
await self.insert(msg,
None,
risotto_context,
'Info')
log = Logger()

View File

@ -38,13 +38,12 @@ class AnyOption(Option):
class MessageDefinition:
"""
A MessageDefinition is a representation of a message in the Zephir application messaging context
A MessageDefinition is a representation of a message in the Risotto application messaging context
"""
__slots__ = ('version',
'uri',
'description',
'parameters',
'public',
'errors',
'pattern',
'related',
@ -54,7 +53,6 @@ class MessageDefinition:
# default value for non mandatory key
self.version = u''
self.parameters = OrderedDict()
self.public = False
self.errors = []
self.related = []
self.response = None
@ -63,10 +61,7 @@ class MessageDefinition:
for key, value in raw_def.items():
if isinstance(value, str):
value = value.strip()
if key == 'public':
if not isinstance(value, bool):
raise ValueError(_("{} must be a boolean, not {}").format(key, value))
elif key == 'pattern':
if key == 'pattern':
if value not in ['rpc', 'event', 'error']:
raise Exception(_('unknown pattern {}').format(value))
elif key == 'parameters':
@ -86,9 +81,6 @@ class MessageDefinition:
getattr(self, key)
except AttributeError:
raise Exception(_('mandatory key not set {} message').format(key))
# message with pattern = error must be public
if self.public is False and self.pattern == 'error':
raise Exception(_('Error message must be public : {}').format(self.uri))
if self.uri != message:
raise Exception(_(f'yaml file name "{message}.yml" does not match uri "{self.uri}"'))
@ -143,7 +135,7 @@ class ParameterDefinition:
class ResponseDefinition:
"""
An ResponseDefinition is a representation of a response in the Zephir application messaging context
An ResponseDefinition is a representation of a response in the Risotto application messaging context
"""
__slots__ = ('description',
'type',
@ -192,7 +184,7 @@ class ResponseDefinition:
class ErrorDefinition:
"""
An ErrorDefinition is a representation of an error in the Zephir application messaging context
An ErrorDefinition is a representation of an error in the Risotto application messaging context
"""
__slots__ = ('uri',)
@ -245,13 +237,16 @@ def split_message_uri(uri):
def get_message_file_path(version, message):
return join(MESSAGE_ROOT_PATH, version, 'messages', message + '.yml')
def list_messages():
def list_messages(uris):
messages = listdir(MESSAGE_ROOT_PATH)
messages.sort()
for version in messages:
for message in listdir(join(MESSAGE_ROOT_PATH, version, 'messages')):
if message.endswith('.yml'):
yield version + '.' + message.rsplit('.', 1)[0]
uri = version + '.' + message.rsplit('.', 1)[0]
if uris is not None and uri not in uris:
continue
yield uri
class CustomParam:
__slots__ = ('name',
@ -581,19 +576,19 @@ def _get_root_option(select_option, optiondescriptions):
return OptionDescription('root', 'root', options_obj)
def get_messages(load_shortarg=False, only_public=False):
def get_messages(load_shortarg=False,
uris=None):
"""generate description from yml files
"""
optiondescriptions = OrderedDict()
optiondescriptions_name = []
optiondescriptions_info = {}
needs = OrderedDict()
messages = list(list_messages())
messages = list(list_messages(uris))
messages.sort()
for message_name in messages:
message_def = get_message(message_name)
if message_def.pattern not in ['rpc', 'event'] or \
(not message_def.public and only_public):
if message_def.pattern not in ['rpc', 'event']:
continue
optiondescriptions_name.append(message_def.uri)
optiondescriptions_name.sort()
@ -603,11 +598,9 @@ def get_messages(load_shortarg=False, only_public=False):
properties=frozenset(['mandatory', 'positional']))
for message_name in messages:
message_def = get_message(message_name)
if message_def.pattern not in ['rpc', 'event'] or \
(not message_def.public and only_public):
if message_def.pattern not in ['rpc', 'event']:
continue
optiondescriptions_info[message_def.uri] = {'pattern': message_def.pattern,
'public': message_def.public}
optiondescriptions_info[message_def.uri] = {'pattern': message_def.pattern}
if message_def.pattern == 'rpc':
optiondescriptions_info[message_def.uri]['response'] = _parse_responses(message_def,
message_name)

Some files were not shown because too many files have changed in this diff Show More