From 460c962bb6d71e58c12e165ab49e954c055ecc66 Mon Sep 17 00:00:00 2001 From: Johan Date: Thu, 18 Dec 2025 14:35:15 +0100 Subject: [PATCH] First commit --- .gitignore | 1 + README.md | 234 +++++++++++++++++++ config/__init__.py | 0 config/asgi.py | 16 ++ config/settings.py | 91 ++++++++ config/urls.py | 22 ++ config/wsgi.py | 16 ++ img/OWASP_ZAP_AUTOMATED_SCAN_ATTACK_MODE.png | Bin 0 -> 43917 bytes img/OWASP_ZAP_PF.png | Bin 0 -> 26984 bytes manage.py | 22 ++ poetry.lock | 71 ++++++ profiles/__init__.py | 0 profiles/admin.py | 10 + profiles/apps.py | 10 + profiles/migrations/0001_initial.py | 24 ++ profiles/migrations/__init__.py | 0 profiles/models.py | 22 ++ profiles/signals.py | 20 ++ profiles/urls.py | 20 ++ profiles/views.py | 59 +++++ pyproject.toml | 21 ++ templates/base.html | 43 ++++ templates/home.html | 42 ++++ templates/profiles/profile_detail.html | 15 ++ templates/protected.html | 27 +++ templates/registration/login.html | 10 + 26 files changed, 796 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config/__init__.py create mode 100644 config/asgi.py create mode 100644 config/settings.py create mode 100644 config/urls.py create mode 100644 config/wsgi.py create mode 100644 img/OWASP_ZAP_AUTOMATED_SCAN_ATTACK_MODE.png create mode 100644 img/OWASP_ZAP_PF.png create mode 100644 manage.py create mode 100644 poetry.lock create mode 100644 profiles/__init__.py create mode 100644 profiles/admin.py create mode 100644 profiles/apps.py create mode 100644 profiles/migrations/0001_initial.py create mode 100644 profiles/migrations/__init__.py create mode 100644 profiles/models.py create mode 100644 profiles/signals.py create mode 100644 profiles/urls.py create mode 100644 profiles/views.py create mode 100644 pyproject.toml create mode 100644 templates/base.html create mode 100644 templates/home.html create mode 100644 templates/profiles/profile_detail.html create mode 100644 templates/protected.html create mode 100644 templates/registration/login.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..757fee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5f6202 --- /dev/null +++ b/README.md @@ -0,0 +1,234 @@ +# Démonstration: OWASP A01:2025 - Broken Access Control + +Cette démonstration illustre la faille OWASP A01:2025 (Broken Access Control) en utilisant un projet Django moderne. + +Nous allons simuler le cas le plus courant : **l'IDOR (Insecure Direct Object Reference)**. + +## 1. Objectif + +Démontrer comment un utilisateur peut voir les données d'un autre utilisateur en modifiant simplement un ID dans l'URL, et comment y remédier. + +## 2. Configuration du projet + +1. **Installer les dépendances :** + ```bash + poetry install + ``` + +2. **Créer les migrations :** + ```bash + python manage.py makemigrations profiles + ``` + +3. **Appliquer les migrations (créer la base de données) :** + ```bash + python manage.py migrate + ``` + +4. **Créer des utilisateurs de test :** + Nous avons besoin d'au moins deux utilisateurs pour tester. + + * **Créez un superutilisateur (admin) :** + ```bash + python manage.py createsuperuser + ``` + (Par exemple : `admin` / `password123`) + + * **Créez un utilisateur normal (user1) :** + Lancez à nouveau : + ```bash + python manage.py createsuperuser + ``` + (Par exemple : `user1` / `password123`) + *Note : Nous utilisons `createsuperuser` pour `user1` par simplicité, mais ne lui donnez pas le statut de "superuser".* + +5. **Lancer le serveur de test :** + ```bash + python manage.py runserver + ``` + +## 3. Scénario de test (la faille) + +1. **Connectez-vous en tant qu'admin** sur [http://127.0.0.1:8000/accounts/login/](http://127.0.0.1:8000/accounts/login/). +2. Allez sur l'interface d'admin : [http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/) +3. Allez dans "Profiles". Vous verrez deux profils, un pour `admin` (ID 1) et un pour `user1` (ID 2). +4. Cliquez sur le profil de `admin` (ID 1) et ajoutez une "Note secrète", par exemple : `Secret de l'admin`. +5. Cliquez sur le profil de `user1` (ID 2) et ajoutez une "Note secrète", par exemple : `Top secret de user1`. +6. **Déconnectez-vous** (http://127.0.0.1:8000/accounts/logout/). + +--- + +### **L'attaque (IDOR)** + +1. **Connectez-vous en tant que `user1`** sur [http://127.0.0.1:8000/accounts/login/](http://127.0.0.1:8000/accounts/login/). +2. Allez sur la page d'accueil (http://127.0.0.1:8000/) et cliquez sur le lien "Voir mon profil (VULNÉRABLE)". +3. Vous arrivez sur `http://127.0.0.1:8000/profile/vulnerable/2/`. Vous voyez bien votre secret : `Top secret de user1`. +4. **Maintenant, modifiez l'URL dans votre navigateur.** Changez l'ID `2` par l'ID `1` (le profil de l'admin) : + `http://127.0.0.1:8000/profile/vulnerable/1/` +5. **PROBLÈME :** Vous êtes connecté en tant que `user1`, mais vous pouvez voir le secret de `admin` (`Secret de l'admin`) ! + +C'est une faille "Broken Access Control" de type IDOR. La vue `VulnerableProfileView` vérifie bien *si* l'utilisateur est connecté, mais elle ne vérifie pas *si* l'utilisateur connecté a le droit de voir *cet objet spécifique*. + +```mermaid +graph TD + subgraph "Scénario de l'attaque IDOR" + A["User1 (ID=2) se connecte"] --> B{"User1 demande /profile/vulnerable/2/"}; + B --> C["Serveur vérifie: user connecté? Oui"]; + C --> D["Serveur renvoie 200 OK
'Secret de user1'"]; + + D --> E{"User1 modifie l'URL
demande /profile/vulnerable/1/"}; + E --> F["Serveur vérifie: user connecté? Oui"]; + F --> G["Serveur renvoie 200 OK
'Secret de admin' (ID=1)"]; + G --> H[("FAILLE DE SÉCURITÉ")]; + end +``` + +## 4. La correction + +1. En étant toujours connecté en tant que `user1`, retournez à l'accueil (http://127.0.0.1:8000/). +2. Cliquez sur le lien "Voir mon profil (SÉCURISÉ)". +3. Vous arrivez sur `http://127.0.0.1:8000/profile/secure/2/`. Tout fonctionne, vous voyez votre secret. +4. **Maintenant, tentez la même attaque.** Modifiez l'URL et remplacez `2` par `1` : + `http://127.0.0.1:8000/profile/secure/1/` +5. **SOLUTION :** Django vous renvoie une erreur **"403 Forbidden"**. L'accès est refusé. + + + +```mermaid +graph TD + subgraph "Flux de la vue sécurisée" + A{"User1 (ID=2) demande /profile/secure/1/"} --> B["Serveur vérifie: user connecté? Oui"]; + B --> C{"Serveur exécute test_func:
request.user == profile.user?"}; + C -- "Renvoie False" --> D["Serveur renvoie 403 Forbidden"]; + D --> E[("ACCÈS REFUSÉ")]; + end +``` + +## 5. Explication du code corrigé + +Ouvrez le fichier `profiles/views.py`. + +* **`VulnerableProfileView`** : + ```python + class VulnerableProfileView(LoginRequiredMixin, DetailView): + model = Profile + template_name = 'profiles/profile_detail.html' + # VULNÉRABILITÉ: Ne vérifie pas QUI est l'utilisateur! + # Elle cherche juste l'objet Profile par son ID (pk). + ``` + Cette vue hérite de `LoginRequiredMixin` (l'utilisateur doit être connecté) et `DetailView` (charge un objet par son `pk`). Elle ne vérifie jamais si `request.user` est le propriétaire du `Profile` qu'elle affiche. + +* **`SecureProfileView`** : + ```python + class SecureProfileView(LoginRequiredMixin, UserPassesTestMixin, DetailView): + model = Profile + template_name = 'profiles/profile_detail.html' + + # CORRECTION: On utilise UserPassesTestMixin pour vérifier la permission + def test_func(self): + # Récupère l'objet profile que DetailView a trouvé + profile = self.get_object() + # Vérifie si l'utilisateur connecté est le propriétaire du profil + return self.request.user == profile.user + + # Si test_func renvoie False, Django renvoie un 403 Forbidden. + ``` + Cette vue ajoute le `UserPassesTestMixin`. Ce mixin exécute la méthode `test_func()` avant d'afficher la page. Si `test_func()` renvoie `True`, la page s'affiche. Si elle renvoie `False`, l'utilisateur reçoit une erreur 403. + + Ici, on vérifie simplement que l'utilisateur de la requête (`self.request.user`) est bien le même que l'utilisateur lié au profil (`profile.user`). C'est la façon "moderne" et "Django" de gérer le contrôle d'accès au niveau de l'objet. + +## 6. Allez plus loin avec l'outil OWASP ZAP + +Les failles comme l'IDOR (testée manuellement) ou les pages "oubliées" sont courantes. Des outils comme **OWASP ZAP** (Zed Attack Proxy) sont conçus pour les trouver. ZAP fonctionne comme un "proxy" : il s'intercale entre votre navigateur et le site web pour intercepter, analyser et attaquer le trafic. + +Voici deux exemples d'utilisation de ZAP sur notre projet. + +### Partie 1 : Scan automatisé et autres failles (alertes) + +ZAP dispose d'un **"Automated Scan"** (Scan Automatisé) qui va "spider" (parcourir) tout le site puis lancer une série de tests d'attaque sur chaque page trouvée pour y déceler des failles de configuration, des vulnérabilités XSS, et bien plus. + +**Comment lancer ce scan :** + +1. Ouvrez OWASP ZAP. +2. Dans la fenêtre principale, assurez-vous d'être sur l'onglet **"Démarrage"**. +3. Entrez l'URL de votre application Django locale : `http://127.0.0.1:8000` +4. Cliquez sur le bouton **"Attaquer"** pour lancer le scan automatisé. +5. ZAP va s'occuper de tout. Attendez simplement que le scan se termine. +6. Une fois le scan terminé, cliquez sur l'onglet **"Alertes"** en bas pour voir les résultats. + + + +```mermaid +graph TD + subgraph "Processus du scan automatisé ZAP" + A["Entrer l'URL cible"] --> B["Cliquer 'Attaquer'"]; + B --> C["ZAP explore le site 'Spider'"]; + C --> D["ZAP lance des attaques 'Active Scan'"]; + D --> E["Résultats affichés dans l'onglet 'Alertes'"]; + E --> F["Exemple: 'Server Leaks Version'"]; + end +``` + +**Résultat sur notre projet :** + +Le screenshot ci-dessous montre l'onglet "Alertes" après le scan. ZAP a trouvé de nombreux problèmes qui ne sont pas liés à notre code, mais à la *configuration* du serveur (A02:2025 - Security Misconfiguration). + +![OWASP ZAP AT](./img/OWASP_ZAP_AUTOMATED_SCAN_ATTACK_MODE.png) + +* **Analyse d'une alerte : "Server Leaks Version Information..."** + * **Ce que ZAP a trouvé :** L'alerte en surbrillance nous dit que le serveur est trop "bavard". Dans l'en-tête (Header) de sa réponse, il inclut : `WSGIServer/0.2 CPython/3.13.5`. + * **Pourquoi est-ce un problème ?** Un attaquant voit maintenant que nous utilisons `CPython 3.13.5`. S'il existe une faille de sécurité *connue* (une CVE) pour cette version, l'attaquant peut l'exploiter directement. + * **Autres alertes visibles :** ZAP a également trouvé "Cookie No HttpOnly Flag" et "Content Security Policy (CSP) Header Not Set". + +--- + +### Partie 2 : Découverte de pages cachées (Parcours Forcés) + +La faille A01 (Broken Access Control) ne concerne pas seulement les objets (voir le profil d'un autre), mais aussi l'accès à des fonctionnalités entières. L'outil **"Parcours forcés"** de ZAP est conçu pour trouver les pages "cachées" en testant une liste de noms courants. + +**Comment lancer ce scan :** + +1. Dans ZAP, assurez-vous d'avoir visité votre site (`http://127.0.0.1:8000`) pour qu'il apparaisse dans "l'arborescence des Sites" à gauche. +2. Allez dans le menu `Vue`, puis `Afficher l'onglet`, puis `Onglet parcours Forcés`. +3. ZAP vous proposera une liste de dictionnaires. Choisissez-en un (ici: `directory-list-1.0.txt`) et lancez le scan. +4. Cliquez sur l'onglet **"Parcours forcés"** pour voir les résultats arriver en direct. +5. Au bout de 30000 requêtes environ (position du mot `protected` dans le fichier `directory-list-1.0.txt`), vous devriez voir la page `/protected/` apparaître avec un code **200 OK**. +6. Vous pouvez alors arrêter le scan. + + + +```mermaid +graph TD + subgraph "Processus du parcours forcé ZAP" + A["Lancer 'Parcours forcés'
avec un dictionnaire"] --> B{"ZAP teste des URL"}; + B -- "/fake_page/" --> C["Serveur répond 404"]; + B -- "/admin/" --> D["Serveur répond 302"]; + B -- "..." --> B; + B -- "/protected/" --> E["Serveur répond 200 OK"]; + E --> F[("PAGE CACHÉE TROUVÉE")]; + end +``` + +**Résultat sur notre projet :** + +En lançant l'outil, ZAP a testé des centaines de noms. Comme le mot `protected` est très courant dans ces listes, il a testé `http://127.0.0.1:8000/protected/` et a reçu un code **200 OK**. ZAP a donc prouvé l'existence d'une page que nous n'étions pas censés connaître. + +![OWASP ZAP PF](./img/OWASP_ZAP_PF.png) + +--- + +### Astuce : regardez votre console Django pendant le scan ! + +Pendant que vous lancez des outils comme "Parcours forcés" ou "Scan Actif" dans ZAP, gardez un œil sur le terminal où tourne votre serveur Django (`python manage.py runserver`). + +Vous n'y verrez pas un flot lent de requêtes légitimes. À la place, vous verrez votre console défiler à toute vitesse, submergée par des centaines ou des milliers de requêtes en quelques secondes. + +**Pourquoi c'est instructif ?** + +* **Vous voyez l'attaque :** C'est la signature visuelle d'un scan automatisé. Vous verrez ZAP tester des URL qui n'existent pas (ce qui générera des `404 Not Found`) jusqu'à ce qu'il tombe sur une URL valide. +* **Comprendre les codes de statut :** Vous verrez le serveur répondre : + * `GET /admin/ 302 Found` (Redirection vers le login) + * `GET /fake_page/ 404 Not Found` + * `GET /backup/ 404 Not Found` + * `GET /protected/ 200 OK` (La requête gagnante !) +* **Détection :** C'est exactement ce que les administrateurs système et les outils de sécurité (comme un WAF ou un SIEM) recherchent. Un nombre massif de requêtes 404 provenant d'une seule adresse IP est un signe évident qu'un scan de découverte (reconnaissance) est en cours. diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/asgi.py b/config/asgi.py new file mode 100644 index 0000000..2206f9d --- /dev/null +++ b/config/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for config project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +application = get_asgi_application() \ No newline at end of file diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..229fb7f --- /dev/null +++ b/config/settings.py @@ -0,0 +1,91 @@ +""" +Paramètres Django pour le projet de démonstration. +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Clé secrète (NE PAS utiliser en production) +SECRET_KEY = 'django-insecure-@votre-cle-demo-ici' + +# Mode Debug (NE PAS utiliser en production) +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Définition des applications +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + # Notre application de démo + 'profiles.apps.ProfilesConfig', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'config.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], # Dossier central pour les templates + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'config.wsgi.application' + + +# Base de données (SQLite par simplicité) +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Validation des mots de passe +AUTH_PASSWORD_VALIDATORS = [] # Simplifié pour la démo + + +# Internationalisation +LANGUAGE_CODE = 'fr-fr' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_TZ = True + + +# Fichiers statiques (CSS, JavaScript, Images) +STATIC_URL = 'static/' + +# Type de champ auto pour la clé primaire +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# Redirections après connexion/déconnexion +LOGIN_REDIRECT_URL = '/' +LOGOUT_REDIRECT_URL = '/' diff --git a/config/urls.py b/config/urls.py new file mode 100644 index 0000000..46867ef --- /dev/null +++ b/config/urls.py @@ -0,0 +1,22 @@ +""" +URLs principales du projet +""" +from django.contrib import admin +from django.urls import path, include +from django.views.generic import TemplateView + +urlpatterns = [ + path('admin/', admin.site.urls), + + # Page d'accueil simple + path('', TemplateView.as_view(template_name='home.html'), name='home'), + + # URLs d'authentification (login, logout, etc.) + path('accounts/', include('django.contrib.auth.urls')), + + # URLs de notre application 'profiles' + path('profile/', include('profiles.urls', namespace='profiles')), + + # Page protégée pour la démonstration OWASP ZAP + path('protected/', TemplateView.as_view(template_name='protected.html'), name='protected'), +] \ No newline at end of file diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..f3230c5 --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for config project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +application = get_wsgi_application() \ No newline at end of file diff --git a/img/OWASP_ZAP_AUTOMATED_SCAN_ATTACK_MODE.png b/img/OWASP_ZAP_AUTOMATED_SCAN_ATTACK_MODE.png new file mode 100644 index 0000000000000000000000000000000000000000..9386cadff3ce23aa35f0ff5ac5067eeb4208c1be GIT binary patch literal 43917 zcmb@ucT`hd_ce-&q5`6HqzOooD#g%2ktQwDOMr;<5|G}5iXgp6F9HS#y&6I%f^?+! zgrd^H&^v^D!RLA3-}l`y?znf{aq}11=j80R_F8k!wGQDgwN%J&-@i>nL`42VRZ)kC z=o*BG=*qL3HwZ0@*Hp@hh~5&tP<*E6WB#Z4mJd@$&5`VjxwF~DmKU2D88b6+GZ}%L zq%BtzGl{R>V!M0zu=LigYl?Rj-d3h^-u>}9Qr`*C`bxpcazP0&=iTJu6aU_O`jep* zs9FHj=2JT$Uf|~kKh@I*4%*p>RpVEDp|K4m>zT#n>l0%^#f{uvE1$h}93A-xgCUCe zQEL44=8fRiSCtyH!FJaOO;R^#38N)?%Jl$!c?aQ>^Woj!D}+DaQr>`(|7|29Lcg%I z@8$_FXgzG}*a1?{+7#?-7F*!Q$ zSvuN{=Z&!*4$_Fzytks30L+qP;E?ldV17u z$m?HiAw`ZoHZu0VgjDiaRoze5THSrxKJ<9>3CNDmSGp0c@?X5xsJbXRE2{SjiKmG8 z9>Xl1_%Z_kG5LKzD~DblDlOYr=tnGdCM20eRz-DShqG&#_UDUQ|W$*Z5=-XUD(<y|GB13$4(JL!4Mm$9ZLVX}B%%%t z=B(SL(A!ENC8dy4o1H^<+n?$SCNpkMdoq7})>7xZ=vNMj{mxSZhZ80)cJ*P3?Cyl3=%WAfF!`n>@N( z4WzG%;WE0}e6pP3afYqcjb)J?qr1b8cCKHd^*i6`TB~2l5)HWBktylE+8xiKr5wxh zxq*hEtv^Pv^Vq}ds4|*fSYdYf zt4;s?bZKuv%`}k?9ul|QeE9p`*TNBoHT1su$|+R^qf3kzIse_%rP_<=^ucqO$*=_< z&}kb=|A%AY@mx4VL2>fW@S=joUR79f!s5Ciugv-OcSH~#`h;b+%V%3H1BLGNHB~;X z-^Lralc6V7LN-?vx(2!Z@oq}W!v_9sDowVt63v5VypUO;2KuT16O&>d7QWnw^!=R` zGs>+X@Ig z(bHb;SP>R1DGdL^H*DF{bQssSV*hBQX%}L0@HHs6VQ#AcJ%3JtH5KdPS!g@Mr(#ZY zolF;&vRP-KYr0NI2nL=dzPz%0LMlk>+9cF>Qde%-Vs14(@LrDPL@vhk!q$`8Oc@_0 zSl%2eXu@oW5n)~Yn5o2AL8KRSg4;iwya+J}*n5sRm1c$4;Lo=7f_`^VU!M0a>pK3k zZi%RkYwbxa%7St#*JQo~ZZ#hz+Ku|Y64M@hk+)kHweS9GUFFFde@RX{)7d1AIG9=r zYW|C6jSL3qqmYM$AqBz%ISf7}7fW<3?0oyEQXcrJ1gv*lAJ!6h2LEXbPE%p=RSd78 z0|#iAmCpKYl&w#6&z~=gd;AKa8yO6`aOW`i@{gHkH|98>N0#4eZ4JY<79KK*Wr;)*~aj)qDi$#$uZsUAa z;Q(VU-j2HvkqXf&$8jAK|2dT+s9n_h#cl=Sq-7&o1aQ8gxi#rDko|h$zL24xN~<^h zb}rpHDh}VW+siiqnu0)T1Ba*5Dw#*CR8#I13`@FzU)bXJZr2FxJ%e@|BTo37rs@B# zUEbuVHA7ZruS?(id073ZPE{WzmFWTbi_B2{$m0fZ6Z;O^I!;#@&343mKy&}1tNvB% z)pX;ax1oaECNn>UD??ZN(bsr|GBCA^uJt3|W5iSG{-AtjfE3WMhTlCqB3%H~j&%_A zd${9ZHxJrm@WR1rcvznPgVQsgK-)w0Reasz zMdfTO^yqLwFW#g^9q0q=jKlH~x4axxN)2+%i@h+-$cB?ju=<*o?^n9(g(H1VW%~Dn zT9auDL4!bZ@fN!UROb{|c~1lnJ_TW7YHr)% z3IA3$sTpOD2_7@3p*J}^XoBSvWe^iYronR&#~i$!O>1#`R9(n6QIdiGjODn5cKX>NSZA@dPcW% zsJivMRoWm>|5=Rnm|WN&Iyc$S)DBQzcePXY*jx6Vh!%24R8p?&$!q|fnSejG|8Ff3!K?REDd4W(I= zZ~*9Gy!e26lvi+=X zW_yvTmEq0yS?cKO>95SaHIlD`x)?&hdHbiw7$GaWA&U;0qJqQ%=(+j=b+24Y1JHLGoMb9t0qOLcOr4#$}siJ5_%*(|^Z~Jh$0I$}EI&d)< zUiQ0p3xACwz{sejyBkiyeiq(TxLqcO!eL$3-jT4pM6F1?`bKo%5Ogt->=NJfJA!YO zaKH^~)s%hBOYcT8Nq#!qoMpTv$cGEu--$cEDviKLSH-bj+`EwX{3bb|I^6lbpIdsE zb_pNv_2+lf7^-{d==-^8pv`G&tyt(mvt-tx(JsA}@fK;vJ~8ljL(BIKZ&uWd5LYAJ z;Frz~t^Cgw(6ID^cAY<~dqG7-7=2}5x)z?pN>buAs%`Im`*>^+AT)gbWYrv zZ{GV^Bxn50Tu%=-D-p zoquT{-&NTY(y3IntnxZfWgS}icwX^a^cqSjn`K+`bnD$bTN-u`F&6Jbp6$dX{;ub#qP*v`o*>nZ=5I!MJ8DY4zV+{G zC8KL`^N9_I!bgiET@qRc&*)^5D;JUiSECrK@TJh}QD8FKqele-qh~L(cd4Rf;L9cW zkAT3fgt6W>0XDSBR&yr6>_j$6un*p%bcZR!m)UBRL>o6^-M|0%czo!*pFe0D{HFD~ z$Dr&{;od*Xd1n3txM*HR> zHvVGI(Jfyy=t{VHQx6h^XYCO|;~{eu5h{Pa#^I038}@yWBGmy#<~fk~a_K!k-I53k zJP8vfEe50h@)Gkt1$)a zM7B1qRKVf&xJ+1?g-?))KSm$1&kfcW+WCcQ8eFR`bwC{xRcfkzT?;ORQcemeP=XOV z81{<#<@=tD4@qm~acjW+kMig52Bf!|O6Pn`ZXVl;%mp6Js(!%bg_hv21~N_HJB{pE zf2VQXNT#n24EaXq7$x*`e-gJ5n8@Gh!l7^7mPxS(1> zZ_X0EGGhQACb#6E^G^c(OcI`cfTbX zcf1f=FsdaTEqB~XQQXLGm8N0k;19q(JPH=Bme01%YCou_4U4y&wZKw*ABV@}^v$Dy z+4u3}r;Ae+MgI~Z?54#=p(}8o;KcjnMD8re_%hQa%4nOxqWAXwgTMFo1kMg-Fg+BK z!LnV`Z6l+IAJG^hs zUvDojqzsp@^A_0k-H@X=xW%ym)LE=j!d|Q2+9$1Nv6l8ULhO*b`GiEwQ}(oB&-Vrm zQG=b^q$Wu^JTZVJ}^# zrf7mQoEyShc9SX!Q=Z{AP#2CruPaQ)6=6l>5f#k1&Xa*X$w++cF8BX!dI(jN!rFHG)w%@DKHihAGR48oxqfk-aij`zrynlVEcMiNwsdqE4Cosq ziyjKdA>BG&c^vK8Zxz45(n97WU68Z*x9gI;wQ@I6$?q+(4J>9(XvB+K4=yxWq3RDS zbO_NQD7PRRDs|w8*SQNhcT1dY^j3LK0M6m^Y)A15m;;m&E9n-w+pTz(qrMwm>}q7w z{<^DptxquUQ5v zImj+N7RAE%=FSVY-%`+Ntu6`b;O6GW(O-1bFqwe~v(>;l&F^FD0=3LNy_Nd)zqhseu0S zuz}qce`$1M{=`xW0o7_$0@;4J&RC(ha~p)ez>P;L`()3*=}owXAHQH0Kwn%Op+?Ki z8b!(xt_*~jN&3w8EQp$`)9n#SjOHAuA?yxr$#Cp&WF@T7ycLt#>02#hCx5Qm5rjVq zDnYRxevWI#&tBM(@+>id)m1ZfQ?TNX zyVj5x=iDO{9=ATBdQqPg%_v?>7bhkrHk2#?8UxlOVQv-B~RTa?QPtNFGZYLWis60Y^8vbn zs>1*9);9B#pi(NXnFmuuJeo$8I}o3hO!t_E#YbmH!Yj$(s zsumvH-=^7lO@%_Hg@0zU+)TZurpDm-{O91Ci#Gk!;28p?yYjDG$RqP#iScFI)BiL- zy>)H)QdYhEaFYF3Y)pLof8HEN0zKX~ec9H%bM^AhRSm)e;z;sTiSk33gf3g&4tyZ^ z-@YZHw=?nVR}yL6nl4+3q^OMS38H8m-69Fu<@eodmtDMV`o_vD@>d7Nl3c2nPtD){ zRY-6Ddzt9(n5aW7l@a=ehK%+daOz7jHRKiP}xY9ouU>my*cw|+!N_e)G|DXg%=`|u^r3Y6@EweLD!X5^PU@A?yJ13@r^pUXHBNigpb0t8>H>SD=t zo^5#KlQAj*Odd?~+?W`is*S7G_Gv(Pna<~^H_lqC456OBdCp>9MAhF5_E`Uh!k`TV>N%CNNo{*7h-q z=zH)*MGDU}U&L494tt*rgOkFDG zt=%4~)l1`tr2J+O$edQ3E2pZudQto*DyIaM5M!y)&EEAQKw^Wcm5j>E&=d|=D0O(q z{nSn2A$LS*wl#bFx_FtHo6eF$#zv~yLGP_-(efbE9UqbT7RoPO?k9OlA+h52ZW9nm zxJ7fh!O}>TZ{z!7oh}BaY}a$7*Y<7ABsy_cTzrI!q>D8QgSOo-D;||5k-_g$`P|dMfbj&X4Gqw_yP#(oJ`(~axrz>1bE$Gj|9+K9?kg+HJ`4Rs6Bl`aFFk= zk^C!lCznO;j{k9`{FFf$LqfvFCW<`QH7|Y9tGh2NASh(d?ZfHh0 zZCB!}3=!+wNh)182|UYe0)Xaq>lGb83z&9t9S$xJ;H~d%ARa zn-9N82xqU27yXGf?j6Nz$nUrdS{X^-Bl-3kiqWUg5%C9P?483^nt#}9fJ^z@5;j>Y z+=f3!5`-({CTpAA^4n9-Ki2fS{jfN=p_n|uY8opSuO6N65bV#{#f5f~TMJKrbSJ6- z(v?`+bp+IG|8%jOD$%JP@k!?B<8+LbQ*QPj?5Jgk_WEQxsVqF3>-q=YmDIY%-Nm|w zb+`NtF!BS7_XkJytX!;Qt-=PWy)As9X51x`%B%ba?)}PhyB}mLF^~XjKf3_i`fWe; zBvn|ni@#OXSgi|~Q-%=1UgWC8!Y}o|zX6n><}JIGETU^1*QRZ=^BJA%-rcyvr0Tx> zgOITa-7^|3s_V%3jsOf`Pphn%N;{?8?gkC<~wI}F2J<8Od;V%q~ zFiu_61{Kp4CZ>V!InX~d?@Dp0j;KT+hyGdDSb?stu$B82cZY4-?QDZqSIV3!N&@{# zH4=AgmKDr}{LT&*QgAe37Ci~r7r@1SXbqB0_zsQ!y;GsKnj|Te8u!jHhMk$)C4*u1 zYji6+T$a@?aEWqv{nIsUvhQ@JG<14@>LF@XctBU>JkG6r1DP295M5kcJho>=#ifzM zEbW!oe!t?e@P!;918_+Rw770iuxJ>918e-uod{u7%y~wFpvXI`Rl1_H@#nUjXaY)q z;wjBVP%#0p+gx;lt$_XVRP70+F~E=e0do9y=|&t9ibt%#&c@B5iZ)iVl|H>A-v z>1a7F%a+^-^}@r(!}Lm{2)acvo~+|Q*vHmgtN z`9&0HD|4i)N89f6h0_FS+hLfx7K_E56mq+6 zXQRbvYwF)5idD~<9@D`jQbP6W$U`l46ygsz%(^nf@P z9OLpJlv8LtiztB4qJag(I09X(B9nF=h;@TpM<=dw_(n}<61;;*&8SxsG<`0sQo>@T zScc05Ob3ZcWf)+djz#Vd9V<)vGg>$2t34dvjHP z%GI$^3AdnMdT0zyztjj}{THvwD@ zj1zjmoL|69b(iS6_~ko-1>u8cYAzn~DR4ZN*F^jfBBEEP^y8~iZ>%YnxDMu zsmY8wwr&EkCk)ou4f6J!217S3tH&7uUv6pGh-y64^)zY^>*pTVO0oN@ndeRo5kb~4 z(+YFq+}2_}%(t7!OSt(BsMU5%+}Y0JtHwFH#0efZMbqDk@!aQGm4p1IN8qL-AB|y& zO6QNmnQDSA&=9wiR=J_1vb+ZA;}ROmBD1t+1gEjwm(R;Ab5Z|Ha0}T&mTVIE4l}T@}r;F#B~qe zW~>5(PN@#oL=!JFIwpXisCyKIjC6<~x`{&dqH>>$!d@`R54mJS9UmX8%B7)gq~Vo*5cbHx{J2k^4P4Sl9{}qk zw8BQr8x6PDbiPdriXgiTg&55(3R#<<6~`{#(H8ha;}5hs+E3d1Fx!%uF#-}-s0iL@N5X^ zvxg%IkRW!wEE-hntX~4+P>#yE%$StH|Ha3D^EzpDYw(P*b@XMD#j}yKqIgHtscFz9 z=KMSBMGjK3%yuyA_FRgqlj1#%PXV_9TxyIl=W8+!jSF-h$I~GDY1Sac3f#_ySnm9m z-8Wfz*yPrQa6hx2)>rfJ(I0cD{MxR$XOZUT&6MR$l#eX05Iq&X)v_y(i$=(M zfD9X{Py}`qw2~JARI&ZXOr0RxmZulx3pOHVpz_+M%YM%F>wRNd&E~#M)q`fPBA-hL zIqF>-W$z!`my0Vf%K%O!b+oj7@X2n0E>dRu+ckt$4<&(3cd-VU&eBTyA39xkalijx z=X$Jvaqbp5WfG9DJ}@Xipjw6^V4_mm#5ErKeAwb5^mfh!}en*~F^ z=wb_=H=~}BHU>sI%j4wjf9TSUyf0=fEIykSga2kXdAzUescyBTDWAjnRhhw+;7O2UqI67%s!{z!Qgy>q4Nn#zt4fU*q_bpk?%Vq zCK7>p_XxyA>gr`h1%aG|uzj+-1l5k^^~$p_3AaVnn)FT=pG9#xk^e2_m{wR;re9*) z^xLcVQNfUPikb3CmcO8xTMy@LTBiU~>!ZzC{WSLh$&UoGYV&t7g+nNvG!g;)Oj8M7 z&Aueq!qXcyKaNG3&nAHOlckY%BMAi~SUP4Pl^-s`yy4Qanu{XAH^@cK%?KxEtU!isf_601*3y^m@4$-vA{bI^}&y)*T^hRZ)v z_vjkM=WGIHRe|#7paxtD_t0n59|iqu<}m#dgGa9C1U>SlrDd+)t*EF={P!Qu7O%wk zozFf1!#)r1<}>%6asBvT*iTCvcs_CuD*BtCUKT(TqJ9xvXOmNi1$FD#5H5fdZRf|z zIkx*~DwT&oVh*iM0KYg*{U75|WUTl(Ha=cdt5|KcKAcN}yEvKykzOV_1~TM>N0*2um!Z{1zW0Ci!y%8xb2y!Tq(EcS?R{Ev=3C4Na@QUF?dGcBt3$ zRrl`?*kk&kXSnHWlWo(sS9JuUPm7U#3Ha!Bg(%`*QXkiBC)!td3+ncMgZJ7@Apc%l zZ^)PPeb*tykNEGcSKgHotNB%LjXP$a^94l?eS#@|Cf(s3m499TG>}#}Ifl;3|0fe! z5#OXtlF+HxZ4n!`?tC+%x4-``>jR<1=Ln@o9N{LLc5U*@J~DM~G)A-GpI_KIl&F07 zayQT9)!_r{SlP2jg~SLwMPPaL!EF`2W^*6=)ZVdO|CCnH;SuFH<_2Xf#U`z!398i` zTX-&|7aSg3==-+1lChMaOj78#^6b>h!t{^diz!>vJV}3gaA-YyQDqRe$EBZb&2)t* z|H?n=MjXX(XcE%ym)th-CHqA;RhxN@Z6$h6a9V}vZH!7dvP)Tz zV9G?*%J`=}=7@Y07Wf6|!R-2WTHI#d1epNq&rWR20{8CAWRwotfplITw>|jGB3PdH z_Dxc(iQrGFkvR{tK1q5V!vWtIIn9*OE_Fsr?4c^d_ZhmYBW`kqZtwQK&k7Pf?Mk48 zJfi(m?=|6`J9(-T#HDpnz;?1K)}hk2vBMVMg$^SU+5prIW+`)V1MrGy=MHmro1gVz>W9D>Oum@m8g++_-j z*5)HQ#q72VxV!BZrc}wMaL6~scv{WZaV^7d4xLU`?Ix}H0*t|a^X3Li(!?-huE3bO ztf0utwR02n^^HkF#x=P0kDlov)sdX%X~^>kz@*VNBDLW6;YfJUVZOJ`ob8e-i17*P zh-KVmUdy&=TzQ@WrIP=Zg0PycQXfOY0bD+PhHsLTv{-`Idc=Qm)<&<)wG+?MY}hHY zxWCNg>~M*ir#CKx_*0$u%n(%3iY8R=BCSa^)Fo+*sbqi8ovoze)b+US)371Rjen(7 zWt01Q{GXJ{2DhHC2lhEz2ysO};qxl8fB0e4Lk9*e zNaM%h_+>T^pRNs$Li-2IGZWc&oK8rv^XIp*m}5$;8J-=B8S2`5*ZS(QQ9-2!j69K6 z@E0+`iR?d0wZm*Lo2gYYPs@ef5pPlch&(I^Ca>W7U;+jfmYp$Ti#C+DCgjuwN_g!D z(^q7(qX1dGmS#n6(a8w-r-sg6DoJCLawvn7rnTYveY+erk4erwACx0!_)UV%8R*;? zo~XZ+1>X*+w<>~gi_&AaLDa2C{}ZbH&2`&EpfV#^YiVienf5~^pkCj&&=05ge8VxyB1CpItfc~5jYJv_*u_BmjnAqEiL7zc=! z%?89s#sZ?`QYK=hK2F5SWK2XKGmKNj&Rn=xGjfSw!z5d|0a4ympJ%uXy!Y00N_g%;29 z$yU;k5j1yI6cDaiD76kg17TDSZ%T0X-IuL0vkD)Q+4!HhFTS{PlQ8XktH0vyQ`gD} z0N~NkB9sdxx2}vvZr@SbShp>-9v@|}H^cEmvfL@Kr5O*uKO&tG>E;7p5r$VJuMIZY zE2u@}7DAP?zB|<_pK@(X^v8b$J+ydi^oUUr)KwLz8RTGU@{D}192QN5C#=>9|2ReT z@ToRliLo|nQ^w>^g`I7F5vooAf8KNBhBD?urJVf;+J*qPnIG2vHf ziBS=TgnY;P_5>9m_o6&jwno}%4X8aq7a z%L?bHDHy(}F&xfn!04T>YjY3(=%t60WoR_kPL3XW<`!D?TtnC&ZbkVJMx&My1&hWK zG+vQMxdJ{|-eMNNM-cGX=lk=gjt|%2GirTv0S2KuomV@3TKZ5*I$9v0sjtZ5J50_9RML0W4Tr$j27FwDphAo%WQu7M5=m`L(vgffDFZ~cE_0W zHaib|-tP<)HUTVR5YXPiLf7eFJ^39E6XDebWf{Vl@--h0Cr4`f{ZcRy{iTeqKmMXB zCght+OV$)I(IjgYsz%r;`-uC%ho1QbTCwWjMO5L)>GZ5O}&s+tbb zf+3Ohi67~7>_iri=}U=!aH35dcng2j&1Qz{>PH?iT9~Kp%XP=3x7->Atn9Y)j8T=H z(`u7PB1OZHvzmy;3bpylI+AGU17*G&tSXEJnHtiQGFpyZpJ4&kGMC^o5pbOf@*G^3 z7HV)V!FNN}UluY`7w?5dbAI<^A#GF=A^lz!Pw09~z!Z^oH3trhdTmXkcgM#zs-fd!zA%EIy!o6Xy|Sl0ZR^V z&NkZ5w*-oq7F2tYmdo6dzdXo!)_mxF4`X;fud`XauCIVT_0isM8a-_TWtu0+^lspz zuZXVYQR7scM%08BIUQuHeCrvbO8e@!bcCHH>tWK4$U$(K5Xi)V((+!ePtqKIZmKYl zMK{cYh^U*CFu}_REZrag8io@JDTp&EiPv_EO`Ccnlc;YHV8~ii-s8A~Zd`HQwtA<` z{nQJqCdbvnW^8jV>;M-}{#vjlFhh$gQs= zjqB_Z2ETS(EfIvEf|DO2LCL;n}a$km~FM zVbxm=i}QNZx#|xI2wX+?6jlbMA0APIF-+c#mI${SS_waG54#Z%Vb?v`yx&3Lvveq~ z?m}4ZV47}N1fa9gb3m|ikt6|nX4a|CqN1_pjSwzcp<9bSYk7)uzF-;`q*u_)NEzNV zleRxgHbFN)tNh0C2=3PiSWuLgK3KIe*-V9xYyRn|A8W;~mCSU?yQVndpMfo&BIfbi zlZI2D_?$&(2?TS7$9LGvs`J3ZYTaeEyWPDAGL#Z_8uV#(oPw(PLK60C-e8S{+f>b; z>tQdpm|F^wf}WezfM6U8%PinzB$u&H#uxzW4+01rl%@+-wv+NLw(K~i3!y*w#I+3_ zFeT3|5A?pD3&>cQ-rptX9y(oexA)%ja?oUAB&{{;*VL0mGB!V1oD|#0Pq6FN&4xcp z%FZ{g$a?NV-e|DYd*My+eVMsyV*I+)c2eZP&!Y*_zM*XlR8q@q7#5oFX#s=AJwIm_$w|BPAS^QPveH3RMS z;W-wD+M7@QmK0R8Q$ro|oK$QrpcN9){;LY>x=?nAY5i0;xY6#eK=$E70s2G#bo3d? z-@I(X60S-(>$yhrUF9uxVYyzz#(e$ox`D|80!`{oY_h}XGUVba0@KjT<}b$Z@?oc3 zikP2+l+luDcD-r5fhs5wzq43j^udU@{rnK?P-{5dXv@2K*wMw|ErvUn_UbE=V#oK; z%@b>6twE??P_50DP`XTH=0?K8kc6bw5TVrH%I%?}FMIOcSMv`{Otr**QrQm!HmPCa zg9dBTBw6d>$O3tdGjqw9J2A4K=QjK^cRkU^yFY8gPmayRE&^+eB%@Q-rj6{PcJx?| zX~*=-iQu(-i0C#w<2?*U6=j*?_?h9yF>`a&ih?T2R;s0Pe#V_Sfv?#N8Oqz(S=c1| zETPMxJq$fJz!J-uF89v;X;g*d`K!AS=I*y3VE#Mri^LnaB?WYoO;J2S zFZ{U*>iCD52<(;w5yk;Z+yRUVv@DS#jA7%i%V?H?-&q0m~Suup)& zub(VWHLe)1ds#(fH7u4{#?~9I_RNiW=W@o{Q2Gzy7OUFBrTCb>V zaC4vjoy*qOuy_FQ=7*YD<811CJ*N)VW~b2V=qvZAyVae!U$1ppl&W@1>DaH`^7R3O z);*YkaAbFncb+@jbCimN9N^J|#OVNmwM|!=Hm-Ica|s}8Ak}TgbM!akRmoCwjL!}J z=d8N$L-gy=n*s%~wr=^+{Zi1!{~%~pY640nptwiBypFdm(yVL7Ydh>lbGwE&*$Re} zvseujocfu!at|m1QUOQjb!zj{Kl$cdYRQ3-NQh9t^OO5rhPO(&xC7Yu__Qo6K#wYu zsLn)yCT_dTM#6g{u*sDS4GPDPqC(n0w`$pznQDjp-czh*0?YfbKrH%>(Ff#e+k=t% zI59(%t&AKhb%|E`V};6xz3#`dz~s>oXH^Z``A_aM(fd3L_M(QYDwoy(Nfb+Dt`xZGP95~Q-WrMR-Rgj=c9CGQOalyB%- z<8Il6o;r2$Z`>wKWpIlTLRpa!#nIpGy*lIgi(B>M_!>+0E<7B2{C-&FE9MY*>6xT5*zXjq2l$kr}-Uf;!VKb7bqhB_ZS zbyAjAqUoe;t`m1X>s~??^Fk2_E(dt+upD6@RbwN_+P{d6oA{q?;eiPlwss;HPx|b4 zgKbCombhQ)29nn>qk&@+@DCtNYaqgUED%wIAJ;ote1urfiXAQX$0w|3$$kcqH)$tNy)U#1E*K1yygzpx6OG!9a8K^I;j{jj<;x@ z#~Dw&9`KUhK(GQEGIIsieS#r90fJVmM>o5=xRlsM}tCy2kuk`q-#x2K%C-N|wf~O^iVL_ug5}m4zQK28;YFpV*UXjVB7sboupLq#Wfp(IEC$f)bcq8!<0O?B=V=)%l%fsidU+klSiu4Hdm7s8Au!{C zd%)X-Vq}+W6Uit;hOQxP)n_*9DTJ`0b!cdW@Ae~*2oEw&A@r+DIU}8x7cFv=324=$ zpYyXf7Gm8;NK1l~BPqY%eFtl1jut}b43ge>9IJ_(rz%N115J<;MrHZdjsQL$8)+?5 zLuA$lD|I+Rp6T8Dg+5#;URhRXk9?AqU)D-^KkElbPWra52GYY4Bt1}w6WAcs1`G*b z0-vwG^w{#UJh;;2khHEAEP6o>c<|g3VXyqR>s{j+nalKd3hoCeS7d zS?&d%8K48=X?$Y(bp&RR>rtISVP+wOQ`8_}MybciZ@o41B5n%LU`92yWp&R;lR#^l>n9Ac4 zE0$GNEmI5MHC1S@`h3{KuNGyCM#vbR?OSg0EDBGLSTij6u^Tlps|4wVS@s!>s8$CrlZf&BTh)CFB-3Dv2zg>jsxI zrXtK3^8c1u3;!WTA1_NH0z1AVoJSZCy9dDTH92@ujY%q^PujbWKd)w58VBrqOnMRTL0JW6QOQ2GGYlp|$ zlO*v+B)IB+%uONp-ta!3kt`G0dvcpHV1(~`0e$7L+q2*@z3EMEGAgkcqw3F(5@4Ol zo9IKO7*@ksgY+Nn;{~KY^WIh?wJW&e%Tt66%1yhz*&qyOTIeUh23Ld>jdT>@Aq8tx z)V`0Wsx1DM_jro!EK5pCbld*QM|}RP$-Oaowj_=HAd zNbyIoqGOlGm*q#oQZM~)F;Dr3KXGAs7I`CE$+7{Ym|@)pNoBi==m|73cO$%sZEbC0 zRb-`0d_5%LxrE(8p5i;*{X*i}n^ocW4f%7MTT>rBBX~`>e1QnNJxuv6y;&=!S$^Mj z2BnP0QbJ&9kcqjVJDiHSF-d`JR-N z+wlXeBP3@6uD`C2AI%CIj<^s;>j$!7O}v=PQUk7OZUi>jtT+qV`2fb%lx-GN$b=R%0EZ!WBqo0a8 zY;fjI!g_`QGE@`J?h>@0{~$hgv>T0+6RF=GB*u2MPdxK!^oGM~_ZOTZ1fo{QEQg3q z2DZc&tC(uKgC#@Y2GCHr98NMFgO@30@eS_%YMfZBa(_@xsW+j-xd*}~F>{eVtr$T7 zH8F+ERaOm-(vRNj@HxQld=+Zm^=jokEc2zk*TL1do~)#iiz+>M;kt4XGRqQfi+x?XWW|_C4K<_NS2r ze~VWK?H$_6XBBM2KIa+TV}B$AES-6Hybp7;ivFpRZfi3r)k=LrmFXAb85Lq zovMKtkUp0%lu1dY;WUp!l>nBvc@8+X*UBVJw-fJu;8KMo*b%|_k zZKusp9Z}L8Y@Nbu<1D^zKJxvKDzyUl)xv|G6~fEaEeeueBqhb@W=IuQ>G=0ojkYO){H!&~<6g*Ez$3YiBJ^`gQ6tNwQkL=p z$7DMzg}M{0%iNgD`f%#oVSr17uw?ZMCh)cL7fmUm@M|P|s5+EjraFihY-vCGc+h{K zyO)~wTZPM5Ik;?rXheQWr!{-Nr6Cy(h&hwFOcaC;XTvn9TFMFDNIs{RS#pSfmY@!k z89eCIN1Q0|90!2v?5dNWD5lS(!20LlKMpXs%SXQc{{wLF+YWPu#G}Oxn??TiKrx3d zl+S>h8|WB3d!kU-bJ}*k=z6(EA%r!;tiKaB5nonEc(z)PiELORyyrfysbcLBL0El6 z!6qQ7#c%9|*jEu{0nqZ$ufS-8?S>V)s>zmpOX^?IsM=JCG1q+c*SvFT0EnXkJ{|Dc znmnHC2r`(PRx1HFx1&^Vk<}U7+8wQ`x%@XD-E;)2N^ZHIt0^j!6YOs@{3{=v%b_l( zfH?GUaIA}t`Lmiovavk#!exdom*%@x&i`WVy`!3HyX{d_Py_`50RaK&AWfuqMG&Qm zAT4wRLJ7S?f(j@d=}mf4BBIsYNT8ipl3GaiJyGro>WT8OM^>LOsHUssUAaI(S zk3~rac(Io9CLdN0e{daOAw07Ju_HZ(;e%c?-FLM22DfRqOv#!P=Y2@!pBmX?5NYpgq`8Sz!Ib%7@J$kmc zM!S$~T!_W=zWM1#-}sqEd#|Yp?2)S~Xu@bY%U(K%2*&X0xUf_7SJd|6!FJS8w2#QY z#5!QqRc3%7W})!|@^tfFSsK9hq}u!a+JOmS8vl^w6fixEgpQ*t?{mI3Fp%bg;KXdw zr`t?2kBXV$2=%QmLJEAe%(hyViu`CMRkg6*-d^F0I?t*;pFV>Kd%3=es1<8G$`zDq z?o%WUatjliGQzp$H}XBGYfHNRP}kM8?ah3d=;PO>OWpsz@&N#pSmK=aUl91BYGGg7 z*o(i(d~&Mh$Ir)7fh$hYtA53}^1YF_lJAFmFZQWj=X4 zV%}z$>$J&OBSEH0@7#6&5xeIDK^HQ#;fcxq(>BCO;(DOF@aeh6Qn$-6UcgSi0b_1o zZ9geN5{}r}OP1)=ap+xJVKF|X>)bSW@?gj3fyS%Cck$4IKc0KgjN5-X$#9b?Q2|67 zaqX&RN0Fy%9T;0%d_z<{1eM+JH`CUaY}P(Bqq3{Wv!(9kJ4baS2GWR)NTx$9@>_KK z+AA4lh^-(2Z#fv3kSUj(U2E_nsp{njgIm!wzgd4vaHyx9^_q>(G)>RH=T8UKp{u7?7=-=(^OIHL zXovj$T%xlcu(pTh*VPNU)R{G?Nb&Lw@BwLNLGXoyNxryXeF)e^Aq(;%9APytpojce z&-tbU=@)zVPZAE{n7wZwC$-AVfpB`CaKH1dsGYPs={`#R#NO%|c0&S|I~`KBh_rr> z?Y+v&^cZ=E`AYbZNj>wB(~xGWz0n&r=+%l^gRy0BwWE0_C5>kCbm#l?L?Pq--~>|p zPkX|A#&wTr1z4s{}S?Owy z`wV+o9hIiywU=JPUyd7bDQE|M|H9icT@^(7$Ss29vdQO?Q~w|1us+G+Z|iH;lXlBn zy`CU(%Q;(~pPXm8ZDghQR=epqv(0h8`rinGy7=F{zgei|v}Y5t4)JJ5tgo$F?A*UA z*${11(}QSEgiie!kZe{PulobdQbSBo#`V839j^*%7mmQjaP#6p9qyyFx&oz)Lz)R5 z5e&=Mbunnl} z-NE>Aiw4AgcV;4&>DO%eBr2)*USqjw$zKYF_;NUbRT)0%#j>q{nZ?!SmW_E^%NK{% zsEq{0P|S>RB0_MHlTJf|R&*VrDVda>TAi#tl6JOn(iyg%6mdUmtR}n=Y=Nd`lU?DK z(DFxE+z8BBi4URx{`$?;D9(DXhU7={!B+_+|Qo9pk8Xzv3yR$ z6vwG=@#b-hSMldKXzPJ*K__WAkEH}A8$<)8jaZh7GGq30vfJazSv`1}%^-@X+T`#p zk$*2D7EWx@!+WjvKoZN~$)va;F3H&t9AWk*=%#)T&D2O%0q7 zvG|D!2%2E%GU(0@clpeZ?)Xq`K!9horn&OU*Sh@2cd4!;|C^xmxzo=H-9@^kAhwCe zVTemJ8JCA8MK25g&T|d4|NrQ(Kx))!0u`D>nTK8x#FJe6IML@mhQDry9dHs#2HRg& z<~=sCD#`H%NW01x+PqHZ`)-`+DVQ-OqZ9gYJzHM)2B#)gI_zCo2P{jY@ku;jpGEMb z7ywX_dgIsKrPDfO3^W>RA@LKJjKtalQfdfaP^?AS!wed;@&qv-UaLpZL}q+77JAxR zfSiR=WI!`+h3nBV2mSSbfHD82;pW?#1Hw_PVB(S3OH;mj#k5oB9mOvKD+_-NtWnm) zPbn@-)K>VO0I%iIJw7hCP_Y0jz9Uv@?V^{h?OQ#`qg?fNNm0yi;N6~trV*EYh3?l? zOyIZ)nFvp`?(5+k`;~MaE>wq0X5mS;5a!W|Hr)0H`i<}I&JCS($SDO3bE%hG{#0KJjU-EW|m*Yl?z77Z_zI?=8i0*cR z3AJ#B`mR!19nny)YA5PUhao5#D2XDFx(2{EUGfN2gOF)X8L2>Sq5*Sm^RAqlvYlq` znSDi$tVeC}-e|5yPD+B1-NfThWSjPRvLLSEs6|;SA9VM>2%>sLkWxy0!(wI*r(-X8?g0V6dz}lw)*(Gh zxa|^=yYnEQRMVcl``d2ikg*I6$n9EG3}ds7@6;D*K)|+uTa4d?Z}+=_@nN5Ar}Bis zi))#m|A`r)z@~?OQogQHDg0FX1OH7iuJO-(CA6igm2xR9LahaPaDxuU6JCuC*4$TU@is zXE$}wGSaXgH`1_ZO4&xzId+jf)H{IA^0IJg%N5`~sZo_?ZiniZs%OpGo zMXQE2$iK4h%?+LR=5KZ1QRE_&ARq_=vg`jN_Ux1LNuct8iaz5l;jF#XHC34lPbpao zhB9Y4^>SWQnLw;m#!3=ms4Q(VA}BBeDqs0DD?$i4G37@MbRNZRD`yLv3@+}>ngmgT zXbTKs5XH}g4KO)K<%Pk?vqRM=r##D%0isKd^U&>;xe~a7pWpVQeA?ZizckLPXPJ|F zmck}uNis9(4H0WX^FO0Z0>Nufyp`0$as~zvPQJWBjm!_65*)Al)LI)xF%u9(2?6TW zxGRO@aAt%SZowIaaMC8XOTCl(%;RrzFBvjdGBgy2+FrD+%GZ5-N(XVB%gmVdWqrPM z<1o&H=+A8p$7>!zi=l`$oQ^^tt#r4i{_c;twL~lVlznUzCeMd-s*qwg z?u&c$rH&JC?-G@5{)w>tLYiv3Jt6;wQ@a^)PO87-AD5$Bz4f>3`BRxTfNAIGS|_RW zpW8WQ*CgqcK=&eN3dhpXuh+_3IHN9t34c=cX>56ZFx<(g#y+2R^G!UzO7KoYLraMP zWNe&H>G+A9Inh9u;lEckbMrldg&&g`*=Tf3pZLXLjcRaqVHn|RPW9m7(^8o(D8KG^ zy|HZ2iwufq8buI~?z7KF4N89siT4{3*AlnXNn*Aa6A_Y_x!+O(6LM2~z`;(N$Rd>9 zsbC!dYELdjqXNW8OXGVEy3cOm7TuGGy&o%ABP~w7WAng_1_0~Aowt@ zBTplhvi@53lQLFRKm~j9lZ;uc;{iXwc05p`hSZnB)x8&TMR4^_!yj7pP+wh=v~X>_ zw#Rj@hO&#ChQc_SuS_tCGREOxz(!}~368_vY<_B_$%l}i-k&CNb%PR;*lpkAox>aa z-u^K^SDCB)TUMq!tO`YSI|@Xfb!+>oH$Zc1oX|YFDCT8~i9=Z6QA>Vm+MuED3$NW+!6(Oz%RYTV(~5bPy|oVvfu#%3Z{2#Aaev4wJh5RJkrr-&?6g-Sp&0PL#Ot_5IMdB6y>0 zp1+EKVB*zXRAPylCFob0lbcMM3Gz3gosr%%WilL>o&6<`oDH;_?m%^9=j>APJv<^c zJHjIStAnV?^JT65E(V$4A5yVz6AaCvQsmObqg?fuW*1g)c7!u}1N>Iq4j$v1o;@>K&gr2udAkwHs zN$*G0>&v6#0Y-|6hW~Vzq->D$Ub>mdhc`D7sq>>e6ES8y#h{mRO5*KUg8;KbHy3RKB(zmhPR&s&UIK_JXC1@c1%Vxd|A?h z-ot*WYZo(XSCO$%+ZjQzClozAG5tB&Urs+baZ%#x8Mq7^K`!mv|98D3pL2$9{F{+` z%nA?0zs><>k@;Nh{BKN{8Em%~JU`Uki+(6nbzqtI;^b>nYBIg{43|oiMJuG3TlEzi zCgDX%RlJ^D_xv`t{4lL;g_gfOVqRO9WqxaO0UBgjd#?OrxrI_LGy7{no#78x@uJ%TwhwzHMtKyjUdKFX`WFSrb`1G!&u z%Up!xL`?o`7W1~#PU{f7FYA~MCFI1+tP}#BrC&=WsTR~R_~(5){6`JcI=2uFvF;)P zZ=K5wRu@z)GD+{V4pb9{Yrj!Fsjb}#96!!4$l-f%%lq5?=o{@<3%8MiZ{5vvIMEG9 zFEn~Qv^o^Jq5PZ|L+S8R4n+xAs$HF)cc zR@?1abKgfsqNTFwt~U(&LS<7Qcr|~JkJa-@qjclGysGs+wsHwe+Ftm@yBsPN@FV#k zq3ZO^eDr?q2fI^u>KUrwa($`CjyAdZOXi|HdOyrnUGs~3(u!^jdGB|#-f}nposaQenCmNyI;t?H16`C9hmWW4R845#eWrXYyX z7s-^=o8j*o0n27YSLPL2wBibhbXDUZ&il`Tdk0-3c;r<>lQVR0}j#FUDf~p?zsQ7z;^a*x(L@(JVb zyca`pxFl%*;99u%hE8?n*jB~=8^MkqZyfeLQq6JAYENn(_G>ri=p{u@P+9EpcodJy zEuKf$E-Sd-dHzT00IELf= z!X<9TzeIG(poh1iPx~$7=mpW+D?aPIQKRx1YB1M!RVN$$fG&N6yWTB%Q|4M7X@DjI zi}RokMx_fc9h_rs(n1`w*>?kLg-=#WTs={VCdO`4^mGv%Sk#2pVAUHR3oZ`I*b>Qx zd*8A(D;L>~Nhm;cFCR?PxV5)aE91D7BG<;ciq20Eg7iNW(=s>zaM2BZbcj}@6+n6q zL-PW_88p3Jaj+;)n?F=^s!K|dzvB$I0kJG=3P@NQ3v9d^*18#1odmx;qcT0C2l~*p zb>u~!hu=8A(Z6zNkZ}HMxJ7#r>30bJ3Bai*=3N!w>Ll@=KY)9O{hL(~+fNHd$Pw?; za}o`^AN<4jd7l@*sprqQ_qlesUe`0HZCEels!;y;c(fX0FSU zEdFIj+??WOMWvK~e31qmb@H0lN5*%W#zm$ZD#`RQ)6(DHs8-{oYh3Jc`=cHpiT&jW zq-?VI)8mnfO(GHVm0%s=(`vZ&NN4e7mIYIxH56J^et1@+Cy*7^IZ((vEesf_q7_fh zo1d+&w;ypGK&$^-mkMgL?2^kimWRndYx% zO(W$&S+DZTF6<|;u0k;B^w_CrM6Q+_6|I>stTVj6AF`Q{aJ!ylKcwBh~@h3ZDBe`I*xN@!qHd$;69; zuA;MbzNAU!#E7!PP#F`D>2-|#%V_$aZ^(d6c5ull{5R_q=;P$Kr*`J0(a@@hP(C8!#yW?fEsd3q<^PtD!um&L$)cOPLW(Aq2nFx zHCy8h?DnEG&5$Zk6`WuAEqe8sQXR^_Qi7SN%Mj?%mfnRYUC5Td;Wt9&0&$uiTzwY zXl;#^;spR!#Fu-j`tNXjg=2{}U7*z>QOgA)lQIX5%9JLluRtR3Na)JNg2SL{u)`ZPs=@=u?LZsHhBV2>A*XG9H(I%vDFsM_4b&OwSG1gjQDgA zBGw_sg2+#x)WQqd)hnP-`^ryAa$h%izQyjz=*?h;AhzZ{Vwl zZ{2%CR4(?8Z6P}$ia)gJ_f7i#o%HLD_y)pWO1=l0r~9fQgUVE$hg_Nf0x;FS|8B_A zi@f*fDuF9d*cRFSrmi2x`P{znV>rSKeV7%=X5nt8ZV0a;$M<+6LgT-<6X{V6StU4G zvZ=qBY1_>1=pBJ(l5(J)CSOd3;Hm!>|Ah~BEDDuuHK{;P?=8Fz zd!m=-(7F0mlgZ1wZ+~xS$o+0T3HN-x{|Aq^;?~m-)qixVzA7M$^z?5Csf>&)T_daU zjmwmCjx@-G<&dd1Mj+Mh$Jtd}pk1RpM{3+U-3>epaG9TWI@Z z6M}a}JgH#I|2oO#oqxH_x|<@MRKPX;2eh4Ua&bgu>@>$u7$Ig5H7B?hPbC>DT<}H2 z-em}~7bWQ(4r&mPHOW$E>AZ*GbopYw-#tHO`SoU=;Ny#ngK7B}<#UEq>!G1%PF}2H z78l;aPHfWy4(mvR&NL|1?`7E7ILu_sNq@qeffy@oeS5yRs+gkVI^W*UtJh*Hxg%p3 zZY5`mMHtc)AJ@dgdu*Zub@Er0`cVAE6Z7y5og5X&5FLb<+oE`%aIR5 zH^Yw(J|?;k&Ono_Y;krTA>I**aVMXiyVlPo>#=kj%oM_akZ_#dApxFt@G zp`|MLqS{QLo6dH-G3&AXOT!Q(cnCwklP=~lP<5!znf1;7XINRN@w2LJ@4mhpsOul- zF2)zmIRmhHKFp=It~WVqg9kJ{pF}##?C53JNn*i@xU}e-_Jkj174?h3!lf5S6ugt? zcAhJ7b>|=DPWIs5vil)y*=DT5L5kj=1`*aIg#w{jGP6oGgCSfY|o5(uK;hZ z+$cc^Mkk>z6jL_uZdumXc{=s?nA8r>^l~kQx5Z-%=JVUPd!x+a=GwXYwZbUUxSZC8 zAic|r2c#Ac*B0Bu_g1})1SCGpcNry!{*j=yx}E1%P_yXclG$eBx9Q9Y5j_&JaN)?L z0a3;og7hktmM3=%=a@P3&@VrsU#)HbkYT|*snll5S!fJkNNp0FU_71AS2sUAerpp= znk^jMX(O^+t(PoF{DI0UKc_4%9dTR!$OgVT7Vxaa#8c>OFVSsPne-x~yycv9@8`t{ z^~rWW?GPeid05r{oS$)KT$#%&AXtOxl!-CGgIcz6-N;Z_j}{^j_oJQPTY{A+n*Y0Z zYa7cUx!WuMm}CQnT^{fErNq6S;|If*{>mM^+=~kQPMu5R54mgwFy1DpE;QO$Z-c61 z)^qy#E)mXR{`%VY=$}9IWW;-pszw(!fs(pZY;{4jDu6vnhqQ+3gDe5+mtr`vA zeV!x^9KAeSNHr(q`Q~arS}t)CX>d#>chR_1cfru?v!1hn|L7#g)pv!k)v@X^YAn=D z*ngoTKGORMj+CxJ%V#uPJc;0|GzK*p;agO!j11Viuhb|1-_S5ZC6nA zE5xm0?ct8+KJH#;xqHQ{s#h<3gq^Z3Bz9dJ><)bPei~d9=KDY#Iy$ioOGKJvEoS>? z8czkN%@ta)8UgZA1n09?NCTQmk7{0IRu;-_u`srzgXZ%jJ6vOziX%I*CqitvAfdYS z+R&4ps9v;)#DS99=_I>l&Hi3jmrEVli6m-g&PBp+yZL8vO~U2*6z4!+g9uG0xsum6 z@d`SVhn}#+R`1>eDW9eO?!01(;uqtZe3DoL8#P zf2W=qmwY+9O|L$G3w;VRz<$?d&~S5}TUambD3UoyQL$A&JEmQ>fTILULt%1H?hv+4 zn{nhoSdD}aNkv4~gujBXJ-u^U851Dk+%OJ7B@&agWK!6rI18VjcNC$Kx+O$nZufhC zJ6LkIeG_G5U?@m+b2y7S@~~AZp96kL==Vt$s3|cz2EGKbbCE`)fe%=jQ8%SI0{$fL zATz88;$)3YucjX*L?yy+dbJwG*vuDPWvdd$l9M7(aL7y4m$ zg9{gI zyjyzvgoj&-{5e$Vl%SOY!`yzof_xm9=9;QMU9-iN^Y9>=fypbjwA7r`ceOAPAg z%D#}{3ylh=Q(@mT+KVqH*d6gbOvQO~4fFY%$OHLp)0L?z=w9oQwEG{N-s;qR?%sgC z=9V+?)F(-=p2Jh-AJ5ndJM8bhz|O#u$}*ugy!DVFna^yrpKR*EZgj*T&UeRN=t-=a z58q{c;uAo8UaI(&MbQi1v)|~8_Z=Ch%&Y09XNP;|8!fqBrVYu{80jfYz~R=+FRE)$ zkpsNx(2+dyxzpKiEh)r&N5N*A%zfbTzc}KlAt^;6va*jY*S#=J`s;Wn-O2hNig=B* z2lsrCHuUx2YB4XyK+$+FQDBL+PFoFzBXEa@C|*tfm}$SYm-I8|NqgsR7uQg#7qn1k zZ>i>hbx->ld~eDg`OIc+I`4TZ+4)?xyZiF(y&bmYU*$J0xR`*h1A-Mwk-yJBGcSWs zFt*7@7%+#6NK*w$+0{`8y%l1nI5y0_o_Zh!Wcd!`F%v2@(-8l@j1;`TuZ}{jXAZYU zGUv)~9J4gTH=H1*QL@*xi=280a4rsOK>3H&_nJqg75L1NZ|98Wg9PfUCBwdsPB-i# zWJMlvoVwTB*C)L*wy~M?P0qXJrSa#K^9n{k6{_%JKyy_z#%$NFoS)R9d=cqbZYpj; z{0RNYXK;J1tw{-qyPapE7LDBegJ<7I00lOP(f=d+t2}s$8`+0k*pxYsJ%Og?ly< zw~r0M^L7U}vq3p!JpO+i(hJp6g8UmJ#{CBTpUfRScDb#|~QNl`n3 z99MIbn8L#m@;K8cH}v*V-Lk=8VW^rZg%1iGw#<`<4tnnJ0%qdQmBT*4;Y_mR{^d#| z{`ZE&OB@HNQHYQ@=NV;LBcw^{_JTCC|1?LZ>O3J#kQ~An5aIE3Lxt=Qz17rY`fC>P zKr_Dn@&z+9>_f@Avx1Sdxv%T{aqxsryOTcyK6aQt`@S)PgR%&+)WJxUU3#G8jy*SI z(IkK=?=a0(_KNHk;8Q%n-Y)%^5God^5rmx&KLH=+x}XlCEBLa|;(Ms#=D){%dx^>) zG^_H)>iR)(%k|9zm0C^1wl!qVJrB$A^sl8~A#5Lsi~D_TrjOU)vjY}bR-_{A{-4x( z1@Xr;v^Q1AeoJx7>F$-(tFEz+?|YbiU>g!labhtVNd_9L-FJ`r^_6xD?wC}RnL04C zB4_=t8KDJ6S4y2sr3;`jvq{_;o^E!Bb3txq+gNou#ZT{bAI{@d_VJByK9 z5lgy;w8MW5p>Cs7`%MQi`yT$P(0f_o0r$J94;X&u^yGX??mJkK`NH;t6aPX^ir4M# zQ^s8{VE1E0dBZll^#7b5C3B!9yNsXu%HC~M7pT#f;i4^%uARTC8`{0R2;>?8kOx%4 zvNZTz7b8V~I~f>XzgCFtNl1>#;Fa1J2Lk`BlZP0o30v$|V8HV<$20N0H7nZT@U`=6 zIHuzZaA$+mnd5w|?O*{f7~8YEnlE~h80EB{8`s#Pz(o#=j%sNL%P{s702;tT5%4UL zMoNdm2ZAH&ETuHbWZo<#H}6b~aeLRl0t-I>+&F@KR3KSC*q){%;ugcPNE=ZShDd(p z((!tUb|7~_YPr+FSkjvrRbKJzx|8h4WN%s7h@kR@eR<%S2}oGOv)^vMW9;c}O~mq1 z(;-k;15elQ*WeUU1XTc*Q^3+8EHSsrDWht&-s+0H2s{6czlfzVJ*VC4*)3Vc%?l3W z=QmOENT1|s|1%|g(S^GBK5C#<&esy_v)4g7lb0T?k*h9$9I_wk92D%Os`u`d5&P`b zHvZCdxw0@L&0Fw?1HqOXaar)2PUMSji_#GDTXZT|Gh`1i72cDvYkk?F*UB_SZqSAv z_3YchP1jFcvKHUY9<4jBKJgV8-M9QT*7~4sUndmES%3I-?oo6^Hj}epd};4MfaUZ+ z|28iLB%MSo(Db5ha!Dd#BmDrqhw07TJ1_PMl{@+ltTOG37t%~;auCpd=1Otq8NaO9 z*7DwXf521C+pr9TtVsWfLgid6ZO$#L3?@4NB;|+R$3nXvz;-B$tZ7v8V3pc2{-!&l zM@h1I>vt+g74&^k1a(Sg>b1;VzdpCcPCD;f?b9Y??I+jI?!Tu!f+q_c-RHQ{iv_RU z*|pscnm1#byTN2MNymLvP^pUd<{te&D~J`txg!nHoH5Du*d5GmTw~79z2WI1&WPD2 z-y71dMY)kf!AT>aX-A4&vonGS7Xyp zilqg}*_$yFP3)PJ!uPQ6ISb(EyQVirX+vr6H|`dZ7b)=FHSgYkP^0eOLr+l1mJYjr`cz@!>xZT;(Q1`7xsEcWQIuRS;x6R8*}73A2khsiJK3Q~y-|cH8Yh9IVrI zsFnqvK0p0&90<&u4Gl1HRIRD?rdinrnSd(%i$$#!_hz8#M?JR4m2VdQ;Ep?4gy7Na zv+ItL>kxNsc%;}#af{F&Mzmec_a!C<^B>r^ANnJmPU>^|n5s`Bz%o?9AeMo!aU~zaa0r(3W%PX%$-Y1omXzX`|QNAg<@jcn+bz&EkPX6bwu6$Ya64IfG+q)o@ zh#HoScf*0-a{95yf1F78pTZ~Pioq&D#`2;ci9ih{y7$E`r7gmvjUzJmM!lBb3h}hB zQ5$C4E?JE6#99gtUI8`}UuOm_#QGDYyR;Z?t(C5EU$ScoxRk%!DgZdHO$oukIduTW z>8f!+*X?=RB0jr1_>1#U+i#R5q#O--_OBm>ZK`pYI%%w59+nx$035okD&OPm2|+w@ zu$49(&`8B)hDE4-t`~cDsQdEVxOOse^HfUOT>&eco(fi7KR>;^L51V~?tM|_=D-j> zZDp2#4V>41MtGC9)kt?*hq|Tph z_mMX%{FOB(UiO}ay$f9&U31{T+!D>c1w0~y4v0#u&PRS~cf8lVx9hyX%*ZTz(SH^icVEYbv)w-S-QC1(|Gn&(fcQoo15%Ou)iXou zi};nH%%~x_Uo9%)#^Eo~60)KF5^@lp!%J2*v+;lhv){vPyPkds?&ReHoQgkGV-&%${mWRUQUDbk$Sfu2TGSYr`4}>q&6-c!ky<`>|6Y|$xe=}4pG#HGD^FN zn^{V1pTGx0I1$`{>Gt2aHY@b63!kvD7f1z{`qNecfAdKr64Pc|Ds|Hn=>|VuRD!DK zVh-fIfo<=n>6c2+dGQ=vPFg0s(RFw1ycHn~^?)CP%r~NmI$;uZ+U~+IaZ8tR?+vbT zx<-lT;@SPC)gbm3$)&Rc@5WbyPq+Q7nKOmCVxQgGK5d$L0y0e+q<~$!y>j(G9b*w@ z40Lp>FY1i90&jbA@dHIOCO+@v0?zDGN8y6wTMacSas?G()a;3}`0;}qjec5$+{a%%!|S*o67eIgok(L&jf6TnzAbmH>ioG;e&qq+F8MEz69Ryo z;4})$q^U!H9k?<6SIORDN3Mt;*hv7R#yqU|k;k`R0PFj;Q6Z3aWOZBWaieRyV|&Ah zNd>L6I_wIrcDa$^+EaN+7{$=ObYmc5O3@;rJCY@&a3aB_u0rl}1}TZ<*1W!&i!#rU z9F3pK?xb(di_qNi6o0bQH89E93hD)!%nTT&QPSX0duO??^UAxOe^6@*Sa?OTPQBSW z0ebX84W4o%)~Ig9WTf3Mkn}E7YNq}{_DoB?xYmY+ zt9*|g9s1#JS2A=cs+&T`3Z)FAv1U6XST9KQa3qSlI9sa&58N|8gNKvv@e8xgew@J=jg%BZK%h za9THou5!samVC6Wlbyc4u`T+8KlZyKU~Bg`PxBfz)~$g(7T(OS7&KNBc4Eq5NOZ{V z_+|q}g>mZ1DT|lg&kuK+u&`xPuppfDAZN}YVpGNqV7V&pE&S%@^@gHYs8Q?sevJ3i zuBLSgvZz>8%pDuR8XdTcLsQ??rf0II>!Vmps3y5A@@N}@d{TY=zct*f?b3_8V zb;aPf=T5Ur!|v}gk$|n2CrddsA|3;-C+nlUD?`lk3Z#kWLnjhyG4yvEkNIQIHG;UF zk61{{kRb!|ZW8ksprfc)iUqyDhse^jX^OkLv0#R$RBg;hU6Kd!8X$)=19?Ht8S}7y z-e)BqE`%<#!xr{yCQG799WnmpW`nJ5jHa{*9MtAH7D*>v?vXz;o(ha6R=IsD~ zdbT>*(eb<(mDyL@B18{ogzVk$tPOBo5&dn%Q2(+WA-^q*${5R+XMzi21`ZzT&UnRSKP7JXoe7~IfY`qSWLCPd=o#Y^t39d%J;ui4R$HXp~ zjg!`^)6_`XAB283#p7O`R1&Vs0oNmc{f_qD_fj|8i5l8p+duJj3=;EKP-BpIN=rzH zGyz~pE|#4&OO$i*5~=;F8oA-}Iv!nQ?REEc8f~G6f0BQxaOi7blY6csoT{{ANN+bH zZ`+M1WQrpqLAf7!5)Ib%{nx^AIy^{+a$b$D-R_mJg7t9(gEd+wRX*C(Nb}nuV>sTs zMWZN^V}b+J$AyV!HFjiZM2!0`Zla=5rcxFU=>$Wy$^+IUUwHB(_HEJnCH97)6MtlL zBWc6w($cJ>+f^_RkL%BdJO(f1Us4v z^W8zoHYN7Gulxe)H?{Qg7LhcYnzi!G+NsZ`XS&}+GB$RwqD7+V&I9s~Sm(EsCzG_& zglp}qj`kVWl5X}n;6{^sB{j`nOcaS~h z4>mKId4pknv~9So%vu2lo8V=-YE?wC=C_)kc>z-BE_`^jb}1?C)5Vidk`h&AMQ_os z*gXNwgmjs=u^`Yz^*mtIS4LJ7G7#IW#duJy zr~Tj2lwy?Jn8qCgK0*E~$P2(QklM}KlOv+{Ai}CSac z)EJuo$Izoe3deX|2c%OTVTRMuDhUW zv4+|wVB2NK{-=M%Bk4@Yp3`D=Elz0w67jn%qRKus8g+6}GB)3hYm!rG8W&xw32$Qd zX>l%84=r9GM2TG{4F4O@x+o`-nk2Z4JM6>eYwq?qQ2e$9%h7b=JbG?d{)5d!P+1j1 zjCVbE#wTXN+Ndkjuz*R`*+*S`zKt zjreOnPlzBI5Pek#=dm3xr++HpJ@Lzu#K=>;SC3b_3h+@Vdnx)jQ_w~YJ)Iy;_2?Il zMw;+i)hM=Ql;q}n&CnE7^LcBxIRK_HIL+Z&*a0ZD*`L|?WLPWJb5ksJdzPuedh}@n zY~S3zWue?=)eSAD*l2Rh7TLVZg!5h`ESvHT*_a6`R0moq)M!Ld*2%O_*T?851ZjO< zW}~~pU5fR$=Ib)!dp(n0HwlO0%7kct6i@kB+&3kMt#{%I4f(_8qf6Sw<+R7Ck58&Z zGxdmhC9Tf#bF}`NIE)P=&~W^+kyqRa$Lt_IEdK5o z`V}enwIXTVV{R$*=P6kV{$;pu2YX~aQlM|pjCH=OLfQ@djiF^*G{*liJcQ-k+MtyD z@b_UK!4o1y?5DV9quCs9LTPhB)8S?ffBn~t073A)GBP5&NV-pmRmh2z;sb4);T=-z~x?>#)u9mEZSx4L?ttH#t zz7x)Jv)ss^1bR1<`xH3#|K^&*F1h9vojN)p&k5+DvJ$q2yjjK6QAVZu)4@_s|DF)^ z+FK=wV`2_gY7oSfz*(F z3md*eqGRrPpBFo~MSGd(ljovJ+Fqko?F1({`4jmCC8Lh!LJb-;(c{>g9t zCUHmXbtgVWw<)IiJm?M%XDZy}pGgWA8}8h@JxfemQDpO><7j7!nr*9TN~oRo0fpGt zmVP1V`vqbimZ-WkE_iQ8b)#+UNyI?Klafgf(@=j*h}HvE_=sHINgV;uf%7lg&mupk z-wg13*R6|;PQt6#Xlk0Om>Sf@Sl`Cw3r-z{P+KhbdH>qTNwKsPw-`9}#ESBgFXZ4D zyiPj~ZqHIL_Wv;9JNIeb3}H7J*|oemeb_2eHQJGU!W@ ziQVh4&Ot8UQsR-Px`Q3`)?H@D7{YgV{^9VbA?fSzbopJAlatAIk=-Sh2J>zjJ3haU zG-Y7dMK|~19<7F>ME=Aj7WU`gjSsh23?I3UY&1c4!k#h-aQEz5$UG$Mi!!jMh@G@Z zdQByTXucEP85x=KgJE#Hq5@wl3{?H$1_`Y`CLeX*S)lDUPIqI^91^utN<(CH*}dL4 zZ{H(R>2x+*2l$#t#s8#hq2M*0O1g+iPs!W;jh`LM0zQ!8uu7Zh^o8Vd4|pdvkm4F% za!m@(jNpj{0|pwI&Avp&C-chS)*(|vQ-WgdS}`Q(&5pFr>G(A&Jpkv4Fh<)( zlbTz3%{FZYc3)LyjLz;GW*gPFD2FT!KaaQy33);r7CqxiLO}2@px*xf6`TKSppGO^ z(PAQ@I`ouPdi0|6$`K@Bqx{WDR%}xM5PtVU8bUpjFC~<8{0mqs; zq5Tr<{YPu)b7}}d)(_&!Cq@YJN&1@vj}5~R1q4N-b{Y3#37^y1oryFa$5;uxV7EIP zybIvzB!JwFi#^ZXuZDXT!#`9kks4*@EC$a4;IFh>>gP!Xwhd(@bQZ?AMABeyOhBh+t`lVIKGTW%4|6f7v{F*t{2IJk^Rsn(+J&P zk`{x%sQoWRl}p{!+H*XyFIB~!W^}bJCU6osRS3RtDk^~<8b#>udV-T*JVH8f2~6&z z@ILdzp2%Su=E5`o9(ce1M)}>DRadBClkS3>GkTgyTH5pyx%PEa<+w73Rta5-?XLeY zkrp}qcTY3G6pECBOH)(#wL*yJ_^4h?MClHMJOWIYu;uB=GF}_K%M_u@h`27x9T2T7 zie~$GdtAWGZdVcR>X(-UdiZ*9WRny~{trn0?q!Yw;MB!?>T?_{`^6mY3nPotQJtl$ z-wvZ7r-Dk9Ho{n1$R{dwJha^n`|@t5c8~!81NkQe>(zzxtEH3sZ+-xT4Cgsuj0cm* zdAJLn!m#O>fM35Rd`YHq>YNN4!7(`O@l3iDxZfKeHT0%*2m>Rbf6 zX48^C(R+x>muJkkJ#@^aBj3oj*JaFG(_=;)sBZWs%6Z|Q!PgSxhsLEhQBistzc6K= zR?gMEG3>z0dHa+K5(#^1lz(-$<5T_j7mnHUwb$$v@|uDD%UZN2v52V|7Lyn|vGp`x zcP@{aOs)$%jy3Li^eM}fomu|*(+~+@-3e|){SqJD1^_hwUp=jJ@KR; zdQKR{;iU7ZjcFD4#aQEda9Ya-@!1wFSLxJIB)D;9i3?Xg*;EryiBmJBTPxmxUe$Lp zR+tGilRzS?Ye=}^vY@Q8;CA4?9#UQ(vL$!)cPeE^7|cWu zrciaWeDsD*Z*KT3kI|8~l_to;6;R6atK3+R zGEk`Th8&cj)4!x4e496-=W&V>Rfdtd`_;~_nXhXVs`eXF6q+U+v-FukucX%&X~$LW zi#p{)$39^d{n$Txnm{sr-AuG!XExHLn64BKu&otD+N8Ux(N@(@xGhB$wrRWVVJlQS z4VNbhe?>DzF5FpQ>>adyLtbIN`7GQ_Mvm+k6ih4(?DFN5q?uEWr0o@|OEhfyH=X(}{LzKf;_@)US&8Wp&p%i6|A0|_AtI+kVZJD|2 zs<7gepO)z;Zc3ImkMsA?tq*ZqUYDcoSSGoKWO9_V8gwh!hRZM#ZXZdydG*J8SKY@# z85y`zGp|tz9ucq^Tx|Jse&>&kBmW0)h9NXOF$0-Cf>7v&MXJy4ovF&Vq@szk&%$*@ z>USg?Y!|tW{d;LtckfWL&6fKe-Qm9{Y3DU9=hOaxoa-k|vSU?$xPRg7|I^-?$3xk- ze_te(Hd!Xwq9WO%>>_&-lCm#Z5-}sxATwN)eVLFY+msepUAEEK8cSr1Z7|3%i=83H zHg<#ORM*{o-_Ps!{PVl-KcBzP*K6kdF6a3@&iChY9LKUAi&T*!(ze}J*L=!fMC;fr zzIHVjjL$Ynl|q{dn6ifB<`+%#X9afw_byyrfel)cW2(5uUGe ztmNi5G~-K-?@ceysQ7s`a!3bdK=wiR{ycCg zkjV6lA}jnfGFC4C&85nRA=qpcvwth3G&71Y~ubf6865UN3mcKw_m)7E&ox!h>4*W3kJstn8 zmhBv0FH@ykt~n=8Nz{r{wtHP31iq1NJxGx#YcZKr_h1Sh1dw@!!b8TWyt+!~f%?E3 z>L8g;n;5Sg+PlIhuK*Ix>eP}`K3s$A)c4C3C5ivvN_G-iHvdf!sJn2*tm_RNGgV_T zwp`EQ7C~-VYntwes_16cOH60Q^>z2EwoQMwDt>Jo$~-7zy=gqdS+8dVkvd#Etgd9^ z+suhA)wE>!Ytc2Ll#@6&C-_m{A1aL=1M54vfn920S1YfN;m#AC+D%&Q9$K6#yc8W$ z{HZ<2r1-tYF|WUx##I*?9}___)8W|Tn<~W6VW|{XOtGXw;yC9ZvTr0#9qJ&y&16W= z_3v{4S}FZ6$S%Jp3h(p%RMIKx4SlheZ#XBA%>)wfI4t4(h{wc2w*X z)Cj3nf-f=#>TMsGA<$5IS%gBOzH}XH*g<3VT)~tWjeECW$CRT82Uga;djxt*ufOd% zdSel{$9IPe%bY`RlFI?AD>N>az_z!+@}H$fm&e@0Q{=k33OfJHJF1d!@G#@JKxO}a zao)+NDNsK4;lAdFUB-F%M`(iCb^;--ytKDz-(L9n2{;A8Evd*n4)+}6wxXIp5=91a z)NAh_LG>D`3C9J_kUkkm%J)T9vWZ731M~8V5yspl`>4B!>SWJz*j$pxKre_$aA z(vWBE`soWL$^9AfVZZ*x9f+%Gt3vq+WJK2}p2MU5lfxBS7=1OM-(5N=w@85GO#iDw zxST#15|ACRS4czr5S0%sFUk`)tq?<4mKSXbXx&3)m#nPN&iQ+Gmx4rq0v}MG&kq(3 zeg)wl7~=7k>sqd-O_SGr@&}IBUMCj9T1YAY zFg|K8`R&W|x#;rRQOy1NHR@sQ2X)UrG>%^ZdzINP^j=?4_hhPrJz+c7xXMD~V8B9X zKgt7wQ%lUY=Vo7Li1Fw2`M1WQYBzf3s0NpXDZ0c0wd=%UO>cTSWkni3M~dfRAH>>$&G&kR4a7hU5F@6H?_$B89bwld6uh23Lru@?~r2Ptb81PxjJt$XPWff2D z(;o56+U0=9YnFLZ>9ei+WabmYsi-;?XL^&59$8;m%QIkNvT*wu_`zoDrMlvY5Ps@L z*p$&`{nW~e<|kuq3D0W3>mdOq?QQqqRGYWr5}RbJlq*Cd8#1JmdQWV_&4WvqDRdcy z*vbm-@WGELcl@FBTRuEVaNkdEZoY2VKo1x3E>ZWCCqP$P*_JnEta=JQ!0RK4g?k)F z7hpp&OD1o!Koc%u>b^H)OENA!Cb$!yPu!opB)X_zEr6bQ(`YrNz zNH|^YX_}Teti^KTlKJ1hO4YwDf(G#p&>@C70ZP{`?oJm#bkDx2`6=`cvA7Vto>O=> z36KB|S+BDL7i3<{+^N9uexx?ZLLb(JaMFpp9g~MJhs-RKb4e+ngN9I018qME` z3YV}!lt3PcnYGn7wPLYgfPOQpxwH{|V^gALI z2P}+S5Q}1)`y!erxxtfR=RmSgvui70PCr$0pyQR@siCCiuZQ6l>ywycbBTHv#h7Bb zRQIOQXiSg}##;k3#(nW(Y+~x<`AP5NuR61I5BC`woa2yv)E$T+H{+4P-UYJ7;Ez?D zGtEVuR4Xy0k_%5Xz|L`k!FiW>KH9Ue{;~g`L59+^n&36t5hYE>Ipyz~SK*TI`y&RK zissthuiSfKGDuH1*RC_uJ~Sj#zYa$-TJpX+$_iDScS z6Z(%r1mkIZT`<^DQ}tY{6GI8O0L1N#vP1EOCt5_=DmbcAbGFVE0k_7eO`2)ADw}0C zed<`e19w(6;;OrPa)BHGo?8Ebz?^?2h%g=u8ap8|8SaA`ddkuRItlkXyYhHVieQNl zTh$w&p;BzO#`DI5l{3PcdYlG{q?`&fNBQP&EhYK|w;nTGh_Qlia-NN+p%?hdSDu6_ z>w@?op+TT?&s6w2rPPqO7OW5IS07O1_^2gPhp&orwF&Mo0(xls1=Q2HFtWWXh;a+G z$Y=e>MCZBNlQ^&U?9V&uarF1>#gv=w&#HfKC6_mEuNdWi6BC^~P@Z#4Bsj%1q0Y?E ztlESlF}uVp;flJCQ+7pKks!r0^c#Nm%c~*q(RD zz({OjzNi9lcc+~OJpF0Y_240cWBip@Gqd8Ft((XzWipooLgrFubR0m!-?aB8um-TJ zyXvC%Is5|-0htK@q3Eo0Y2OR-cpiCsFsUM|p3`};^1^evthtiMyMtCZe=Y&zW&KeO zucX%#+&oy{(v2I(r9bwlq0G)z+;)8u1pe4a>2Z}$%kawDKy$slCLs1A8CAph*4F@P zU^(;lA|Z`w{Y2z@1(D1}b zgsJf%a0tdmgzABX+O-Fyt%#aT-EP%vj?M@XWBAgCO>3&7T^ygk9NZxghw_q24!B~l z@2G3j9z}847c-OaDf@<{x?M`Q8GpF;S~)wyqnPbnLiNWttxDxj{ zkcUU#gFAqpcI~h;u7G>E@A_+Lad8rCF@@O$UBeFONlqNzJDKl({+~Q=`&b@ba<2b= zeuM+2(q%$OBX0>@$s}DCO`mO)Pg1t`hfcfg%UB1L>&&_272=aFaIdxLd{8lfk`JMg+(V0)D>s@SH{jnhj{ zSoe-T(_dBB&b|aLy>8Ttmw;8<9pAPSiaQnS(EokZHIlU*6gKe93@Cg!CaHr!Oib?I zo5K5h)S?QG^Bh|}iwt>BD+LPr3Rs|r6il~PSBrDAg-)-1f$1NxzW%W(J!Igxk`svST5OQWO$5KXhtHo z)_jtV_&7;#wz75wpLEl^ejnpjmbts!e9lsgl^pW)onJc~n7%XCk-#67euINFlhhl#r zF`fLpLSnNIV(&4icXS2PeHfGY$Fu$N5ZQ?0L3GRqxosh8P3lzZd5j-HDU`~!IU;9j zjad{RKnZv=l0#LH*X}*?tJ;0-^S4&Md%|skD^Rs_pNJPe*k7QxXRilS3%uk({nD?GhjpAa;|FRN(-RV@9>8?oURH}yTWjP33ll*ii z805^UtQ5%$0`uM2r-rXd$$KfL4-=60^MIejJoMsw_axwG7W>>wNqT4DU5w?-^(crzaP*I_9wV+X4LLO=f#q1 zgyaVYw|mKp1EYW1NRx9!KO*jUi>n*yYxBijy~YORkaX_G`P7-Vi=MQQ8l}sNNQKF0 z&R{nKBmPwOxo;2BMn+$C0eDmym{f3&F!|xlUCh^Fd}nr#yCxg<^ngx()dyySb&SPQ zcGSj)v3dE??{dr(O^fNzrlBGDepd;|G$Tj2n46K}Ez0W5jh11ANP4~P_G%~U!9y3I zvfZfFaT*TA0ngr*v)y304vK4+d>D+wY77xar`wwTWLcU^M%Qimiya?JFd6nK`gYuAIy`P>tgo&+K_t>GCb)Cy2-J+c}ur-AdFwVe;>XG zw6YszxwC(;O12>ULQeq6n^QjV$nTvM`@askl>e~2gN;=G> zDCCW;b{NRe)&hkbaqOMI5fsOR-d&HI^u{$vc*#v<47Ar7oy4;$UzJr<=CGB$%_Wi} zLN+>-NUcp$(U?#6UxN7h7poU)A+U*UIX+PPr-gzUnni!y>M%^=M7*^pqZZpb!YUXi zVsJ?vM$F=`WGZN9*K-8q10MH!=)82FSk5M&_)jC~e*&)ZL3b&0yz$k;V-fG}Tu_L{ za_e>tNv>9zNnLQIpL`~G*(05Yh85%-db(!Wh<31UH0*1MxK#8ILCR#54taScdL?_x zD{~c+QgcNBl$8;ubpBXpes6ZS1HW8umtnAsV7mIyY~`}8>M(|&p=mg5RYdP!*N9Z& z7)*S*7S&aBEfYDwF1r#MR${=vzwSbLqvDfI8__A6RFnDdubTf1XN8CU$Zg=cY%4m! ztI6zva}dH!_?2Z!Q_Kc~yz`F(i}`YIF?Eu%<`0x)>~zGXgr3l~a_qI8NyI2&)+D`vUDJNXJO3!>%6w2S+96 zj|+E3e4gQ_^htWxOw0_nB6WD|U?x@3{zU1BQ-=RN*W$T;v*sGH3zB-+`LcsJ?fTRW z7j}X3*b}>V$dlbh-i5cu>3_Q2q(2F<=YbAx>TiA=`!ei`oh|f+60cn#y$?BQTPAf? z3qW`%rwe}p<2yg;R(BbjU_&Tgp`o4!#nQ6y-!Qcir$z;@Uh>5JB-M?7{9MB7Vaw5l#h3n$K zN%h=EG~~Ei%N(FE&B)RwSVHd_2O=H4s>Rbbr8<@b}jP5ZDz!4z=dN$DOe(S3fggIg@6}h+$$lddQ~J{>gV|60bo;g@|6Q#qpZEhiE$-jvlT4eY zpowo2LNI*;bSLE(+!b~ZaY?kvO>~v) zyB8iHDG%Vufmo42)QGKPe4~pE!Sb&b)r?bR&JrORUMDdIM=hAv$Q0dnkYpDV79^s@ zp;^J}&5^R@kBRwikBltck)^90l()tCi7%F<%$E*PmW&1BMa_Wer-=x=_VWXGJy$(>7%0}p zS4g{MV_G8OJAN%1S_^??B#4erV9otzmv;@xAF2$XI|FsI6p-)wu8p((ZuL#(cD2j8 zR!~(?5NFWCUt?dbX!L3uvT9{|2h*=U*lG^tI2wdntrFQ5@ctewC`ffemrC*D39tJrgC(QkJ`ir+ZSBrKK zo;G)AfNusVFQrl%*u81wm4!9y>l}J>BleA3fx17FIlk)DZNF?(IO2Ih|EB!pvDI;)9O}ESMTF`d2e@=E?h+zpk z>g-G_j!=Y(DdP2+!AoSCaS#m$sshwxK?m!7-HKXIKgo_OJutd)o3vD|fk}29jU3WiA2&URD*1uDeBpP4G#r%;v2W^pnxMuS>vKzE`yO95YF07VAazmK2y z$dT4X`WBA6EON+gDv-vYJXzQyqcfrF}D9INk161Cc+Xu^~ED=`UKWgwy|_2ZP!l|Lu5-%e%v zBgtl5V5+qg*d!b#9thVPldOI1;7z#(XayTr`LfmTGy4O>DAncF+_Ne&-w6OX(cYtYFy9HCy7leh?z&>!R7gOS4Au9u zuU(^G0I4*OvLmmEH26%mTY_pA`I+;tRLQ?2LUIX$XrlKVL=z?$Qb54KrAx~-95vTQ z3}o#|(@rD_N<-6@9xkEqv;Phy=Ky${&FPUQ&>sd{R=P8srUf)^Ek<IURoK&-5(Qhr#01-mE`OMna;uN#wAufAK35OW3ZN9`y3zI zuO5Om#T4*5J+mfv%cnBTp>q_gr+Y&fvlNqBUWC5)H}CuU0$IFQ{{j-<2e+lQGnPg# zFIxA<+M_FBFkMX=bj#~s>w7#2L9N-3&;RkyQP`m;LZ_s^vEmS8Jig^8++FouY<$zS zt|~}3e}>wGxxCUeZ?9o5wQOTLYfEmruxBO%&3VO^XDtRQD;X~OeRe8_bEhZ9((LUd zg|)2ct7~ICwbmiFB4GP(>uOP|U%Z6lzagl;UJ1i$3N2q3)L8ZGRGW6r;9$d12&`!=)95O2 zUF2V{|I*uZ|Hd2th&KLym9YLT$Nm4yzcSAMtJCsrS7c(_M_WIeCjwY179)LAy&|1^ G;r|DjI_=f~ literal 0 HcmV?d00001 diff --git a/img/OWASP_ZAP_PF.png b/img/OWASP_ZAP_PF.png new file mode 100644 index 0000000000000000000000000000000000000000..adf35461c689d42430e7529c5f8bef56f088b127 GIT binary patch literal 26984 zcmd42cT|(x_BM*5f>c2TrHX)vbm?6|rGtplJJN&@DWMZ=h)5GqdJ*Z;1wsd@ks5k{ zP?SIdgn$r2fDm$nXYX_OIp1%LJMI|we&fp@8F|Zk-!<24&wS=e)I&opMmi2UDk>^Q z9qoIMsHo0GP<~I*o}+xTgQ9OxQMpU#+`IcY&~~Hsd@aDt0v*D${xGz%-*qPDTfoCF zc^j-NK`SHYIWC_O6MVz^c=vtXDf%9beyXuEm&C6r7N?HAkU5<@a5nKn$QVodZmJ&Z zm=BQl<(>2l)WX~@!D{1};VIZ52cR47#A< zCi2GQuj_Am9}do#1T*|K7NVgL@%K#(J!Zr^{-3+_4xId&*}c_@%TIcp77sO`JbjWVs4ll zE#CAabia!i60UV#b9~e}#l}VLcsoABDPDC-7qXHX7YVTq*iu8xwd1g;j`r}9gWiGg z#ncW5({Ex>0sW;_znc@E%xvAo8<$y$glM~?FO8c`m_bJQIeS4wbHEnU*tIt!-*kJA zZJPbljKUAcA>`)0S&tz={XAQ@W^`_bS5sdL76A0X_Zfl5ti$k=b0&YwoH0e2V0}9Y z>q0&z?QPw3aNAKLZqy7#s_pVM-(0wc@X_z|3$3j?Jmgg$4Mi5fWSB_{UxQZTv+W0S z)Q3{@vYmKlx77zVD?=(6lN6M=>F`_Rm4B;A^D*yXzl>9Jm|cvB25E4o9tDATd7t<6ikZQqfz5Se#iio6sI zRbFsdAhZ$E#toH+;zI0*vv8#?G`4=x%d2sPjmUq#`h1_>9y5=Mo8ZZO1>!dJII>`- zZp4FdFD6yIj_J4ZE|$Mk3b5r@j9@zIksf+!7oHzP!q@@Xtyi|@Vc^bq%6 zRv(w{To4*9DawAfhefb=-^Z2q3Ja+39BjC%dn|U6Bg6M;d^^svrvUWrrN!egtl(oGr?R6>G~2UTlcWs1n>0$JzJHf^EquYO&0)B~W&dq= zvzm$G+dqcuhVk2q4sZS;><6m>kIo$ol3{bW7CTwy76*Ir#l88vwqbBZbf#0=b}Uc; zG^Qg|m-Dz=>QV}3Bf$v#URpb@&yE1#%hV{oX9gt8`cCGA)-{vh={llhhN{F7TArD2<_PG+nH(|L@p=2 zSh|#0y|Issmlw)>uJNJwQwxcIn8%yxJ@-44hkJr{37!Kd3jQYH?x$vzt7!!OaxgE^ z{#_RWQuxb?a)&$wibg)EpZJYKd=)UC_H``d3`}QiHT2#jHQv7l_nCYL%VX3tdMma_ z)~hLKbn$3tF_pO43|olLQ?u1sxb$p+Em1NmZ@gea)Tivs?3`@+9w^)YIB7yPoK$95 zG?J}(tch=byS!hmHS1&-e3*OJHkOHJ&K*+eDXlEdlTB`bKb`Zl%7ulZq)`|GaiA?T^p2x$LD5t5_PTQECgcv9dnHVZLO8IU*T;s5(* zj|&uXE4&ek@VB$YdmJrt1WP_YcP9}%;$@UK%en`^!2cM zd&6}~cfPV$E&o_)s7w%;)13mNrhzrRUJ-3^Kvca6Qi z=BvJw5I-#+wA_{zy3;pxb(iAO@RnbUKuIlb&|``gLfJO6wnB?Qhd=g(m;`6op`W!W z(eaHA*G!6xQcn3ut`I5lAWJpe6HKH>VE@ZP6XmanA67@E!N}e9sUqO@UnqKYB004* zd{cQ)HvZ61WG2~(^@g`VMgHi)yGQhPB}m2CB$unZ@6B2|=fVi{Q^L#ZlR{T+>lj}HnnIWW{69}fSLD8H_hsUfejNu(|sVdNy$SaBRujgRUzBbH-u{Dl@^@>V&L!@X`VgKZN#R$L5M?;;$`CV)} zG!;z~)}&gpTgAce_XkUM;iU)bBH8TSQ6=ucO%Qhv*rj}eq|3c{tItYs=e)%l{YvE# zFr8kaMe?1{`$^Tk*RStC+Q>v1P~uG(k(^^4_LG{oYWQ=nq5$EM8aDwY-#*i_S63-p z89Z6~s>M3W9`df*ly^PctXT=QOW?b<6_g^0GU zjcHS{w`cGp#HgJ@F$;kYGzTzi-}ZywE4>?SZDZc-oW7`RB4GMv&d?b8lTx;_o%&!q z7*Q_|CH1sF1|NbW2>#y=Fc>Mu`J*`@kW$2E;7&Zda>UpB&l;IH(W^?bbX#fRj7-vz zB?={ea)5l*^+N-b9i#fKcJlFc9qX(%V^Ev}sV2<0HT(#Bj4#=%&)~*dX?2~Q98>Wl z73TJ`EGq@28$o4#$dwO$2SFEP|IDLC5OZl(t+uFQk5d`-3HuhlH?I(}415lhpB;`% zJnrffq4izoLys%ul=(Ul4bkcQE2}Eskcp5S{nmO7;=%!&FP5I1chIk8HfW}{AD-qB zzi;G1zR!MUySBD`+G(#PYwzM3G19%uL$V*Qv%(@mz_a&kr7XtPpLHIzyuCMIb#3Y1 z1=mgwTO5b)LJ)cexpZ@68%g*c@T#!F1XSJ(UB3m3Q5D+DI|ztTuimL~#vJjlb%jpi z*NaW`?GDFMHpq<6CPFtKFI)(Qg)BI2`N>p*xli~Vq&odu)FGLbywH+R~7en}4|sS$U7HfU4A;@?m#wlE=6 z18V*I^TDBJFW26$E=z9jUY1cnLiZ;bCEf&Es(0K32=?{9B)DNjj=`6Gc|$X})qFwJ zZJ(aY;CON=vi?I+CR23}olUk?rMup-*7rDW=vG!@h zEMV1($9BZ%jV~5Q*-vrf1dsj$mfkSoHB5P(FzAzLi^;|&pw()KMKI(0apzfNS*-_L+;^*$;NK=JKnW>^i`odi zkhyC4Fq<9HM1K{mI{J-6i#1OZc>E)Vi?z=c`Sh3ZaTC;O`16C3CiEd=*pT4hRuNL} z${yOGaa87ORwP`__JNu0Jl1L2@U)QD!^r1WDJOi>Lc zApvT3LglIhq7%*LxMN0`)fSFWmtj(oShY^2SKxz1=KA5!&`kW} zn!~h3Nh5`=_B3b?(F)~_J}L~_m@%qBBtAgD{>yOOWGFBsc)%hcgV<)P7_m@AW;9)* zeGky%iV~ru=`Se0(EUb1o;4IsK+A zTxWWCPBJ{PaOww;ZY|`O#Kg+5nVpdO;%KFv_`J+cPK0<^f4CISY<5Cl-?R%A=5-o&GREtm=M(KV9F_)4b744MEad-_3rob!1_4j$TTWi$nVFBw(Wl z?*c5)_K?eOZawmB*V(7#kCg{ga@4|G)%E7vh+H*WMm#{T5O~+nv9zh>eDDSt;B{Q8 zPPw3Y>Jw$a=l!J=d&m(RPI){uaSZC?t&vVeXF)isPP zn5{;5wd|*1@(E7vZ;A7JTyIJsDP#GFL~(fYxd_l$)8TsQ(5MzK-~@Qj@Jhc+dH^20 zMtH=f2n-(2C^K1YK*mKc)PL;m;Kz0J3h#C>gGY`RuX&$|N+%0XC`1TsCM!h5g=CWS z=OnVo2-BrA6tge_A628Y)OUSN)t}E9Wo(6S8Kv>6Y`PBe$Ay11pn%E0FsDrV10Oo9 z0!hvnP3jHZkD0%p*g&xbeqA*`G~4Nx`_cQaj6b-1yl(+ysgcaGp&g+Y&XZv9e6V!J z(#lHubq58it)zMmEPYUTn@#N9{Suh>Qw4iED_p!0Hwl+a1OCEC7iKsf*NkjB(V*TM zaeKsZtK6c!Xmf;I^sES>!~o@PDZdHdL2e1MjqiL7FCZS%fUU%|`qCphZo)sy%u9L& zhMFeYDKCQHHd#PA9j>U-ad};O38kzg?yxg1{9fQO+JFd_2IB$K86P? z@5faX{)v=@ps%zjv@`s)f?|)(L!vudnl9JHSJ_P0fAPV}R?mZugTs@ReixDfOvm$t z)Ml9FwjX?L@u?TFb(`(^JjS)2KP)`2!b53LA}lO1=w83Ud|t)v!jkZVlyE6^oV2>r z2}H;Jj#Qs(lgdXfKei++gT~-Z$fg;RM#z*y*YB$Ng8uO!zhv&xaN`W-c(fm;ygq$E zqJ6IIssLF;d*NpSAP7-rF5G%MjS7FkWL7gu zcrPr+Vk9IZj}cDPq@P;hy`kjc9J;t@f!JZ1zY6COH;z7GzX=_928r<4F!${oJSBW@*5~msL^g?Bth?yJq0I4RH5+k_geq8d`@pqc^MW*)^`X z>#kHjQIu9Fe6WJc3RtiZ_@lF|q5v@;xNw8Z@O1}xr;`A)O}l%s_qiwz8e^RQhhvP= zt2w_|N6^-~{4U5ROYRp!VcW0T2kC5pGsvwvda#$dOcn^rN%0w$HjWHESi`}IH4`1K zM=e3WUwtYT(1*fx6v(dWXZ%RC3F) z-4G%B=%T+>B>!Jzz-$Y}>sojBCDOtIKDARan_%p)*2mCQL(wya`U1uAn5Eh>K5=sFH|)uWUW>sg=he_%uz2OQ!SamxO&_|q4obF=m}~9(_*BSd z&|Y5bOsnJIq|r{D_X*!2hJAj8$D{YB@!|3B^d|RR{3x_lg|2^SzS&}xQc30pE%Wob zXCF=!r2|P*z`9{;?xK4(Ao#BtkE{kS*6dm|%c0hM_$ujfRuTncK}*6NvTD191)Ne8 zoR@37fI$OR8;H9*a`6hDw)n4RGxfoKP2x5o*t)3>fgqVgl2Pc=Mbqk&>Y7rPA3~t1 zTgSWnLjhrj(EkS3htULE_cnjYQ2v{BVt@3N&)ew)1#vY$p9ZJ%hrR%^Ur3Lz;5Jg< z$Arnx?_m?pzQ5&(h~rMM%Z%IW`(W{G#h6-J!9SqLN!yN@++cexJ_jhOkLKLa8tYyv zSXvM401Ft1E{OV#8MnJ{Hm=tiC`a7NV8$$nmbD&tZ&%!H6FVR%P4ZAOO#zO=$k?8E zm4__-acpgu*d$E@d}IHRcM-U_(2cLO(BWya9slUR^TQR%v9i=SCb!IMgy#f@RtpkM z4VKdj#@wy&{a#&iwWEfR_=CcSX13LPETT7+qq{aq0k|DcZDplL=t@jl19Q!YDwNB) z?N~zn=^z$J5TMXx(D*)e*e`rcBoykhl^*vz>`_oYB`{W6UmYKzxBYz@m#2Q1hq8bL zCm$pO57=-mXjO@KCSMbqiYiP*0UJ#bP?AjFu~cGs-#(=%@1NT4Y$!Pddg*_Z_ifuP zkVun;sf-`LyL;RY>l4cHROcncllS9MLqIT&!k}dU+h&fpaZMc5_mh>PKLOLrQgY@VOj%}fz zj|}+!;G5{rF5dHDKkc4=QhGb$hwmVh22dRyu$fvD+Ia`E@g9ZFQChr|sK9Dgl=GIY zeDQ~3NN-f+VM((@>xjxe945)!iq(pdf}8h2xRNBfc>%UN?$f+S9lW*S?Q0oreJZ## z7{k)%gJgv_;;p-nFqsL}>xp{e)x;geM)}q?1Jq_S`f-9@hI~Qu2dJq;4H`@>6K1 z{|q@%Ccn~2E&@6f&hEO~C|iKuveMhC3(P1-D&yqrB?rEPb-lY-#Ye@Cs$rN(-L6`H`+ywfiN z;F|IBe1Mh*^ab@PzIW19%Ok|Vc^sI+4q#R%O5hFt5%dEzvi3JI8CyXqjLg(<+bCak zA~$MD=(wSA`JGU@9(i{L_08-kqH&2)%i@pvC=3E6G}8dGx}^kyXRNxy*JBGSFD~p` zV=g4ND>1|tUNltbps+{GVXK;ZNyD{8$)$rIW(phw5@^?4*4wQdtq+ z!)kk9|B-Pc#SYvE5|yDREzy4HIqp@QP+;=|yX_^)zj^AJD5bZVGpFnw_oMZqP>uqK zLn~H6v%6Xd*ygVoLD6=slXgD$o9ps%Z{+2^UCxQW7;;Igu%)Ed6_#DRoc^xMWZ16g zZ&FdFiIQsmi&4+*x%9tIE{1&Gv^FP>nH$M-TW6E>QA)mS zU+FhoY0A-e@oewhfuYkLIYO#lWimB`S=wdMNq+aHlhvGpzr(&~ z^k~lh^gi?J2q&t;IO?xOJrT}QWoS@XpxtXrVC53)dYJ;R|iQ;)|<%gU`|G(Dp1aDJ~}6*_zhPxc85x7RzRX3JWBX8>NB3290ca6;`5DqhftF5n+lxc=1)y!Yv@HD!6-SZd8Ft9s0C znsX1)PtL;OGh!PtVZC#Qi(Q9e@1N7f>aU$Xl6m~sJ9Cb3?HTiMPNG?)fiAmkVD7=P zkh;4d!UhUW$R|$}sX4=lbG|UylUVnsf$*;$%CLenz$&9cvpC^n9((MG7HTtDp&zn+ zuf1W@lTatGpkU%eY1~^9@b~VnNe)lB23(#>azbV%KfrO-I_OXgS=XfwkZ0b4FT;7_@UBVXYVs7cm{CuYI)h(a1@DR zTha=5U;VMGWV7Jmmm@0cKRdEqi2d&^X4<4_H3L%lVQOP7KbP3eT}Kudj_OU@54IN> zmQE}%`Wj4l!tk^uKwg=dr>s+a!2vQ!+=L8E7JRvnjWKZp)O{Uq^7Y(0d~X53G(9_q zDdNP%B8C>~o}jtjTG}8=^<+2`hCUC6ID_n8-DC#Oq$)2!ShjQwTVfA47%|gN?2NqT zL10cq-?v`I@lIix+m2m|2v|hyMc5N#qWg>o7FZ>o;lb%IC;3g0GBC zuXBJ9Yw0G=D!-M{%W7wJvoykg6I?Us+n$wCVmVQ1*ag&k)wP!C>ddB+TAToBCB@D* z-@AV-F2h|{g6%{!x1a(vSF8_g(e3t5#WsYJC|tt2c$g~#@MRE3$W{zDZ|`EGYRq`S zYZ?hTWk_tcmuaH!EX1kEOe<_5X`~|ykt7#RX%ZNkU-25(s&MjMbaYSOe=;psgIpSg ztsF_@0Kp{wb}w#?%EQ@G%RVV?_$NCFGvuVxB?IKbi5K$J8RoitTV^WrzvF+y5HKfo zE`_qHsU|-_eq{=VY>I8w?MlUago-D>rdYmhouA}X4LC}Y3g9+Vc01=nYV(b41lCIt z>|6$^Lt=ueY-y7xaWs4B_}G?>SHI_ZrBQouiqo0Q20z~ZioLb)Ag!sQ@$lB!14ZxG zl0{|SU8nKW!hU2D0i(%N@>{&MIVIJ-lK zWD3}$FKFP3oS1lr(Wa(MQWA=5tP)3wdT~;b@L!R}F7Cp#P8#8ohAQxDN#2(ke&l z)(OtX&8d`YKI@kA->gHY3x)mEnc~|uvpW(gT~x;quy%?S$mTJ$vL3*BhT>!>JI$hp zPrnzRgRiqQ80>G?H@CZdT_UzW=Ro~JS=1JDd(+9bF8~+5>L^+)4{@I7Eh9oXT@%!J z3{19YfYTp)oB2%a048#j!-}FJQf-@?rhJU)RCd_%_fqOTfngCd+x8d<>`5Z?xo(iw9jR5 zCN7~*jL|Kzd6;z+T*;rV-@llvqSOVTimplseLVlV0c_!;Hk1)L6}78wi?B$!lu@Sx zh?G)wg07(kC9WDRT}ebLZ8j+3CrtiU%wG|qff5lMSv1_*{Q3li(o(osm0qN7Orw9xAmx(q1%)SS!9J0!EIgodo!{lNY!s*M@wz|W_ zCl%qDPTB3415`YW?1cL=VD}WC8lc|Gqr9|B0se-_TLGJ2*0E*%r{D@d(v`N{7Snes zb=G!Wxp4CJoVuhDR^zCFUX`ATp+hy8fE_o#lTkmQom@+c0$g$Xk^yDr)&#* z%w>gx#QGmkN6b!eN}tzyKg_RCLEsj|l#ZPN;$^%+3!BFc4qxL;RY7#;zx_B)p%6wL z*F)C>yKWltsUGZtKgmVx@yktsd8++C-^OAy9Zj3qu z-ZXAM%JGR&jtDJ^HG>sX3u%bk{>)F@G3_$ad4O4-5Hou~BaG1F2HphGk?P@_v#Tlk z*Sr~YQsdO)3>8-{`K}eDSoZnDx4i|{@YnTpksk}V<;Oo(NKkb9bn&#(@&(^_gYbKi zycc^%4Rd2j<|=P4P!wy7#?=Rx<_C5Cx=!Rbz+Uso8IOdqZCbH3qA!+D;B>^viA{Pc z^1w>)tPYE!#A4c5m^e~_e$J;=UjI7mc}Y_<-7s7+egedVK`LiGT1&@lMwIX#^i}}n zU}J$1gnJ@`c{~9gL54$9?)e)c0XdkeTz=^-zp-FU5U`FczZ#Sil0BIh7xqyRv%Y?H zqftl&zKM~k*u{@?*w+8TdsJ#V*-d#Z5w3Y^24%!9G{1{I37Fs@vWfOGxrtwX#*apn zG?4Rr1Y zZ))!ptlvDf0jYAo@?l+GY8)*- z89iPdFVvWOXW4e z$l9ED9>~C)_|gSdrK8`PUmLk|K4{V^QuJje2wS8u(hD~F!=gtRblgY3ma(H&l}FIY z8pEo>erUyl zqw;t5KKgP^kbF3Kh|l}!I}`rrO0(Z0iMi6E*gNZEzG_EYI0_W;I{rPUE~8G0R|lxI z62|XlX1+z2EKlTqtwgRGqmj0e?u$+~;gnQ5Q2JeXxu5%E>nXu2*1Ol(4bSaF&oU7* z*_0%`w3RI^BS&=iOJqpx!KdA!Q474X*?Et{$|()n`Mp=c-WF`Rl_hM&fCcs|z67mI zQqkL$CSs$cOy58d?9Mf(>p0^5kY&-yg<;(~OHA<}Vnu%z=hi67<-!DWp2KzvkM4 zB0&rtU?K)&bLG%JI_3fQx6KK%7L_^fdX` zSDw5nmZA>@%Yc}-##tJ+=D#fo0*H99lj}$M2v)oPS;H6_&)s07GSEkpty2xa*t z9?YTDT>nDZKra11i1y$4D?jT_wS2^E!(J?-aaqjTibJ zwF8if8rF?KWCQdQPGEmgZAfUfHzuWQ>u?Lc?N!PiKS}m+UsNbwTqri(#e7SzzryOU zI1L=6HkfWM>nf%riqr&yBlRH!ol+`g8P9_~b{77+|Chq)9jihJl z9pt@gh>H@p*xjb7sR{C>uj8BV6&`$9<9B`ny$<{-lfV8|eQWB8VT@i# z{#HSO?eyR4HNGODYD|2~Ixxk1S+K>!ixzn4?rruSHgK1#zq|n)7gb@kejt$29`3c> zq}TFv;Td0x&*^SBuirertwL*;Tr9xW2eRmLy3Mms$v@;~?^5caQi;$MMgMxKO2vZ*&E;J}x$$8ePUV^pAkA^{{PLCJu_np>bNVL1 z)qiU60TETS+sBY&rY`&|a9Ak9zoymp8XU$Std0-;^68ct#dD%}e#a5r=W&!8NDEUN z^(je-of|G9@%+IX5PXf)bO0Y~o6j9K^4Q-HT4COL^059MPxayQSdarJo<37B0mGS( z+ccZ0DJ|a3-UpKQgO)qrqOY{2O)AWN#cU{t`N+Ww#wFC}Lt%E|@t-^~zr_XN%yPS0 zP`hR2`e#~0_v`h->H*VczGAlt3cgu-TUq~duKr`AT%E5W#>$=YOs2qw2E(i%_>hqd zg}JzmdMUA5^YUzw-zhK$2c^Hyxnv*J`jXnorZQfb$Xf|I?TQ!M<6 z*a{?4du)L7&JOvNp(_5dFQtY|H3IJ>{k4HK#$*ao=Ru7gk`Y*0?YT~gt+ZlA6%9-8 z?T24NY=w(PZkH%Q8=TI5!qhd}dbHtwY}F&LB` zNNyf(xNa_a!lRAs=x}MA(BLR8Hw=Y&i~TY#t~{jl=FcWuOE$5$CfXlhee+?jq=;nK z)sHi`=J$$v=ZL~KCgzX+FbD?qyym))u^aznouZ3+yy+TM{Nkkup`EGJ67s0R9J(yo z@rj{!m%*$pNmNJWx@AXV%_sWbC%kmfYHp<8r_a!Ls_$&vtp091pDa!8M;Fwk^z|=$ z@XE@C=|)^@@7y+X@7(b-r@i*4ni4W72%5teQxn^`5^yVWxD5(eMaMR-L1P=YiE?qI z_1?v;=ewGd=$~FemUwc^9=S61$QC%L+8h(~+p^tymC>q|N+KtxfwG0S5dPLa_k!|; zkcQncJ?j5;5{|~4Y@dS6Uo5tVw*xd?bHX5DPUrDfjx>((G)!Eyo@Y-8fj@pg`FfO| zX7cVnptjiY6wg1;kK8_C0{?O2$&gym|A*V2KYx1~ADw7oo)jkqQ zWluLUWj|~0L`78-0cQ8H=apo(kEDt|4`4rJGUR(9+;xXqlGDvY5>O>Y^eR*sd#PNk#$t!?8A<0WZd5MR0LDT_xpP#KR;vMg zG<&CTRQ|M8sU!ZmTR$(IPQA?_Lsjz0dp^@nYtV)ob_Hv8Sj8*$!=fybSxU@`C%c=#5&NRb_<;szL^8M*WYi9xfo~9)>9p z)yUMds)Ul4|9N}o2axo1Ad^)ZfkrBuU98q7l^#;1D&A<~R%u~1zxtuTWXjS;I%Fe) zH#N3KL+rRLB2j-dFRYBIz>F@NW(+9hosUQ3nVn)H5eZ@qb0Ff4@WkQDVT)IT*G z0^2k0;1c+zD(AH8YuFe9Hyv8HgzV+9C<+BJS=m?-Fju4Gm7Fp|-u?`#E)umvTuv?~ z4T~kIrj&ce>up~0NqxrxObja49C#XgWlR)(C&__lBa~--M8hiMGG&%!sz8-)hB=+N zrmJNY$upD<$$lE@*kE@616$JL5OFu$H_0)zD>s&?2B`<;wr2gWSUlXYu8rz!m=j?o zLE!aWgXYha^`|Xa&f*eAujJ+U_Gh(G7HbCcw7q`j{8DV!6*Np0!AmV zD5ijwBU+q%Ow5L6XMe*j{l7m4x2`Z>!+Ydb+bsvS??3Cjz706gti+B`wq`{yD^I@@ zVZO`?Nf1{YRs@-Ir@nOUPB?VWZv!=L1l1`xyslf%OaxikCWAkZj!X@DB4%&mExnQ< z<&#)f$>*Ec-s%qf&$E_;500FgXIs*pf^T0O;AfSKvynLUM;G0_R$-yi-c+a;E7$<% zxtO7uT@6jIP-kmfTjJ`qNgxJU;(Co+;!uC`TrZ$ct6{76+@ZTvP~{y7cWt`d9vF9^i&O{cPp1(N;)j} zc0L3ipI!_EmBbbw-2WiE+Q8BT@?vQ3yB(&+1X7**?ANr;)lwzQv7=J6OC9?bB8P;S z+EzNdx+5%|A~^~EZt0v!Z}6RvrCD!)Rd$$C`%Okqd3L$4|6B|emPq*sdt>uiuvu)i z@T=++?-?g|Y+m%ec5J)w$Dng`+1{+M$E;tmgPh8&4ysNiSjlG|5s*)_$y{{A;_t@~ zu;?#%SUz`8g&wR#Cfo5gdN^GpuE48VW8xV7nY13fMq>R;CJji(7I_@$;;p& z*;*+pG7iCeqWUxbU*g?4CyBFdpeq2e7A8TwC75CavU}IUOD!gPRVxTd*p6xz`D{q} zwfK+oYgJMk!RO5_IbZkOt4B|JH;c7Pn)t9QJUXBnJf(-&>Zb;Kj(*pSFNzsRFQ|(b z*FD^O(LEwF;2yJI?6-LnhkGpF(GsIyO_EKEFhOH}5`F4-(HIHq6qu;+VCU-SOR?maiW|hc` zK_ITdnOL1(&&N-{k4ve!+q%e~Jj*0as2hhoLKK8O&fiENHfrv<8VsVnn>{ej<4sow-F2*5 z2mrqK)MAi(@S7uJ@k6L4SG{;+17p)LD@=Cj74Ce6gLoFxIDUg!PkqGgwDKslHg-J> zuqe!FvfEVtX~N^cVitm$?rJ)+%dNYMPxrP*pXc;Ox+D5gx!CX||JF02<*^;Fz7WYA z?(os=s`IwoYDa>UI7dH#*t#(b^ru9CDbdO>zYA;iuiNHl?=D@vnb_7tv`&TyLeGJC zg4?x3>$o3mdE^2LAk@vexyaa2m>{MyesXm`c(GtNQx_Su8knR&uvQ8PWjU<=%L4AC zzTPXv6cC!@Xo6gOZ^#cOR%4gZ*Fiyl%YA=}cx=ETVY5q&n5ArrpKMa|Z|}29DJRS~ zJ!Who9la1=Twupq9mHYVfGYsT(&u#w?TmE^B0}aAv9>Q@0@su=2%r z%$fj_`VAB1#%6fvcHTwAK7;FhADg`*Dmu1zWc2U}voIiJ0hGX~+&^~m z)Qd2dN&2NoOjyd8FXaywv`Yp*=f>9#8~@1tZu-u1Prcd7b`*OC^CVv!?!JE{o_5Eu zw!Vzg?7@UZ{@eS5rOdZHjBLV^%0gWu=ca#Q?7Y(O_sZ2wuY$ROAp3LW)tGPiHrrxb z+1GcIQta4s3PWwOKd9v-PstT=oOzhv0f!bGOSX zyVBxl^I)R3@mM`#zFYc(ZVtHcTdp_f1Zae)>PU%z-geQRB6R>^Y@XZ=zKZOKoOBVvayd&R7>&W z=6)4?zQiw0Neg#1=U5>kIl15b%L92cQC(Fb7)8ac4z_?mTa0YT{{YR*Ln-Th2Z5!I zc0<&ZN*m@zxcc{gQ)Da1PKtc#Vv%abnGFvf;`txo7+VgwLU_cV`2G6q-o|>&|9Qym zyXWWfj@pi1Dk>LxlKAp>HG7`#OsCi`&Wi75U3Ne7H{$*ad%HFEWN1+SrTOoVDpk>i ze;-g$-C_PNeC8j=Bh(ZO`H$mWs?7f!en&B4YRKNXr|g|6e9%_jZ7baZ&}n*(Q>yh& z4W#MW)Z7`lwm7qkjJ`X+`z@uYB^=$kNq!WthCpunKI~pkZ9T_?dF`g?z}*=v_r}}z z;OY8lU)>UJOo*Np?sNIw^VwORKjh6}$&r?_T3ZQ_FY)j(!0AJsGV-(MT|V328p6O1 z$(3%J z`!7cOWKH?0mm8F;%gNIW^*b_*rl1$Y%9b?KH*cF>kgz=#WV@%H3%27=u zIaZJZgk#MvmRIUkY{5RsdZnuVv^x>zbYr#fA6eeOm?m*gf;Z7A)Kx(~aycOju%QS! z9ecugUF0cI=`5Jov+^Gy6}Ny$@M4vYF2d_yqG-pAOEI*jvgns_b$vSb(5gj-0V8O` z%qYe_h5EHD%kYfjabety-%9%#gDI(C#!O5etcdR_8-^i0u<;PpX)pC-+IwQy&7)TK zN_@9wn}?H5v+>8?*I;8r&-XpoPjAEAd^h?~!q~m<8()dFvO#yMppQsNPm#Ozf~0Ur z_+H#0>5En&nQ${Ur38rHhj$O12zMqDPESR3LqES#EEx6UZNWo#j@`iVUY)_qr{pw; zQF*wmU_&;>=;@lCERS^suhdY|HMaWS=FJK`A~f~~+G_QIPiI|fx#0j6xcr%WvraNG za!g2RKe`nLdG;G2QvAaPv)3I)RYX_tzrDMxyDBe94Dc1_*_p0Y$~|g#)8(%GF)7b} zj{fd&oUg+-b`PPKO*K%S*IYam|W{Rm*pdTMc;{_(RZif+&wN+3REf|=4h;zPEqu%@GcL4eG~l5 z3XfZB>ru^qmp4FoNb=RwYB!%Hr}X;vO_^!${_!e3n-fF4&adLT-1$g5z57)%p|JOX zT4^ahFzFq$noU`(JUoSk{#F^W{cKW2Kj9K^{w*CJD$FqHAU{Pr9O7oMc!>6@&C111mO>R zQVaAZ{`zd>zjP0Kdt5yLAAQ%tAxx}b+d2_1dvKzq?oKKy`=)!ng`Xvc@#ANa`eVY6 z#o8FNGz?U)gHESjIZylwkUVV3NvCf-gA>Mg^-dr4>X}Rqkd3XEW`2}-ggS8A>x=?A z1l?Sph3eLMOMvr%ZnXV{v0Z*Qi8&gl4Cf}gMN(`d*HoV6G1Op-oo?G``%EgVZjqA&-|IkQZMwcVT| zUg71$&2u*35K$Mx<$02$7J2az2aJzh2th#ib4ZJV3{Fwm?+8E(_0_W&^>W8#1fa_< z>bIGwJ>TXJc}(EIKH|$?uq{7lZonbdkJchSgQ@iNE5x)pZ#^s3@D#P$vI8 zQ{%YQri#==|(E#fz|6iN37P&%Mg8p-TM>&skLb!mH-fD<@-!IKb8^L%4j0ln(QgO z%I-^Q*K${(v9U|>T+9*iXsKG3JxleXFOfTAjO3>#YJe}{2`DS+o!_HjNvQr&7!}h) z<&;o&u!{qO#g~z!AJ7avQ>@E$%&m~cf(^B_h1eo=aWJoaT2#zs@s3$bgj~~X;8);p z9fM4jJTwH5*;eu|z;#^&`A1DIwx09i zx#iU++WOo^T7#!i()`krzxCPCDGJOmUhjkdwpH3=d&9V5Fy-h^_ZfSA)mJ10u>vp) z8@C_Xm2luS>jB245Xd$pU-$OZbuu(Qh0^9W2@Ah`MZ%-3YVbtHs7G>gYv3jU(bw|X z9|zagy@NmAregJ*4c!e=XJs3Vu+D_AOxJLthK1%EsudhmEX;YPv?yV}`yP}oMW0i@ zc6+$3gFh*}mNeGu;P=sl_9S5!YIRCc|CPBV|H|Tn4FAsFRUis~^pC{8dWymX{o@l% zWytrB!xua+{`J_u&V8o&e>wbfrUu5P>YJnKy;2XCO3vDVp)xrKk=L=Qwx2DmY-ByW zPIKBpOQYT@1_e5or@mybrX|A-XE4Zni_N#ci4DVG`?P9H~a@o6!KCEKUJFUnsx_|0hJ(&>VSK-g|n=bu8wI1l0>gL1kAM zew;tXoe>#Vf6?ij(;3lAQ-xV*VyL;7cr67TJaAWeQ6M0H1CTfdc|yTLrxgtOuD;;v z8jrrZV4r`(ZRfL{{3VNfb?wD=%#4cv#tP0SSYfP6`Njv?KBWhhFMY#y4`c+o9Fyj5 z*jBWyb9_IYSdA>s`r|A-Q8d2r-bDYrn-n3wK%!q_VYe8GBdF=5A(L6`%b6zxF%P`P z3j*60h;)6s*c!QpR1+|<#jbk$Brvq-hbcqbZ+KNuc2x;XH`>bSFGA&(euFE#=C&Yq zCD0Fby}zC)XIjyicb>Az!C2bM%O((Msj6f??u{q3Nvahpu7cIRbLU)KStnJfDJwp> zvsDdAs){l<_ZQFCnfAGG!%qEOkC+N-(7n@#DmivA_0k1*iBZYfYl9M$Elhq?U)dRa zD7ic%bA`i5Kr}tFpoK@xD-}_7t7pK6c6~MF$zf^iM^{Bq%XO1Q`$y?Y;i*((-Jtz5C*@n6v*)sFaatsQ;1l+$F#eagD9I%}h`zbmh|3VX6CulI@VXaL zwZ;b^kBiCs1+CfUjb|L9RMvYh0o^D#l?cUeM=U>X_3*)$;{e2$ zpT}P{3VA?l>*y%R_eEmJ)NFjBA$y(4s_Lxx9BBfwjvgoFhp4b55^(E->>s%~G z38_`LCU=aLAESN6T2tg7K>xNFARB##J4x=OTwwnrmw&!Q)LxPo@1sf0* z$$jtZ{$1DJqj+(I25bk4-M)2(KgueOG(r`%d*R7qQGb1?fTo)G7WDyJTx7f^x8%<{PaQtko2GC6v1>N4aiy8`A^xxfSeBZo zK9Vx3IHddP3FcU(Xf1ptUOAXqCEtP+RH@Zc78iYER-YI}2no=o8(vlP4$WC9h#Xat z|9D+$FGA@~FhNc1Pm}E`3o~Fte?<_r?We!r0@o~Jd2(!_s;_|?XFYqVB zPpNQ)1c&3*ORBB^S(|9i&h09HAct0lBG2u_G#$l7M-q3|73K|vH z!Ll#oP%za<`(;%npbn82`@O7v;1jT9K(kC%&QP1lU9(N%n|KNo;!U6r=UcsIbW$r5 zmI+A@`4c{zEX*P{FvL&nkA}*}Vr#A-xN~0D>69OCNIlv%^w_pvjl^drZa?fgi)CHP ze#>d9^7B7>E^x>)5WZp0D4lZSC}u(=3>lL*RIv_AYqTWlg{zG1MFd-%?~t{cIr1eA z6_IU9kGPes=HAX25XtwfTgz*@x?I*QThNc${ODu`$zxDC?zT+}_dN@uzs9CKsPns_ z!YtR<4F2aEkmI9XfsL)`2C1>b=jO|A_L6N+gymg$rpO1KXnLW8OC^i@Dy{a!eW01| z?IKnyH3R=#0VScxVJhg>onM+Gor?x_`nS;NJpBtuh+qE;M08>v|LNY&vYDo#t=1UzR>*6PJ!-=p8UJ2=Y3kSf^$*#EMzo_o@SOls%vMVMu2l^g9x5JHXKuQ3%!a_#(q5?SCVd}ncDFyS?68?dthQ6-3>5{Sk&Y)3-M_GaDsoLWbq(0ZPZU{ zLG$8YvZmxaAv8HOtuXSp~pqDu$JwND~ z&V^hFj`E69_2ndQTc+{gC!>BK9WA%k=Zt~vcVL&Kd_LfgiZ8)hinj7tfZCEcjNqka z2q?|hRC|5(K9vM1=72an>05Q@73bTnrR$j<;y>7N7GOJ@ln#Q}lb|)9ilfuY zs+y^)!|c{&oE<6k6{|7FJKIoGIDLuIEMF6BtVwm+-AeVcyLQ_tK7rOg=-~bbWS%c= zk*^8BAwZpjBiEyKP9jM?LHB6$4!fAq?5C}&=qyTjlE$YpjecyeCT?ZQV6XY*#i0~# z*`tQ?t7wl&YQvYI%tAEq`!zu}&@ztQ^)|h!dmTbC!@D35IPoxlC4-+MRkg@zn0%i@ zSi0e1PyzH~eeqSBn&N%7?gzfH3^J3hoquWYeg8HyN6RY$gxM(p zkUKK5L&GA*%IoSZ1wT=UfJC?Zvk3L=>J)4@ir$O9<8CV$;0hu4zlyz>vuo>0oIjs& zK*g~5W;?1$&=J)nJ`vN9+-bu3FWiz}@htXiWE%s5p}>BYI!6 z9U+~+rvvN7z|*7J(+IL}YLnp4Bb%^Qg&}Y*k>o!>-$d3+yr3e+vy$&u>n7XjqZoB# z2Cf$uc^p3ESl7NGz}jNok}zbj!|~oNz+F_LEBm{4I7%651=OrBn-UwqGwkd8D_F%PFP^c;FXx-+`^%7?a1W z+sqrV{%Jsp{fYnMEO+Obg85@R(Vy#5_$HUS;H7q?y}YscFl8`NPr)6Mg$dYVhXkrG@aZ*UL3)X9=$cUQiHKo&t z?l-kAo~Sl2U77pdQ;FTi5X?nin9xfrY_EL6tzbBp9N6b72&BMgYcw1R^tvY=LaXOI zFU+nf)t71%>Vu4U4r4RS&sA!EVv0c#g`Q^>*9IPG0P(sGJ(dhF5Lk*omcDuLL}5>Z zE{WlL;eU#29>GWasbemkOde&oOx3o$NICli2A&;BM4I3>S-ur}_^l4M?y%m40pVBc9yK62JN7gm6w=_6%W6W8O zRQFNlw75#9f@@tH@06bX*KH%kRW947&$|+o}Dx1SdVG~8k1_f%`KI7NuPzSWzu_VzqfMKU*#)M z+akU3rZhY0XX}~U^wD{^Qof1Xa5ELLPWrBnPk zTSoW4M$5IlU)!`geuLrs4T#ArHCEXKA+Sdr^i%Yt$N5_n>Uisoux~eMeX5_w^^*IO z3w1^y7>i1-`GooW&DY4Xb|>+N2RYRVWpg)B{iLJH!=`$OF7ap{rE?KWdeh!P8=EF& zV&F=;Mpf9@kSWk^Q4Ik@EprQOar0 zP&D%&mM?<(wf6kZ^Ij`XX_&7jlK}X7aOsx|q|1fNY-Yx30Vfdd}RDuLJmXJ+KJ@n@?iGaJzN z>XwA6x8SXr57U#QOfk>d{Frp`D2sGzw}ns5$4@EtykP-#UQAA}!#-XOk&`GE)EdB> zIAZeSWp8($O|Fa91gJwjjrKBc(H1B*bs)W-O?%P=Krl34WFmhY>lNjyFEB!1Y87wv zcs9~ZA{2_*e0)5iAYN^YCOY?3@N{*qAxv0f*Whf)+p#! zwsY?NaUo0;xD675!!hF6Q>7h#`yT?WRe}q1Aye1$b@WQ zWiA&~*M-%$6~TI)mEStQTAH9>wAAhCm9KXfCr$C41e+wBl2ecsfa+rI^q}npEwJWT z4Sb{Gmr_ZlCO7AQ{G;qgR{%^=a>2qZD=HZ){@N)eL(XALt9v8;CPo0y+D7$>II=wU z;&hCjXO>^a!EJovGdeHrp7@pxG1ygw+^2S|u!;@=u7T;mta^^04X45Kv}>q|(|D9L zD#tZv)Qw@Fvf04AjO7nkWmZD207uEcV3OomuKhu1!YJ@qpZHL{DT=bxs`hGgO>a+t zz5hzKn0`A8>$sc1>k`*p<|EU1raIzyvl6zU)6JQ0j|pV$bcs;H9}ErB^%^Dzg~=-k z_n}Fa!9Jd`VKcTcVJLG=^}2;-a`hsQ?k|xwkTDjq2m*6tNQm%^TD^kStE5Ao>gTV3 zjKx?Ny6nWHW&5RaG|eO#bo>>AroKAqWC}TTD2Ma) z#o4#c_U*_sPc{a+z0NSO#FjsWkl4BZ=Vh>dE-nuwRV1$7@YP$s;6q+{b$jZl zpJ?wvW)MY5+d$F5tfB%v8`I)5pR-jHftm)hG_}h}r`q*M#%(|>pNN+XVW71T+W4z%s|!L z*X`o@EJ0=arlMeF7SEBNT(Epvu5gr2g$#h-c!@Xajl4J>un^$;XLd@`ojh!$CBOSf zuOphbbozeehmZ{J!Rw}gLdyH9sTAETwQ?{S?08e3?96{jhhZV$9gpgZDD&B!wlH;G zTT})~$K2=ypbL4PG;rHfZ%F(HoCce%os$pZOAc)85ZyTPBG(n=K?aDF5W_|9V9Q!c zoo;n?Z5cvPP)h}k%nIyIw(8%638nuZ36tFnWZ02TC8q$xmYUtyGcTRrRqMr@+qv<; zJbqDDyrp>^skPZN1~c6}4(7~|&`NZr;4~h@$4an!x7tocQ9sOgafPOSLg zXlq_D9#1x&g`Di^XTJh&EuLvG2g0n4VPjLuoWXPLtPwff=2V8XG&s<+hoxQe%aWsXJjt0_?Yp{Ef?f@Bn6W|hy z4vD>N=Ye1r*6mI%+xC!T35MyCYbSrHGPBdaaM;bz@jWwJH?xUWw;zw=TDfcou0~PL zDaFZt)3!as1smW*v3f0cuzO)i(mPPw4W$~-%SAh1(xeY_fUCM~HxyCnBxT2ei=Yv>d&WHL=pEfZWc>WMRxDF5Bt;|3Rx!PAVjnPTEl$ALQ>RHd@0Y)P( z%urQ+{`xn*RLqD$qc5yMkxQ9cy*z%x^(?7x(<~`+#`eQxHpnlSroPB(eKyrNrXnA=2@3yA`9cH z2yrq&JtMc3?jFH|Ta6K`9rgtqWx$;hfjt-f-T>!4j269{_mX!NsPMpr*ZG0EpG`m@ zWYYU_W2V)Tn16}^-S2IfzJ$(H+0RUG=@EaoAK)~3HIBN?+Wze-N3GNxrnF2eiDgN@ z@BQm3vd=M5n}@%ZSqaoE9L{pf6Rum7Y@@^OrT1412wZNvB!`R*&8a@+Jv|#6H;j2o zVSX;QUjHco|1B_hgE_^;G&l=2xPAB=Bm9jhPDy{>&a|Z<_$ASL_h0-r;Cl1*>``})4x+fSGx5iLQvv6Hn+uV zJlUVJdcx2)UoewRTSmK(0~~H6kMhrZv|9CX`8~ zI8*O&)=NEX@I~GJN)XClpT3K4*VSV=Q!bw4w+kq={3-zmsx%=vx{(tqiVW@y4w(0z zum^uJGVPZ0oME2fW5XW{;skCu#r)3*Sy2b>TgZsgksl(rReh2(-C4XOD%U1SD*Y4p0eI6l0{-$gU~@|6bW z;*h`+_}kB`CfJMAy2<)&tMfgRSI(5Y6KB{Q%mF3{{z8VeFQ01^#egJ$cv{NN7$;O@ z%459SJz{DNgfzI zG8V8K}fq#|bJ zNVt$woG>Ykdt9(*DXjbcO#$a8m_o_ANkO40{M*{(LLB9lJpVYJvR)$JbyonP`so#d z(>*ib*4hbiiL<-+Cau3wIte8T)v$6gYuVqaG+^H=_I+KV5q2d3d@MET)+Fp3F2j>n z{aZ+i;kC+DA5D%Zk(z5$=L$xiGO>k$|Fb-l0K0pH_x-h?yC}HjZ5e}f4Y`e~UwNqH z%sg4?qD}w5gG7Wp1D)2B3%l6nO}F92MwE>YEb*zRCMBROgpq#P`~)nR2My z-)PW*Xyi%^F<*!nAPw$Y|=wch{VT zIz^)xvU4eJIS&tPkQ7nOgxstlN)Zv@itB>gp-*nTw5^bLyEa3AxIB~T-Ev!#J7R*g z(JCQ;@}ND|sqlg`>nz#Z(TtsAh{L7SqtUWFCRSZq&kjt*8dE;#m}6Au#ml{^p)_Mr z>wKfz!FYlsY7eAr-4qlx&?Hd*Z6u-C5GMLi_%PF~S}5(IQLl7qe?eTgklB_mK9V+3 z`h+~sYW2XJs$DI5E^^=BIQi}Hyqs&w|Cp{qmao9;qmBLN5yD{FEt2`k53Ui&3o%(& zuLN+y_;3$qUsDL%$`sH{JgSbj923vCJQ3_>@7{8Fw7X#@{QMH_@o()wLn=@#ZbRK87aXjUj1g+Ww@%v55V2A-# zTJi@v!ti1_Lp~)H&KWQWWWWIL3QbcUAGs^hd^18mb(6}N>%G8{|(P2T2}x7 literal 0 HcmV?d00001 diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..c19836d --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..634af54 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,71 @@ +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. + +[[package]] +name = "asgiref" +version = "3.10.0" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "asgiref-3.10.0-py3-none-any.whl", hash = "sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734"}, + {file = "asgiref-3.10.0.tar.gz", hash = "sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e"}, +] + +[package.extras] +tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"] + +[[package]] +name = "django" +version = "5.2.8" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "django-5.2.8-py3-none-any.whl", hash = "sha256:37e687f7bd73ddf043e2b6b97cfe02fcbb11f2dbb3adccc6a2b18c6daa054d7f"}, + {file = "django-5.2.8.tar.gz", hash = "sha256:23254866a5bb9a2cfa6004e8b809ec6246eba4b58a7589bc2772f1bcc8456c7f"}, +] + +[package.dependencies] +asgiref = ">=3.8.1" +sqlparse = ">=0.3.1" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "sqlparse" +version = "0.5.3" +description = "A non-validating SQL parser." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"}, + {file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"}, +] + +[package.extras] +dev = ["build", "hatch"] +doc = ["sphinx"] + +[[package]] +name = "tzdata" +version = "2025.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, +] + +[metadata] +lock-version = "2.1" +python-versions = "^3.13" +content-hash = "da42791ad2f553edacb7bc8bf2c986644bf58702eba4adb5d9bb08dcb60aff76" diff --git a/profiles/__init__.py b/profiles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profiles/admin.py b/profiles/admin.py new file mode 100644 index 0000000..09280ad --- /dev/null +++ b/profiles/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from .models import Profile + +@admin.register(Profile) +class ProfileAdmin(admin.ModelAdmin): + """ + Configuration de l'interface admin pour les profils. + """ + list_display = ('user', 'secret_note') + search_fields = ('user__username',) \ No newline at end of file diff --git a/profiles/apps.py b/profiles/apps.py new file mode 100644 index 0000000..0955718 --- /dev/null +++ b/profiles/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class ProfilesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'profiles' + + def ready(self): + # Importer les signaux pour qu'ils soient enregistrés + import profiles.signals \ No newline at end of file diff --git a/profiles/migrations/0001_initial.py b/profiles/migrations/0001_initial.py new file mode 100644 index 0000000..c88b37c --- /dev/null +++ b/profiles/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.8 on 2025-11-09 16:32 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('secret_note', models.CharField(blank=True, help_text='Un secret que seul cet utilisateur devrait voir.', max_length=255, verbose_name='Note secrète')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/profiles/migrations/__init__.py b/profiles/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profiles/models.py b/profiles/models.py new file mode 100644 index 0000000..f5125b7 --- /dev/null +++ b/profiles/models.py @@ -0,0 +1,22 @@ +from django.db import models +from django.conf import settings + +class Profile(models.Model): + """ + Modèle de profil simple lié à chaque utilisateur. + """ + # Relation 1-pour-1 avec le modèle User de Django + user = models.OneToOneField( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE + ) + # Le champ "sensible" que nous voulons protéger + secret_note = models.CharField( + "Note secrète", + max_length=255, + blank=True, + help_text="Un secret que seul cet utilisateur devrait voir." + ) + + def __str__(self): + return f"Profil de {self.user.username}" \ No newline at end of file diff --git a/profiles/signals.py b/profiles/signals.py new file mode 100644 index 0000000..a9f8306 --- /dev/null +++ b/profiles/signals.py @@ -0,0 +1,20 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.conf import settings +from .models import Profile + +@receiver(post_save, sender=settings.AUTH_USER_MODEL) +def create_user_profile(sender, instance, created, **kwargs): + """ + Signal pour créer automatiquement un objet Profile + chaque fois qu'un nouvel utilisateur (User) est créé. + """ + if created: + Profile.objects.create(user=instance) + +@receiver(post_save, sender=settings.AUTH_USER_MODEL) +def save_user_profile(sender, instance, **kwargs): + """ + Signal pour sauvegarder le profil lorsque l'utilisateur est sauvegardé. + """ + instance.profile.save() \ No newline at end of file diff --git a/profiles/urls.py b/profiles/urls.py new file mode 100644 index 0000000..e1a2622 --- /dev/null +++ b/profiles/urls.py @@ -0,0 +1,20 @@ +from django.urls import path +from . import views + +app_name = 'profiles' + +urlpatterns = [ + # L'URL VULNÉRABLE + path( + 'vulnerable//', + views.VulnerableProfileView.as_view(), + name='vulnerable_detail' + ), + + # L'URL SÉCURISÉE + path( + 'secure//', + views.SecureProfileView.as_view(), + name='secure_detail' + ), +] \ No newline at end of file diff --git a/profiles/views.py b/profiles/views.py new file mode 100644 index 0000000..2de9b16 --- /dev/null +++ b/profiles/views.py @@ -0,0 +1,59 @@ +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.shortcuts import render +from django.views.generic import DetailView +from .models import Profile + + +# +# 1. LA VUE VULNÉRABLE (IDOR) +# +class VulnerableProfileView(LoginRequiredMixin, DetailView): + """ + Cette vue est VULNÉRABLE au Broken Access Control (IDOR). + + - LoginRequiredMixin: Vérifie si l'utilisateur est connecté. + - DetailView: Récupère un objet par sa clé primaire (pk) passée dans l'URL. + + PROBLÈME: Elle ne vérifie jamais si l'utilisateur connecté + est le *propriétaire* du profil qu'il demande. + """ + model = Profile + template_name = 'profiles/profile_detail.html' + context_object_name = 'profile' # Nom de l'objet dans le template + + +# +# 2. LA VUE SÉCURISÉE (CORRIGÉE) +# +class SecureProfileView(LoginRequiredMixin, UserPassesTestMixin, DetailView): + """ + Cette vue est CORRIGÉE et protège contre l'IDOR. + + - UserPassesTestMixin: Ajoute un test de permission personnalisé. + La vue n'est rendue que si la méthode `test_func` renvoie True. + Sinon, elle renvoie un "403 Forbidden". + """ + model = Profile + template_name = 'profiles/profile_detail.html' + context_object_name = 'profile' + + def test_func(self): + """ + La fonction de test pour UserPassesTestMixin. + C'est le cœur de la correction. + """ + # 1. Récupère l'objet Profile que DetailView a trouvé (via self.get_object()) + profile_to_view = self.get_object() + + # 2. Vérifie si l'utilisateur de la requête (connecté) + # est le même que l'utilisateur lié à ce profil. + is_owner = (self.request.user == profile_to_view.user) + + return is_owner + + # Optionnel: Si vous préférez renvoyer un 404 (Not Found) au lieu d'un 403 (Forbidden) + # pour cacher l'existence de l'objet, vous pouvez surcharger handle_no_permission. + # def handle_no_permission(self): + # from django.http import Http404 + # raise Http404("Profil non trouvé") + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a72ca7c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "django-broken-access-control" +version = "0.1.0" +description = "" +authors = [ + {name = "Your Name",email = "you@example.com"} +] +readme = "README.md" +requires-python = ">=3.13" + +[tool.poetry] +package-mode = false + + +[tool.poetry.dependencies] +python = "^3.13" +django = "^5.2.8" + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..2dfbed8 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,43 @@ + + + + + + Démo OWASP A01 + + + + + +
+ {% block content %} + {% endblock %} +
+ + + \ No newline at end of file diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..61ea249 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} + +{% block content %} +

