diff --git a/cadastrapp/src/main/java/org/georchestra/cadastrapp/providers/PreRequestFilter.java b/cadastrapp/src/main/java/org/georchestra/cadastrapp/providers/PreRequestFilter.java index 992aa7f1..b98d3dec 100644 --- a/cadastrapp/src/main/java/org/georchestra/cadastrapp/providers/PreRequestFilter.java +++ b/cadastrapp/src/main/java/org/georchestra/cadastrapp/providers/PreRequestFilter.java @@ -22,8 +22,10 @@ public class PreRequestFilter implements ContainerRequestFilter { public void filter(ContainerRequestContext requestContext) throws IOException { String rolesList = requestContext.getHeaderString("sec-roles"); + String org = requestContext.getHeaderString("sec-org"); String userName = requestContext.getHeaderString("sec-username"); MDC.put("user", userName); + MDC.put("org", org); MDC.put("roles", rolesList); MDC.put("uri", requestContext.getUriInfo().getPath()); @@ -34,4 +36,4 @@ public void filter(ContainerRequestContext requestContext) throws IOException { } } -} \ No newline at end of file +} diff --git a/cadastrapp/src/main/java/org/georchestra/cadastrapp/service/CadController.java b/cadastrapp/src/main/java/org/georchestra/cadastrapp/service/CadController.java index b6a1c869..c16aa8d7 100644 --- a/cadastrapp/src/main/java/org/georchestra/cadastrapp/service/CadController.java +++ b/cadastrapp/src/main/java/org/georchestra/cadastrapp/service/CadController.java @@ -1,6 +1,7 @@ package org.georchestra.cadastrapp.service; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -146,39 +147,46 @@ protected String addAuthorizationFiltering(HttpHeaders headers) { */ protected String addAuthorizationFiltering(HttpHeaders headers, String tableAlias) { - logger.debug("Check user geographical limitation "); - List> limitations; List communes = new ArrayList(); List deps = new ArrayList(); StringBuilder queryFilter = new StringBuilder(); - // get roles list in header - // Example 'ROLE_MOD_LDAPADMIN,ROLE_EL_CMS,ROLE_SV_ADMIN' + String usernameString = headers.getHeaderString("sec-username"); + if (usernameString == null){ + logger.debug("Not checking geographical limitation, anonymous user"); + return queryFilter.toString(); + } + // get org in header + String orgString = headers.getHeaderString("sec-org"); + // get roles in heade String roleListString = headers.getHeaderString("sec-roles"); - logger.debug("user roleList : "+ roleListString); + // merge org+roles to get groups list + List groupsList = new ArrayList(); + if(orgString!=null && !orgString.isEmpty()){ + groupsList.add(orgString); + } if(roleListString!=null && !roleListString.isEmpty()){ - // set separator by default if not set if(roleSeparator.isEmpty()){ roleSeparator = ";"; } - - // Force to add the array of value in first place of a new Array - String[] roleList = roleListString.split(roleSeparator); - - // get commune list in database corresponding to this header + groupsList.addAll(Arrays.asList(roleListString.split(roleSeparator))); + } + + if(!groupsList.isEmpty()){ + // get commune list in database corresponding to those groups StringBuilder queryBuilder = new StringBuilder(); queryBuilder.append("select distinct cgocommune, ccodep from "); queryBuilder.append(databaseSchema); queryBuilder.append(".groupe_autorisation "); - queryBuilder.append(createWhereInQuery(roleList.length, "idgroup")); + queryBuilder.append(createWhereInQuery(groupsList.size(), "idgroup")); queryBuilder.append(";"); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); - limitations = jdbcTemplate.queryForList(queryBuilder.toString(), roleList); + limitations = jdbcTemplate.queryForList(queryBuilder.toString(), groupsList.toArray(new String[groupsList.size()])); // filter request on commune if (limitations != null && !limitations.isEmpty()) { @@ -196,8 +204,12 @@ protected String addAuthorizationFiltering(HttpHeaders headers, String tableAlia } if(logger.isDebugEnabled()){ - logger.debug("User have geographical limitation on zip code : " + communes.toString()); - logger.debug("User have geographical limitation on dep : " + deps.toString()); + if(!communes.isEmpty()){ + logger.debug("User have geographical limitation on zip code : " + communes.toString()); + } + if(!deps.isEmpty()){ + logger.debug("User have geographical limitation on dep : " + deps.toString()); + } } @@ -233,7 +245,8 @@ protected String addAuthorizationFiltering(HttpHeaders headers, String tableAlia } } else{ - logger.warn("No filter, no sec-roles was found"); + logger.warn("User authenticated as '" + usernameString + "' but no sec-org header, maybe something is wrong."); + logger.warn("No filters applied because no sec-roles or sec-org corresponding rules were founds."); } return queryFilter.toString(); diff --git a/cadastrapp/src/main/resources/logback.xml b/cadastrapp/src/main/resources/logback.xml index 72cb3668..731e9f71 100644 --- a/cadastrapp/src/main/resources/logback.xml +++ b/cadastrapp/src/main/resources/logback.xml @@ -30,7 +30,7 @@ /tmp/cadastrapp.log - %d [%thread] %-5level /%X{uri} - %X{user:-nouser} - %X{roles:-norole} -%logger{36} - %msg%n + %d [%thread] %-5level /%X{uri} - %X{user:-nouser} - %X{org:-noorg} - %X{roles:-norole} -%logger{36} - %msg%n @@ -38,4 +38,4 @@ - \ No newline at end of file + diff --git a/database/README.md b/database/README.md index abc9c8d5..3f720fa7 100644 --- a/database/README.md +++ b/database/README.md @@ -2,6 +2,7 @@ Ceci est la documentation des traitements qui mettent à jour la base de données applicative de cadastrapp. +======= 2 scripts existent : - 1 script de purge + rechargement complet des données : `cadastrapp_load_data.sh` - 1 script de rafraîchissement des vues matérialisées : `cadastrapp_update_data.sh` @@ -9,7 +10,6 @@ Ceci est la documentation des traitements qui mettent à jour la base de donnée **Pour une mise à jour incluant une mise à jour des données foncières (données MAJIC) il faut faire un rechargement complet de la base de données car, généralement il y a chaque année des mises à jour de valeurs des nomenclatures. En revanche, en cas de mise à jour des données du plan cadastral (EDIGEO) uniquement, on peut se contenter de rafraîchir les vues matérialisées.** - ## Principe de fonctionnement Avant de configurer et jouer les scripts, vous devez disposez d'une base de données PostgreSQL / PostGIS contenant des données cadastrales créées en utilisant le greffon **[QGIS cadastre](https://plugins.qgis.org/plugins/cadastre/)**. @@ -22,7 +22,6 @@ Le script de purge + rechargement complet des données va créer les tables et * L'utilisation des vues matérialisées permet de gagner du temps lors d'une mise à jour concernant uniquement le plan cadastral car un simple `REFRESH MATERIALIZED VIEW table_name` suffit à relire la base de données source. - ## Prérequis ### Versions PostgreSQL et PostGIS @@ -31,7 +30,6 @@ L'utilisation des vues matérialisées permet de gagner du temps lors d'une mise * PostgreSQL > 9.6 * PostGIS > 2.1 mais < 3.0 - ### Si toutes les données sont dans la même base de données C'est le cas le plus simple. @@ -40,7 +38,6 @@ Dans ce cas il faut 2 schémas. Exemples : * `cadastre_gis` : il contiendra les données cadastre produites par le plugin cadastre de QGIS * `cadastrapp` : il contiendra les données applicatives pour cadastrapp et créées par notre script - ### Si les données sont dans 2 bases de données distinctes Dans ce cas on aura 2 bases de données différentes (sur la même machine ou pas) et donc 2 schémas différents. Exemples : @@ -49,6 +46,42 @@ Dans ce cas on aura 2 bases de données différentes (sur la même machine ou pa La base de données cible qui contiendra les données de cadastrapp devra comporter l'extension **[dblink](http://www.postgresql.org/docs/current//dblink.html)**. +### Si on souhaite remonter les autorisations geographiques depuis les groupes georchestra + +Il faudra que le [fichier de correspondance fourni à la console](https://github.com/georchestra/georchestra/tree/master/console#custom-areas) comporte les codes INSEE des communes. + +La base de données cible qui contiendra les données de cadastrapp devra : +* Comporter l'extension **[multicorn](https://multicorn.org/)** +* Avoir accès au serveur LDAP de l'instance geOrchestra + +#### Installation de multicorn (Debian) + +Dans Debian 10, multicorn est disponible via un paquet : + +`$ sudo apt install postgresql-11-python3-multicorn` + +Cette commande est à adapter en fonction de votre version de PostgreSQL. + +#### Création du Foreign Data Wrapper pour LDAP + +Afin de récupérer les emprises géographiques définies pour l'organisation des utilisateurs, il est necessaire de configurer une connexion de la base de données vers le LDAP de Georchestra. + +Commencer par installer l'extension multicorn sur la BDD précedemment créée : + +``` +CREATE EXTENSION multicorn; +``` + +Puis, créer le lien vers le serveur LDAP : + +``` +CREATE SERVER ldap_srv foreign data wrapper multicorn options ( + wrapper 'multicorn.ldapfdw.LdapFdw' +); +ALTER SERVER ldap_srv + OWNER TO #user_cadastrapp; +``` +> **Note:** Remplacer `#user_cadastrapp` par l'utilisateur utilisé par cadastrapp ## Configuration @@ -58,7 +91,7 @@ Sous linux ou git bash sous Windows : * le renommer en `config.sh` * l'ouvrir et compléter les informations de connection aux bases de données * si les données cadastre QGIS et cadastrapp sont dans la même base de données, laisser `uniqueDB=True` sinon mettre `uniqueDB=False` - +* si vous souhaitez remonter les autorisations cartographiques de puis les organisations geOrchestra (LDAP), mettre `orgsAutorisations=True` sinon laisser `orgsAutorisations=False` ## Purge + rechargement complet des données @@ -68,7 +101,6 @@ Sous linux ou git bash sous Windows : Note : il est possible de l'utiliser en mode silencieux avec l'option `-s` ou `--silent`. Si précisé, le script n'attendra pas de validation de la part de l'utilisateur. - ## Mise à jour des données Convient pour une mise à jour intermédiaire ne concernant pas une mise à jour des données foncières. @@ -77,4 +109,100 @@ Sous linux ou git bash sous Windows : * aller dans le répertoire `database` * exécuter le script `cadastrapp_update_data.sh` -Note : il est possible de l'utiliser en mode silencieux avec l'option `-s` ou `--silent`. Si précisé, le script n'attendra pas de validation de la part de l'utilisateur. \ No newline at end of file +Note : il est possible de l'utiliser en mode silencieux avec l'option `-s` ou `--silent`. Si précisé, le script n'attendra pas de validation de la part de l'utilisateur. + +## Mise en place d'un CRON pour rafraichir les autorisations liées aux organisations + +Si vous avez mis en place la gestion des autorisations geographiques depuis les groupes georchestra, le script a créé une vue materialisée contenant les codes INSEE pour chaque groupe qui ont été remontés depuis le LDAP. +Ceci permet de ne pas surcharger ce dernier avec des requêtes à chaque sollicitation de cadastrapp. + +Néanmoins, pour que les changements sur les périmètres des organisations faits dans la console geOrchestra soient pris en compte, cette vue materialisée devra être rafraichie. + +Pour ce faire, il est conseillé de mettre en place un CRON qui permettra de la rafraichir à interval régulier. + +### Mise en place d'un 'cron job' dans debian + +Créez un fichier de script `cadastrapp_refresh_ldap_view.sh` sur le serveur contenant la base de données cadastrapp avec le contenu suivant : + +``` +#!/bin/sh +PGPASSWORD=#cadastrapp_password psql -h #cadastrapp_db_host -p $cadastrapp_db_port -d $cadastrapp_db_name -U $cadastrapp_user -c 'refresh materialized view #cadastrapp_schema.org_autorisation' +``` +> **Note:** Remplacez les variables précédées d'un `#` par la valeur appropriée +Déplacez le fichier dans le répertoire cron correspondant à la fréquence souhaitée : + +* /etc/cron.hourly => toutes les heures +* /etc/cron.daily => tous les jours +* /etc/cron.weekly => toutes les semaines +* /etc/cron.monthly => tous les mois + +Ou bien si vous souhaitez un parametrage plus poussé, référez vous à la [documentation de crontab](https://debian-facile.org/doc:systeme:crontab) + +Enfin pensez-bien à rendre le fichier executable : + +``` +chmod +x cadastrapp_refresh_ldap_view.sh +``` + +### Mise en place d'un 'cron job' via pg_cron + +Commencez par installer l'extension `cron` de postgresql : + +``` +sudo apt install postgresql-11-cron +``` + +Une fois installée, partie OS, il faudra modifier les paramètres base de données dans PostgreSQL afin de pouvoir utiliser cette extension. + +Le fichier “postgresql.conf” devra indiquer les éléments suivants pour permettre la création de cette extension en base : + +* share_preload_libraries +* cron.database_name + +``` +sudo nano /etc/postgesql/11/main/postgresql.conf +``` + +Ajoutez à la fin du fichier : + +``` +shared_preload_libraries = 'pg_cron' +cron.database_name = '#cadastrappDBName' +``` + +> **Note:** Remplacez `#cadastrappDBName` par le nom de la base de donnée hébergeant cadastrapp + +Autorisez la connexion de l'utilisateur en local via trust pour permettre l'accès à la tache CRON, +pour cela dans le fichier `/etc/postgresql/11/main/pg_hba.conf` modifiez la ligne : + +``` +host #cadastrappDBName #user_cadastrapp 127.0.0.1/32 trust +host all all 127.0.0.1/32 md5 +``` +> **Note:** Remplacez les variables précédées d'un `#` par la valeur appropriée +Relancez postgresql : + +``` +sudo service postgresql restart +``` + +Activez l'extention dans la base de donnée hebergeant cadastrapp : + +``` +CREATE EXTENSION pg_cron; +``` + +Autorisez l'utilisation pour l'utilisateur cadastrapp : + +``` +GRANT USAGE ON SCHEMA cron TO #user_cadastrapp; +GRANT INSERT ON TABLE cron.job TO #user_cadastrapp; +GRANT USAGE ON SEQUENCE cron.jobid_seq TO #user_cadastrapp; +``` +> **Note:** Remplacer `#user_cadastrapp` par l'utilisateur utilisé par cadastrapp +Enfin lancez le script de definition la tache CRON : + +``` +./create_ldap_cronjob.sh +``` +> **Note:** Par défaut, le script défini le rafraichissement de la vue mateiralisée toutes les heures, si cela ne vous convient pas, vous pouvez modifier cette configuration dans le fichier `sql/ldap/cronjob.sql`. diff --git a/database/cadastrapp_load_data.sh b/database/cadastrapp_load_data.sh index 34cf1d0d..b27a28c5 100755 --- a/database/cadastrapp_load_data.sh +++ b/database/cadastrapp_load_data.sh @@ -47,7 +47,32 @@ else exit 1 fi -echo "" +if [ "$orgsAutorisations" = True ] ; then + invalidLdapParam=False + if [ -z "$ldapUri" ] ; then + echo "Paramètre ldapUri manquant !" + invalidLdapParam=True + fi + + if [ -z "$ldapPath" ] ; then + echo "Paramètre ldapPath manquant !" + invalidLdapParam=True + fi + + if [ -z "$ldapBindDn" ] ; then + echo "Paramètre ldapBindDn manquant !" + invalidLdapParam=True + fi + + if [ -z "$ldapBindPwd" ] ; then + echo "Paramètre ldapBindPwd manquant !" + invalidLdapParam=True + fi + if [ "$invalidLdapParam" = True ] ; then + echo " pb de configuration : stop" + exit 1 + fi +fi if [ "$silentMode" = False ] ; then echo "" @@ -55,7 +80,6 @@ if [ "$silentMode" = False ] ; then echo "" fi - # # cette fonction permet de remplacer les infos de connection # avant exécution @@ -78,6 +102,10 @@ replaceAndLaunch (){ s/#DBSchema_qgis/$qgisDBSchema/g s/#DBUser_qgis/$qgisDBUser/g s/#DBpasswd_qgis/$qgisDBPassword/g + s|#ldap_uri|$ldapUri|g + s/#ldap_path/$ldapPath/g + s/#ldap_binddn/$ldapBindDn/g + s/#ldap_bindpwd/$ldapBindPwd/g }" |\ PGPASSWORD=$cadastrappDBPassword psql -h $cadastrappDBHost -p $cadastrappDBPort -d $cadastrappDBName -U $cadastrappDBUser diff --git a/database/config_sample.sh b/database/config_sample.sh index f956b507..a0265925 100755 --- a/database/config_sample.sh +++ b/database/config_sample.sh @@ -23,3 +23,12 @@ cadastrappDBPassword=cadastrapp_mdp # True / False uniqueDB=True +# Récupération des autorisations geographiques configurées pour les organisations dans la console geOrchestra +# True / False +orgsAutorisations=False + +# Obligatoires si orgsAutorisations=True +#ldapUri=ldaps://ldap.georchestra.org +#ldapPath=ou=orgs,dc=georchestra,dc=org +#ldapBindDn=uid=cadastrapp,ou=users,dc=lepuyenvelay,dc=fr +#ldapBindPwd=secret diff --git a/database/create_ldap_cronjob.sh b/database/create_ldap_cronjob.sh new file mode 100644 index 00000000..6f31dda6 --- /dev/null +++ b/database/create_ldap_cronjob.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +clear +git_version=$(git rev-parse HEAD) + +echo "--------------------------------------------------------------" +echo " Cadastrapp : script de création du cron job pour rafraichier la vue des organisations LDAP" +echo "--------------------------------------------------------------" +echo "" +echo " version : $git_version" +echo "" +echo "--------------------------------------------------------------" +echo " Lecture du fichier de configuration" +echo "--------------------------------------------------------------" +echo "" + +# lecture du fichier de configuration des connexions +. config.sh + +replaceAndLaunch (){ + if [ -z "$1" ] || [ ! -e $1 ] ; then + echo "Le fichier SQL $1 n'existe pas." + exit 1 + else + echo " Exécution du fichier : $1" + fi + + cat $1 | sed "{ + s/#schema_cadastrapp/$cadastrappDBSchema/g + s/#cadastrapp_db_host/$cadastrappDBHost/g + s/#cadastrapp_db_port/$cadastrappDBPort/g + s/#cadastrapp_db_name/$cadastrappDBName/g + s/#user_cadastrapp/$cadastrappDBUser/g + }" |\ + PGPASSWORD=$cadastrappDBPassword psql -h $cadastrappDBHost -p $cadastrappDBPort -d $cadastrappDBName -U $cadastrappDBUser +} + +replaceAndLaunch sql/ldap/cronjob.sql + +echo "" +echo "--------------------------------------------------------------" +echo " FIN " +echo "--------------------------------------------------------------" diff --git a/database/create_tables.sh b/database/create_tables.sh index 2a0e3e18..05f40be5 100644 --- a/database/create_tables.sh +++ b/database/create_tables.sh @@ -21,6 +21,12 @@ replaceAndLaunch sql/tables/prop_dmatto.sql replaceAndLaunch sql/tables/prop_dmatgm.sql replaceAndLaunch sql/tables/prop_type_filiation.sql replaceAndLaunch sql/tables/prop_bati_detent.sql -replaceAndLaunch sql/tables/groupe_autorisation.sql + +if [ "$orgsAutorisations" = True ] ; then + replaceAndLaunch sql/ldap/organisations.sql + replaceAndLaunch sql/ldap/groupe_autorisation.sql +else + replaceAndLaunch sql/tables/groupe_autorisation.sql +fi echo " Fait" diff --git a/database/sql/ldap/cronjob.sql b/database/sql/ldap/cronjob.sql new file mode 100644 index 00000000..8e4f1053 --- /dev/null +++ b/database/sql/ldap/cronjob.sql @@ -0,0 +1,3 @@ +INSERT INTO cron.job (schedule, command, nodename, nodeport, database, username) +VALUES ('0 * * * *', 'REFRESH MATERIALIZED VIEW #schema_cadastrapp.org_autorisation', +'#cadastrapp_db_host', #cadastrapp_db_port, '#cadastrapp_db_name', '#user_cadastrapp'); diff --git a/database/sql/ldap/groupe_autorisation.sql b/database/sql/ldap/groupe_autorisation.sql new file mode 100644 index 00000000..468176a8 --- /dev/null +++ b/database/sql/ldap/groupe_autorisation.sql @@ -0,0 +1,47 @@ +CREATE MATERIALIZED VIEW #schema_cadastrapp.org_autorisation AS + SELECT + regexp_replace(cn, '[''\[\]]', '', 'g') AS idorg, + unnest(string_to_array(regexp_replace(description, '[''\[\]]', '', 'g'), ',')) as inseecommune, + NULL as ccodep + FROM + #schema_cadastrapp.organisations; + +ALTER TABLE #schema_cadastrapp.org_autorisation + OWNER TO #user_cadastrapp; + +CREATE TABLE #schema_cadastrapp.role_autorisation +( + idrole character varying(50), -- Identifiant du role LDAP devant être filtré + cgocommune character varying(6), -- Code commune INSEE + ccodep character varying(3), -- Code département à mettre en relation avec le code commune + CONSTRAINT "role_autorisation_UK" UNIQUE (cgocommune , ccodep , idrole ) +) +WITH ( + OIDS=FALSE +); +ALTER TABLE #schema_cadastrapp.role_autorisation + OWNER TO #user_cadastrapp; + +COMMENT ON TABLE #schema_cadastrapp.role_autorisation IS 'Table de correlation entre les roles LDAP et les droits géographiques'; + +COMMENT ON COLUMN #schema_cadastrapp.role_autorisation.idrole IS 'Identfiant du role LDAP devant être filtré'; +COMMENT ON COLUMN #schema_cadastrapp.role_autorisation.cgocommune IS 'Code commune INSEE version cadastre (6 char -> made with ccodpe + ccodir + ccocom)'; +COMMENT ON COLUMN #schema_cadastrapp.role_autorisation.ccodep IS 'Code département à mettre en relation avec le code commune'; + + +CREATE VIEW #schema_cadastrapp.groupe_autorisation AS + SELECT + idorg AS idgroup, + LEFT(inseecommune,2)||'0'||RIGHT(inseecommune,-2) AS cgocommune, + ccodep + FROM + #schema_cadastrapp.org_autorisation + UNION + SELECT + idrole AS idgroup, + cgocommune, + ccodep + FROM + #schema_cadastrapp.role_autorisation; +ALTER TABLE #schema_cadastrapp.groupe_autorisation + OWNER TO #user_cadastrapp; diff --git a/database/sql/ldap/organisations.sql b/database/sql/ldap/organisations.sql new file mode 100644 index 00000000..7459c924 --- /dev/null +++ b/database/sql/ldap/organisations.sql @@ -0,0 +1,12 @@ +CREATE FOREIGN TABLE #schema_cadastrapp.organisations ( + cn character varying, + description character varying +) server ldap_srv options ( + uri '#ldap_uri', + path '#ldap_path', + scope 'sub', + binddn '#ldap_binddn', + bindpwd '#ldap_bindpwd', + objectClass 'groupOfMembers' +); +GRANT SELECT ON TABLE #schema_cadastrapp.organisations TO #user_cadastrapp;