Aller au contenu
  1. Posts/

QFieldCloud auto-hébergé [A07] : l'upstream n'a pas d'espace utilisateur

Sommaire
QFieldCloud auto-hébergé - Cet article fait partie d'une série.
Partie 12: Cet article

À qui s’adresse cette annexe
#

Vous avez monté l’instance, elle répond, vous vous connectez : tout va bien. Puis un utilisateur crée son compte, se connecte à son tour, et son navigateur affiche une erreur de redirection. Cette annexe explique pourquoi, et ce que votre instance sait faire pour lui malgré tout.

Cette annexe décrit l’upstream, et ce qu’il vous laisse entre les mains si vous n’y touchez pas. Tout ce qu’elle constate reste vrai du dépôt opengisch/QFieldCloud. L’annexe suivante prend le problème dans l’autre sens et compose le portail manquant, sans modifier une ligne du sous-module.

Le premier inscrit le découvre pour vous
#

Ouvrez les inscriptions le temps d’un test, créez un compte ordinaire, connectez-vous avec. Voici la chaîne, relevée sur une instance en service :

1. /                             -> 302 admin/
2. /admin/                       -> 302 /admin/login/?next=/admin/
3. /admin/login/?next=/admin/    -> 302 /accounts/login/?next=/admin/
4. /accounts/login/?next=/admin/ -> 302 /admin/
5. /admin/                       -> 302 /admin/login/?next=/admin/     ← ça recommence

Le navigateur s’arrête au bout de vingt tours sur ERR_TOO_MANY_REDIRECTS. Le mécanisme n’a rien de mystérieux : LOGIN_REDIRECT_URL vaut index, index redirige vers QFIELDCLOUD_ADMIN_URI, l’admin Django refuse un compte sans is_staff et le renvoie vers sa page de connexion, laquelle délègue à allauth — la bibliothèque tierce qui gère toute l’authentification de QFieldCloud : inscription, connexion, mots de passe, comptes tiers — qui constate que l’utilisateur est connecté et le renvoie à index. Chacun fait son travail ; l’ensemble tourne en rond.

On peut passer une soirée à chercher l’erreur dans sa configuration. Il n’y en a pas. Ce comportement est celui de l’upstream, et il découle d’une décision plus large.

La page qui n’existe pas
#

Comparez avec app.qfield.cloud : profil, choix d’abonnement, liste de projets, gestion des collaborateurs. Rien de tout cela n’est dans le dépôt que vous avez cloné. Trois constats le montrent sans ambiguïté.

Tableau de bord de app.qfield.cloud : profil, abonnement, projets, collaborateurs
Pour comparaison, le portail du service hébergé : profil, abonnement, liste de projets, collaborateurs. Aucune de ces pages n’existe dans le dépôt cloné. Sur le service hébergé : https://app.qfield.cloud/.

Il n’y a pas de gabarits. Le dossier des templates ne contient que les pages de compte, l’admin et les greffons d’authentification :

$ ls src/docker-app/qfieldcloud/core/templates/
account  admin  allauth  axes  captcha  socialaccount

Aucun gabarit de projet, de profil ou d’abonnement. Il n’y a pas de vues web non plus : tout ce que contient core/views/ est du Django REST Framework, c’est-à-dire des vues d’API, qui rendent du JSON et pas des pages.

Et le peu de libre-service qu’allauth apporterait est explicitement débranché dans urls.py :

# src/docker-app/qfieldcloud/urls.py — upstream, en lecture seule
path("accounts/3rdparty/", blocked_view),
path("accounts/email/", blocked_view),
path("accounts/password/change/", blocked_view),

Changer son mot de passe, gérer ses adresses, lier un compte tiers : les trois routes sont posées pour être bloquées. Ce n’est pas un oubli, c’est une décision.

Info : ce que livre le dépôt open source, c’est le moteur — l’API REST que consomment QField et QFieldSync, plus l’admin Django pour l’exploitant. L’interface web qu’on voit sur le service hébergé n’en fait pas partie. Monter une instance auto-hébergée, c’est donc obtenir la moitié qui travaille, pas celle qui se montre.

Ce que l’utilisateur peut faire, et par où
#

La bonne nouvelle est que l’absence de portail ne l’empêche de rien. Avec une session ordinaire, non-staff, sur l’instance :

GET /api/v1/auth/user/  -> 200  {"username":"…","type":1,"email":"…"}
GET /api/v1/projects/   -> 200  []
POST /api/v1/projects/  -> 201  {"id":"d9a1ad49-…","owner":"…","private":true}

Créer un projet, le peupler, le synchroniser : tout passe. La fonction qui garde la porte ne consulte même pas le plan quand l’utilisateur crée chez lui —

# src/docker-app/qfieldcloud/core/permissions_utils.py — upstream
def can_create_project(user, organization=None) -> bool:
    if organization is None:
        return True

Concrètement : votre utilisateur installe QFieldSync dans QGIS et QField sur son téléphone, y saisit l’adresse de l’instance et ses identifiants, et travaille. Il n’a aucune raison d’ouvrir un navigateur — sauf qu’aujourd’hui, s’il le fait, il tombe sur la boucle.

Les quotas sont des lignes en base, pas du code
#

Deuxième surprise pour qui vient du service hébergé : les plans d’abonnement existent bel et bien dans le dépôt open source. L’app subscription crée deux plans à la première migration, et attribue le plan par défaut à chaque inscription. Sur une instance neuve, community vaut ceci :