Démonstration OWASP A01: Broken Access Control (IDOR)

+ + {% if user.is_authenticated %} +

Bienvenue, {{ user.username }}.

+ + {% if user.profile %} +

Testez l'accès à votre profil

+

Les deux liens ci-dessous pointent vers votre propre profil (ID: {{ user.profile.pk }}). Essayez ensuite de changer l'ID dans l'URL pour voir le profil de l'autre utilisateur.

+ +
+

Lien VULNÉRABLE

+

Cette vue ne vérifie pas qui vous êtes. Elle vous laissera voir le profil d'un autre utilisateur si vous changez l'ID dans l'URL.

+ + Voir mon profil (VULNÉRABLE) + +
+ /profile/vulnerable/{{ user.profile.pk }}/ +
+ +
+ +
+

Lien SÉCURISÉ

+

Cette vue vérifie que vous êtes bien le propriétaire du profil. Elle bloquera (403 Forbidden) toute tentative de voir le profil d'un autre.

+ + Voir mon profil (SÉCURISÉ) + +
+ /profile/secure/{{ user.profile.pk }}/ +
+ {% else %} +

Votre profil n'a pas encore été créé. (Cela ne devrait pas arriver, vérifiez les signaux)

+ {% endif %} + + {% else %} +

Veuillez vous connecter pour voir la démonstration.

