Compare commits
3 Commits
b2b0764be2
...
93f78ff5d5
Author | SHA1 | Date |
---|---|---|
wpetit | 93f78ff5d5 | |
wpetit | 4aab1733df | |
wpetit | ed1e24330f |
|
@ -0,0 +1,2 @@
|
||||||
|
*.pdf
|
||||||
|
*.zip
|
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
|
@ -1,3 +1,8 @@
|
||||||
|
---
|
||||||
|
marp: true
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
pre { font-size: 0.5em !important; }
|
pre { font-size: 0.5em !important; }
|
||||||
table { font-size: 0.6em !important; }
|
table { font-size: 0.6em !important; }
|
||||||
|
@ -401,6 +406,8 @@ export default class Clock extends React.Component {
|
||||||
|
|
||||||
Les événements du DOM sont interceptés par le passage de "callbacks" sur les propriétés `on<Event>` des composants.
|
Les événements du DOM sont interceptés par le passage de "callbacks" sur les propriétés `on<Event>` des composants.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
// my-app/src/components/counter.js
|
// my-app/src/components/counter.js
|
||||||
|
|
||||||
|
@ -531,8 +538,8 @@ https://reactjs.org/docs/react-component.html
|
||||||
|
|
||||||
### Séquence de montage
|
### Séquence de montage
|
||||||
|
|
||||||
1. `constructor(props)`
|
1. `constructor(nextProps, currentState)`
|
||||||
2. `componentWillMount()`
|
2. `static getDerivedStateFromProps(nextProps, currentState)`
|
||||||
3. `render()`
|
3. `render()`
|
||||||
4. `componentDidMount()`
|
4. `componentDidMount()`
|
||||||
|
|
||||||
|
@ -540,11 +547,11 @@ https://reactjs.org/docs/react-component.html
|
||||||
|
|
||||||
### Séquence de mise à jour
|
### Séquence de mise à jour
|
||||||
|
|
||||||
1. `componentWillReceiveProps(nextProps)`
|
1. `static getDerivedStateFromProps(nextProps, currentState)`
|
||||||
2. `shouldComponentUpdate(nextProps, nextState)`
|
2. `shouldComponentUpdate(nextProps, nextState)`
|
||||||
3. `componentWillUpdate(nextProps, nextState)`
|
3. `render()`
|
||||||
4. `render()`
|
4. `getSnapshotBeforeUpdate(prevProps, prevState)`
|
||||||
5. `componentDidUpdate(prevProps, prevState)`
|
5. `componentDidUpdate(prevProps, prevState, snapshot)`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -838,9 +845,9 @@ https://redux.js.org/
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Flot des données
|
![bg right:75% contain](./img/redux_flow.png?1)
|
||||||
|
|
||||||
![center 100%](./img/redux_flow.png?1)
|
## Flot des données
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -905,7 +912,7 @@ export default const rootReducer = (state, action) => {
|
||||||
---
|
---
|
||||||
|
|
||||||
## Création du `store`
|
## Création du `store`
|
||||||
```
|
```javascript
|
||||||
// store.js
|
// store.js
|
||||||
|
|
||||||
import { createStore } from 'redux'
|
import { createStore } from 'redux'
|
||||||
|
@ -921,9 +928,8 @@ export function configureStore(initialState = { products: [] }) {
|
||||||
---
|
---
|
||||||
## Tester
|
## Tester
|
||||||
|
|
||||||
```
|
```js
|
||||||
// store.test.js
|
// store.test.js
|
||||||
|
|
||||||
/* globals test, expect, jest */
|
/* globals test, expect, jest */
|
||||||
import { addProduct, removeProduct } from './actions'
|
import { addProduct, removeProduct } from './actions'
|
||||||
import { configureStore } from './store'
|
import { configureStore } from './store'
|
||||||
|
@ -1099,6 +1105,146 @@ export default connect(mapStateToProps)(App)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Redux-Saga
|
||||||
|
|
||||||
|
ou comment gérer les **"effets de bord"**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
![bg right:75% contain](./img/reduxsaga_flow.png?1)
|
||||||
|
|
||||||
|
## Flot des données
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Cas d'usage
|
||||||
|
|
||||||
|
React-Redux permet d'intégrer au sein du "flux de travail" Redux la gestion des opérations asynchrones.
|
||||||
|
|
||||||
|
**Exemple** _Appels à des API distantes, opérations liées au passage du temps, messages websockets..._
|
||||||
|
|
||||||
|
Notamment, il offre une gestion de la **synchronisation d'opérations asynchrones** i.e. je dois attendre que l'opération A et l'opération B soient terminées pour effectuer l'opération C.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Installation de la dépendance
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save redux-saga
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Ajout du middleware
|
||||||
|
|
||||||
|
```js
|
||||||
|
// src/store/store.js
|
||||||
|
import { createStore, applyMiddleware, combineReducers, compose } from 'redux'
|
||||||
|
import myReducer from '../reducers/my'
|
||||||
|
import rootSaga from '../sagas/root'
|
||||||
|
import createSagaMiddleware from 'redux-saga'
|
||||||
|
|
||||||
|
const sagaMiddleware = createSagaMiddleware()
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
my: myReducer,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function configureStore(initialState = {}) {
|
||||||
|
const store = createStore(
|
||||||
|
rootReducer,
|
||||||
|
initialState,
|
||||||
|
compose(
|
||||||
|
applyMiddleware(sagaMiddleware)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sagaMiddleware.run(rootSaga);
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Création de la saga "racine"
|
||||||
|
|
||||||
|
_Avec une saga "factice" d'authentification_
|
||||||
|
|
||||||
|
```js
|
||||||
|
// src/sagas/root.js
|
||||||
|
import { all, takeLatest } from 'redux-saga/effects';
|
||||||
|
import { LOGIN_REQUEST } from '../actions/auth';
|
||||||
|
import { loginSaga } from './auth';
|
||||||
|
|
||||||
|
export default function* rootSaga() {
|
||||||
|
yield all([
|
||||||
|
takeLatest(LOGIN_REQUEST, loginSaga),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Définition des actions
|
||||||
|
|
||||||
|
```js
|
||||||
|
// src/actions/auth.js
|
||||||
|
export const LOGIN_REQUEST = 'LOGIN_REQUEST'
|
||||||
|
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
|
||||||
|
export const LOGIN_FAILURE = 'LOGIN_FAILURE';
|
||||||
|
|
||||||
|
export function login(username, password) {
|
||||||
|
return { type: LOGIN_REQUEST, username, password }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loginFailure(username, error) {
|
||||||
|
return { type: LOGIN_FAILURE, username, error }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loginSuccess(username) {
|
||||||
|
return { type: LOGIN_SUCCESS, username }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Création de ma saga d'authentification
|
||||||
|
|
||||||
|
```js
|
||||||
|
// src/sagas/auth.js
|
||||||
|
import { call, put } from 'redux-saga/effects';
|
||||||
|
import { loginFailure, loginSuccess } from '../actions/auth';
|
||||||
|
|
||||||
|
export default function* loginSaga(action) {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = yield call(doLogin, action.username, action.password);
|
||||||
|
} catch(err) {
|
||||||
|
yield put(loginFailure(action.username, err));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('error' in result) {
|
||||||
|
yield put(loginFailure(action.username, result.error));
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put(loginSuccess(action.username));
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLogin(username, password) {
|
||||||
|
return fetch('http://my-backend.org/login', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
Username: username,
|
||||||
|
Password: password
|
||||||
|
}),
|
||||||
|
mode: 'cors',
|
||||||
|
credentials: 'include'
|
||||||
|
}).then(res => res.json())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Mise en application
|
## Mise en application
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
# Logomotion 2017 - Formations
|
||||||
|
|
||||||
|
## Sessions prévues
|
||||||
|
|
||||||
|
- Remise à niveau Symfony pour 3 personnes - 2 jours
|
||||||
|
- Introduction aux frameworks javascript pour le développement web - 1 jour
|
||||||
|
- Formation framework web (ex: Angular 2) - 3 jours
|
||||||
|
- Sécurité des applications web - 2 jours
|
||||||
|
- Démarche audit de sécurité des applications web - 2 jours
|
||||||
|
|
||||||
|
## Plans de formation
|
||||||
|
|
||||||
|
### Remise à niveau Symfony
|
||||||
|
|
||||||
|
#### Jour 1: Rappels des fonctionnalités
|
||||||
|
|
||||||
|
- Les principales nouveautés de Symfony 3
|
||||||
|
- Structure d'un projet, générateurs et "bundles"
|
||||||
|
- Notion de bundles
|
||||||
|
- Configuration d'un bundle
|
||||||
|
- Creation & utilisation des commandes
|
||||||
|
- Le générateur
|
||||||
|
- Le routage et les contrôleurs
|
||||||
|
- Filtrage des verbes HTTP
|
||||||
|
- Routage vers les actions de contrôleurs
|
||||||
|
- Authentification et autorisation
|
||||||
|
- Firewall & ACL
|
||||||
|
- Gestion des rôles utilisateur
|
||||||
|
- Méthode d'authentification personnalisée
|
||||||
|
- Les vues et le moteur de templating Twig
|
||||||
|
- Syntaxe Twig
|
||||||
|
- Notion d'héritage
|
||||||
|
- Gestion de la sécurité
|
||||||
|
- Étendre Twig
|
||||||
|
- Les formulaires
|
||||||
|
- Création et traitement de formulaires
|
||||||
|
- Validation des données
|
||||||
|
- Les évènements
|
||||||
|
- L'ORM Doctrine et le modèle de données
|
||||||
|
- Concept d'ORM
|
||||||
|
- Entité & Dépôt
|
||||||
|
- Les évènements
|
||||||
|
- Mise en production
|
||||||
|
- Gestion des environnements
|
||||||
|
- Cache applicatif
|
||||||
|
|
||||||
|
#### Jour 2: Mise en application
|
||||||
|
|
||||||
|
Le 2ème jour est dédié à la mise en pratique des concepts vus pendant le jour précédent.
|
||||||
|
|
||||||
|
Un cahier des charges d'une application prévue pour exploiter l'ensemble des éléments traités par la formation sera proposé aux apprenants.
|
||||||
|
|
||||||
|
Le formateur les accompagnera durant l'ensemble des travaux.
|
||||||
|
|
||||||
|
### Introduction aux frameworks javascript pour le développement web
|
||||||
|
|
||||||
|
#### Jour 1: Frontend
|
||||||
|
|
||||||
|
- Angular 2 (3 heures)
|
||||||
|
- Présentation générale et historique
|
||||||
|
- Structuration d'un projet et générateurs
|
||||||
|
- Concepts principaux
|
||||||
|
- Travaux pratiques
|
||||||
|
- React/Redux (3 heures)
|
||||||
|
- Présentation générale et historique
|
||||||
|
- Concepts généraux de React
|
||||||
|
- L'architecture "Flux" et Redux
|
||||||
|
- Travaux pratiques
|
||||||
|
|
||||||
|
#### Jour 2: Backend
|
||||||
|
|
||||||
|
- ExpressJS (3 heures)
|
||||||
|
- Présentation générale et historique
|
||||||
|
- Routage
|
||||||
|
- Intégration de moteurs de templates
|
||||||
|
- Gestion des formulaires
|
||||||
|
- Travaux pratiques
|
||||||
|
- Sails (3 heures)
|
||||||
|
- Présentation générale et historique
|
||||||
|
- Structuration d'un projet et générateurs
|
||||||
|
- Concepts principaux
|
||||||
|
- Travaux pratiques
|
||||||
|
|
||||||
|
### Formation framework web (plan généraliste)
|
||||||
|
|
||||||
|
### Jour 1: Concepts principaux et prise en main
|
||||||
|
|
||||||
|
- Présentation du framework et historique
|
||||||
|
- Structure d'un projet et générateurs
|
||||||
|
- Gestion du routage applicatif
|
||||||
|
- Modèle de données
|
||||||
|
- "Templating" et modèle de données
|
||||||
|
|
||||||
|
### Jour 2: Fonctionnalités avancées et communauté
|
||||||
|
|
||||||
|
- Création et gestion des "composants" et mutualisation
|
||||||
|
- Tests unitaires et fonctionnels
|
||||||
|
- Gestion de l'authentification
|
||||||
|
- Gestion de l'autorisation
|
||||||
|
- Mise en production
|
||||||
|
|
||||||
|
### Jour 3: Mise en application
|
||||||
|
|
||||||
|
Le 3ème jour est dédié à la mise en pratique des concepts vus pendant les deux jours précédents.
|
||||||
|
|
||||||
|
Un cahier des charges d'une application prévue pour exploiter l'ensemble des éléments traités par la formation sera proposé aux apprenants. Le formateur les accompagnera durant l'ensemble des travaux.
|
||||||
|
|
||||||
|
La réalisation nécessitera l'implémentation de fonctionnalités récurrentes dans le domaine applicatif web, notamment:
|
||||||
|
- Routage applicatif
|
||||||
|
- Authentification
|
||||||
|
- Gestion des autorisations
|
||||||
|
- Opérations CRUD sur des entités métier
|
||||||
|
- Téléversement de fichiers
|
||||||
|
- Communication AJAX/Websockets
|
||||||
|
|
||||||
|
### Sécurité des applications web
|
||||||
|
|
||||||
|
#### Jour 1: Sécurité de l'applicatif
|
||||||
|
|
||||||
|
- Présentation du collectif OWASP (15 minutes)
|
||||||
|
- Le "top 10" des failles des applicatifs Web (4 heures)
|
||||||
|
- Injections
|
||||||
|
- Contexte et scénarios d'exploitation
|
||||||
|
- Techniques de mitigation
|
||||||
|
- Authentification faillible et mauvaise gestion de la session
|
||||||
|
- Contexte et scénarios d'exploitation
|
||||||
|
- Techniques de mitigation
|
||||||
|
- Cross Site Scripting (XSS)
|
||||||
|
- Contexte et scénarios d'exploitation
|
||||||
|
- Techniques de mitigation
|
||||||
|
- Mauvaise contrôle des accès
|
||||||
|
- Contexte et scénarios d'exploitation
|
||||||
|
- Techniques de mitigation
|
||||||
|
- Problèmes de configuration
|
||||||
|
- Contexte et scénarios d'exploitation
|
||||||
|
- Techniques de mitigation
|
||||||
|
- Fuite d'informations sensibles
|
||||||
|
- Contexte et scénarios d'exploitation
|
||||||
|
- Techniques de mitigation
|
||||||
|
- Cross Site Request Forgery (CSRF)
|
||||||
|
- Contexte et scénarios d'exploitation
|
||||||
|
- Techniques de mitigation
|
||||||
|
- Mauvaise politique de maintenance des composants techniques
|
||||||
|
- Contexte et scénarios d'exploitation
|
||||||
|
- Techniques de mitigation
|
||||||
|
- Supervision applicative (2 heures 30)
|
||||||
|
- Installation et configuration de Prometheus
|
||||||
|
- Métrologie de l'applicatif et des processus métiers
|
||||||
|
- Création d'alertes
|
||||||
|
|
||||||
|
#### Jour 2: Sécurité de l'infrastructure
|
||||||
|
|
||||||
|
- Configuration des serveurs HTTP - Apache2 et Nginx (3 heures)
|
||||||
|
- Prévention de la fuite d'informations
|
||||||
|
- Configuration des connexion SSL/TLS
|
||||||
|
- Supervision et détection d'intrusion (3 heures)
|
||||||
|
- Supervision du systèmes avec Monit
|
||||||
|
- Installation
|
||||||
|
- Configuration et usage
|
||||||
|
- Détection et prévention des intrusions
|
||||||
|
- Apache2 - mod_security
|
||||||
|
- fail2ban - NIDS simplifié
|
||||||
|
|
||||||
|
### Démarche audit de sécurité des applications web
|
||||||
|
|
||||||
|
#### Jour 1: Principes et concepts de l'audit de sécurité
|
||||||
|
|
||||||
|
- Les différents types d'audit (30m)
|
||||||
|
- "Black Box"
|
||||||
|
- "Grey Box"
|
||||||
|
- "White Box"
|
||||||
|
- La démarche en mode "white box" avec le référentiel OWASP (5 heures 30)
|
||||||
|
- Identification du périmètre
|
||||||
|
- Récupération des informations d'amorçage de l'action
|
||||||
|
- Verrouillage de l'environnement
|
||||||
|
- Audit
|
||||||
|
- Création du rapport
|
||||||
|
- Accompagnement post-audit
|
||||||
|
|
||||||
|
#### Jour 2: Étude de cas
|
||||||
|
|
||||||
|
Mise en application des éléments vu le jour précédent sur une application Web sélectionnée pour illustrer les différentes problématiques de la procédure d'audit (6 heures)
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Formation "Conception, implémentation et sécurisation d'API pour les applications multi plateformes"
|
||||||
|
|
||||||
|
## Durée prévue
|
||||||
|
|
||||||
|
2 jours
|
||||||
|
|
||||||
|
## Plan de formation
|
||||||
|
|
||||||
|
### Jour 1: Découvertes des concepts théoriques
|
||||||
|
|
||||||
|
- Mutualisation de l'infrastructure pour les applications multi-plateformes
|
||||||
|
- Enjeux et problématiques
|
||||||
|
- Comprendre l'impact des spécificités de chaque plateforme
|
||||||
|
- Web
|
||||||
|
- Mobile
|
||||||
|
- Bureau
|
||||||
|
- Service applicatif
|
||||||
|
- Points d'attention
|
||||||
|
- Cycle de développement et déploiement
|
||||||
|
- Authentification
|
||||||
|
- Autorisation
|
||||||
|
- Modèle de données et sérialisation
|
||||||
|
- Cache et stockage local
|
||||||
|
- Gestion des spécificités des plateformes
|
||||||
|
- Synchronisation des données entre les plateformes
|
||||||
|
|
||||||
|
### Jour 2: Mise en pratique
|
||||||
|
|
||||||
|
La journée suivante est dédiée à la mise en pratique des concepts vus pendant le jour précédent.
|
||||||
|
|
||||||
|
Deux squelettes (web, mobile) d'une même application seront fournis aux apprenants. Ceux ci devront utiliser les concepts et mettre en application les bonnes pratiques vues le jour précédent pour concevoir et implémenter une API sécurisée répondant au cahier des charges fourni par le formateur.
|
||||||
|
|
||||||
|
Le formateur accompagnera les apprenants durant l'ensemble des travaux.
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Formation "Intégration et déploiement continue"
|
||||||
|
|
||||||
|
## Durée prévue
|
||||||
|
|
||||||
|
1.5 jours
|
||||||
|
|
||||||
|
## Plan de formation
|
||||||
|
|
||||||
|
### Jour 1: Découverte des pratiques d'intégration et déploiement continu
|
||||||
|
|
||||||
|
- Comprendre les enjeux
|
||||||
|
- Cycle de développement et processus d'intégration
|
||||||
|
- Apports et contraintes pour les équipes
|
||||||
|
- Intégration / Déploiement continu: quelles différences ?
|
||||||
|
- Anatomie d'un processus d'intégration
|
||||||
|
- Notions d'évènement et "pipeline"
|
||||||
|
- Évènements déclencheurs
|
||||||
|
- Étapes clés
|
||||||
|
- Retour d'information
|
||||||
|
- Automatisation du processus
|
||||||
|
- Analyse de l'existant et migration progressive
|
||||||
|
- Problématique de la fréquence de construction et temps global d'exécution
|
||||||
|
- Reproducibilité
|
||||||
|
- Conception du processus d'intégration continue
|
||||||
|
- Modularisation et enjeux de la gestion des dépendances
|
||||||
|
- Parallélisation et cascade de processus
|
||||||
|
- Infrastructure d'intégration et déploiement continu
|
||||||
|
- L'outil de gestion des sources
|
||||||
|
- Le serveur d'intégration continue
|
||||||
|
- Livrables et plateformes de diffusion
|
||||||
|
- Environnements de déploiement et approvisionnement
|
||||||
|
- Exemple: Gitlab et Gitlab CI
|
||||||
|
- Mise en place d'une instance virtualisée
|
||||||
|
- Conception et implémentation d'un pipeline
|
||||||
|
- Déploiement automatisé avec Docker
|
||||||
|
|
||||||
|
### Jour 2: Mise en pratique
|
||||||
|
|
||||||
|
La demi journée suivante est dédiée à la mise en pratique des concepts vus pendant le jour précédent.
|
||||||
|
|
||||||
|
Les apprenants doivent concevoir et implémenter un pipeline d'intégration continue correspondant à un cahier des charges fourni par le formateur.
|
||||||
|
|
||||||
|
Une infrastructure virtualisée d'intégration continue est fournie par le formateur.
|
||||||
|
|
||||||
|
Le formateur les accompagne durant l'ensemble des travaux.
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Formation "React/Redux - Perfectionnement"
|
||||||
|
|
||||||
|
## Durée prévue
|
||||||
|
|
||||||
|
5 jours
|
||||||
|
|
||||||
|
## Plan de formation
|
||||||
|
|
||||||
|
### Jour 1: Rappels et découverte des fonctionnalités avancées
|
||||||
|
|
||||||
|
- Présentation générale et historique
|
||||||
|
- Concepts généraux de React
|
||||||
|
- L'architecture "Flux" et Redux
|
||||||
|
- Routage côté client
|
||||||
|
- Concepts avancés
|
||||||
|
- Les "hooks" React
|
||||||
|
- Gestion des "effets de bord" avec Redux Saga
|
||||||
|
- Modèles d'authentification
|
||||||
|
- Communication client/serveur
|
||||||
|
- API REST
|
||||||
|
- GraphQL
|
||||||
|
- Websocket
|
||||||
|
- Gestion des thèmes
|
||||||
|
- Rendu côté serveur
|
||||||
|
|
||||||
|
### Jour 2 à 5: Mise en pratique
|
||||||
|
|
||||||
|
Les jours 2 à 5 sont dédiés la réalisation d'un projet court mais couvrant l'ensemble des concepts avancés présentés pendant la première journée.
|
||||||
|
|
||||||
|
Le formateur founit un squelette applicatif ainsi que le cahier des charges décrivant les objectifs de réalisation.
|
||||||
|
|
||||||
|
Le formateur accompagne les apprenants durant l'ensemble des travaux.
|
Loading…
Reference in New Issue