Réglage Valeur
storage_mb 10 000, soit 10 Go
storage_keep_versions 10 versions conservées par fichier
job_minutes 10 000
synchronizations_per_months 30
is_external_db_supported False
max_organization_members -1, illimité
initial_subscription_status active_paid

Ce dernier mérite un mot : l’abonnement naît actif et payé. Aucun paiement n’est branché sur une instance auto-hébergée, et il n’est pas question d’en brancher un — la mécanique de facturation du service hébergé n’est pas là non plus.

Un seul de ces réglages mord vraiment, et il vaut d’être connu avant de promettre quoi que ce soit à un utilisateur :

# core/permissions_utils.py — au moment de préparer le paquet
if project.has_online_vector_data and not subscription.plan.is_external_db_supported:
    raise PlanInsufficientError(
        "Owner's subscription plan does not support online vector layer datasource.")

Un projet QGIS en GeoPackage passe sans rien demander. Un projet branché sur PostGIS ou un WFS est refusé au packaging pour un compte community. C’est le genre de limite qu’on découvre le jour de la démonstration si on ne l’a pas lue avant.

Attention : deux réglages ne font pas ce que leur nom promet.

synchronizations_per_months n’est lu nulle part dans le code — il n’existe que dans le modèle et les migrations. Il ne limite rien.

can_always_upload_files() exempte les clients de type QFIELD et WORKER du contrôle de quota fichier par fichier. Le téléversement depuis l’application mobile n’est donc pas plafonné à chaque envoi ; c’est au packaging que le quota global s’applique.

Paramétrer, et jusqu’où
#

Tout cela s’édite dans l’admin, Subscription → Plans. Sur une instance auto-hébergée, sans facturation, relever une limite ou passer is_external_db_supported à True est une décision d’exploitant, pas un contournement : vous n’achetez rien à personne, vous décidez de ce que votre serveur accepte de faire.

Deux précautions. Marquer un nouveau plan is_default ne change que les prochaines inscriptions ; les comptes existants gardent le leur, et il faut leur changer l’abonnement à la main. Et donner is_staff à un utilisateur pour « qu’il ait une interface » est un mauvais échange : ce n’est pas un portail qu’on lui ouvre, c’est l’admin complet de l’instance.

Et la boucle, on la laisse ?
#

Deux réponses défendables.

La laisser, et le documenter : le web est réservé à l’exploitant, les utilisateurs passent par les clients. C’est cohérent, mais un inscrit est quand même accueilli par une erreur de navigateur.

Composer un adaptateur, exactement comme l’annexe précédente composait les réglages. QFIELDCLOUD_ACCOUNT_ADAPTER désigne une classe : rien n’oblige à ce que ce soit celle de l’upstream. Une classe à nous, qui en hérite et surcharge get_login_redirect_url(), n’envoie vers l’admin que les comptes is_staff et dirige les autres vers une page qui existe — /swagger/, l’API documentée, a le mérite d’avoir du sens pour qui vient d’obtenir un compte.

C’est la seconde qu’on a retenue, et plus loin qu’annoncé : plutôt que de dévier la redirection vers une page de repli, on a composé ROOT_URLCONF sur le même principe et écrit les pages qui manquaient. La boucle disparaît alors d’elle-même, puisque c’est la route index qui la causait. L’annexe suivante raconte ce montage.

Info — tester une session sans connaître de mot de passe. Pour vérifier ce que voit un utilisateur, le client de test de Django suffit, mais deux détails coûtent chacun un essai. client.login() échoue avec AxesBackendRequestParameterRequired : django-axes exige une requête, il faut force_login(). Et créer un utilisateur passe par qfieldcloud.core.models.Person, pas par get_user_model() — un User nu casse dans la création de l’abonnement par défaut.

from django.test import Client
from qfieldcloud.core.models import Person

p, _ = Person.objects.get_or_create(username="probe", defaults={"email": "probe@example.invalid"})
c = Client(SERVER_NAME="qfieldcloud.exemple.fr")   # sinon ALLOWED_HOSTS répond 400
c.force_login(p)
print(c.get("/", follow=False)["Location"])

Ce qu’il faut retenir
#

  • Le dépôt open source livre l’admin et l’API, pas de portail utilisateur. Ce que montre app.qfield.cloud — profil, abonnement, projets — n’est pas dans le sous-module.
  • Un compte non-is_staff qui se connecte par le web boucle, jusqu’à ERR_TOO_MANY_REDIRECTS. Comportement de l’upstream, pas défaut de configuration — et il se règle, voir l’annexe suivante.
  • L’utilisateur n’a rien à faire sur le web tant qu’on n’a rien ajouté : QField et QFieldSync parlent à l’API, qui lui répond normalement.
  • Les quotas sont des lignes en base, éditables dans l’admin. Sur une instance auto-hébergée, les relever est une décision d’exploitant — mais un réglage d’admin ne se rejoue pas, et l’annexe suivante en fait une valeur versionnée.
  • is_external_db_supported=False refuse les couches vectorielles en ligne au packaging. GeoPackage : oui. PostGIS ou WFS : non, tant qu’on n’a pas changé le plan.
  • Ne donnez pas is_staff pour offrir une interface. Ce n’est pas un portail qu’on ouvre, c’est l’admin de l’instance.
Documentation self-hosted de QFieldCloud

Image de couverture : Scott Blake sur Unsplash.

QFieldCloud auto-hébergé - Cet article fait partie d'une série.
Partie 12: Cet article

Articles connexes