+ Se connecter + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/profiles/profile_detail.html b/templates/profiles/profile_detail.html new file mode 100644 index 0000000..59ee55e --- /dev/null +++ b/templates/profiles/profile_detail.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block content %} +

Profil de {{ profile.user.username }}

+ +

Utilisateur : {{ profile.user.username }}

+

ID Profil : {{ profile.pk }}

+ +
+ +

Note Secrète

+

+ {{ profile.secret_note|default:"[Aucune note secrète définie]" }} +

+{% endblock %} \ No newline at end of file diff --git a/templates/protected.html b/templates/protected.html new file mode 100644 index 0000000..79a8e33 --- /dev/null +++ b/templates/protected.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block content %} +

Zone (censée être) Protégée

+ +
+

Accès NON-AUTORISÉ !

+

+ Vous n'auriez jamais dû trouver cette page ! +

+

+ Ceci est un (faux) panneau d'administration secret avec des informations sensibles. + Le fait que vous puissiez voir cette page sans être connecté + est une faille de sécurité (Security Misconfiguration / Broken Access Control). +

+

L'outil "Parcours forcés" de ZAP a probablement trouvé cette page parce que + l'URL /protected/ était dans sa liste de mots.

+
+ +

Informations "Sensibles"

+
    +
  • Clé API du système : fake_api_key_123456789ABCDEF
  • +
  • Endpoint de backup : /system/run_backup_now
  • +
  • Mot de passe BDD (dev) : admin/admin
  • +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/registration/login.html b/templates/registration/login.html new file mode 100644 index 0000000..727873a --- /dev/null +++ b/templates/registration/login.html @@ -0,0 +1,10 @@ +{% extends 'base.html' %} + +{% block content %} +

Se connecter

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} \ No newline at end of file