Librairie NodeJS / React / Création d'un utilisateur + mot de passe personnalisé

Request description

J’ai donc suivi vos conseils concernant le dernier message, merci encore, je vais vous montrer ce que j’ai fait côté Simplicité (back) et côté React (Front).

Pour info, la création de cet utilisateur doit lui permettre de se connecter sur l’outil (front) et aussi à la UI standard avec des accès restreint.

J’ai maintenant un petit blocage concernant la stratégie du password coté backend.

Pouvez-vous m’aider un peu plus là-dessus ?

Merci pour votre aide.

@Abed

Steps to reproduce

This request concerns an up-to-date Simplicité instance
and those are the steps to reproduce it:

  1. Création d’un user technique appartenant à un groupe d’accès “ADMIN_CREATE”.


  1. Création d’un objet custom user qui hérite de l’objet SimpleUser avec un attribut mot de passe non persistant + création d’un objet custom responsability qui hérite de l’objet Responsability avec un attribut id technique

  1. Autorisation en création et en update côté back sur l’objet custom user avec accès restreint

  1. Autorisation en création pour l’objet custom user au groupe “ADMIN_CREATE” + autorisation à la modification pour l’objet custom user au groupe “IMMO_FREEMIUM” avec des accès restreints + autorisation en création pour l’objet custom responsability au groupe “ADMIN_CREATE”

  1. En front

Voici mon code simplifié :

var values = {
    lastname : "paul",
    firstname : "paul",
    email : "paul@paul.fr",
    password : "password",
}

onSubmit: async (values) => {
      const app = Simplicite.session({
        url: url,
        authtoken: token_user_technique,
      });
      try {
        const userObj = app.getBusinessObject("ImmoCustomUser");
        const user = await userObj.getForCreate();
        user.usr_login = values.email;
        user.usr_last_name = values.lastname;
        user.usr_first_name = values.firstname;
        user.usr_pwd = values.password;
        user.usr_email = values.email;
        user.usr_active = 1;
        const new_user = await userObj.create(user);
        if (new_user) {
          const respObj = app.getBusinessObject("ImmoCostumResponsability");
          const resp = await respObj.getForCreate();
          resp.ImmoCostumResponsability_ImmoCustomUser_id = new_user.row_id;
          resp.rsp_login_id = new_user.row_id;
          resp.rsp_login_id__usr_login = new_user.usr_login;
          resp.row_module_id = 40;
          resp.row_module_id__mdl_name = "ImmoUser";
          resp.rsp_activ = 1;
          resp.rsp_group_id = 55;
          resp.rsp_group_id__grp_name = "IMMO_FREEMIUM";
          // groupe avec des accès restreint
          const new_resp = await respObj.create(resp);
          if (new_resp) {
            // ici ça ne sert à rien de console.log ces objets car le user technique n'a pas l'autorisation.
            // la création se fait correctement en back
        }
      } catch (e) {
        console.error(e);
      }
}
  1. En back


Technical information

Instance /health

[Platform]
Status=OK
Version=5.2.7
BuiltOn=2022-06-14 00:21
Git=5.2/d9643d29bd316734a300fbf98cbdd9a354c8b9e4
Encoding=UTF-8
EndpointIP=172.23.0.8
EndpointURL=http://a969f6572643:8080
TimeZone=Europe/Paris
SystemDate=2022-06-16 18:01:25

Déjà:

Anti pattern => on ne doit jamais mettre d’ID techniques en dur, nulle part ni en frontend ni en backend.

Mais bon, de manière plus générale le backend doit faciliter la vie au frontend et faire en sorte que celui ci ne se transforme pas en “passoire sécuritaire”

Perso si j’avais exposé un objet user simplifié, je l’aurais fait en création uniquement (cf. un autre post à ce sujet) et j’aurais aurais fait en sorte que le frontend n’ait à renseigner que les infos minimales qui le concernent = le login, le nom, le prénom, l’email etc.

En aucun cas je n’aurais exposé la notion de responsabilité ou autres choses qui permettent à une personne mal intentionnée de se créer un user avec des droits abusifs en lisant le code frontend => j’aurais fait en sorte que le backend à la création du user se charge de lui donner les droits qui vont bien sans que le frontend n’aie à savoir comment c’est fait.

A nouveau, déporter trop d’intelligence coté frontend est un anti pattern absolu => le code du frontend est lisible par n’importe qui et si on y peut y faire des choses trop intelligentes, celles-ci peuvent facilement être détournées et c’est la porte ouverte à tout type de piratages.

Ce que je dis ici n’est bien sûr pas spécifique à Simplicité, ce sont des considérations générales d’architecture front/back qui dépassent donc largement le périmètre de notre support.

Je vous laisse donc revoir votre architecture avec @Abed afin de mettre la frontière entre front et back au bon endroit car, de ce que je vois ici, elle n’est clairement pas du tout là où il faut.

Bonjour @david ,

Pour faire suite à vos conseils, je confirme qu’on va faire le nécessaire pour déplacer le maximum du code « sensible » dans le back, comme l’affectation d’un user dans un groupe…

Nous allons aussi remplacer (toujours dans le back) les ID techniques en dur par des méthodes Simplicités (exemple : ModuleDB.getModuleId(“ImmoUser”) pour récupérer le module concerné).

Il nous reste cependant les 3 points suivants :

1- Nous n’avons toujours pas la réponse à la question initiale posée par @Paul_S , à savoir, comment faut-il faire, dans le back, pour modifier le mot de passe qui est généré par défaut lors d ela création d’un user, par celui saisie par l’utilisateur ?

Je rappelle qu’on a un objet user custom (ImmoCustomUser), qui hérite de SimpleUser, et que l’utilisateur de notre site externe aura à saisir le strict minimum (nom, prénom, email), en plus d’un attribut non persistant « usr_pwd», où il va définir le mot de passe qu’il souhaite utiliser.

J’ai bien noté que cette partie (changement de password) doit se faire dans le postCreate de notre objet ImmoCustomUser, mais quand je fais le test dans simplicité, en créant un user dans cet objet, on dirait que la fonction :

this.getGrant().changePassword(login, pwd, false, true);

ne fait pas l’effet escompté, et le mot de passe généré par simplicité reste le même !

Pourriez-vous nous donner la syntaxe à utiliser pour changer ce mot de passe initial (généré par simplicité) avec celui saisi par l’utilisateur ?

2- Nous souhaitons ensuite donner la possibilité à l’utilisateur de changer son mot de passe quand il le souhaite, depuis notre site externe. Pour cela, nous allons développer une méthode dans le back, qui prendra en paramètre l’ancien MDP et le nouveau. Quelle serait dans ce cas-là la syntaxe à utiliser dans cette méthode pour changer le mot de passe ?

3- Etant donné que c’est l’utilisateur qui choisira son propre MDP lors de la création de son user, nous voulons retirer l’obligation de changer de MDP à la première connexion. J’essaie donc de lancer dans le postCreate la commande suivante :

this.getGrant().setUserSystemParam(login,“FORCE_CHANGE_PASSWORD”,‘no’,true);

mais le paramètre reste à ‘yes’. Auriez-vous une idée comment faire ?

Merci encore pour votre aide.

Abed.

Exemple d’approche:

  1. exposer au front un objet user minimal (et en création uniquement) qui hérite de SimpleUser avec tous les attributs en “forbidden” sauf ceux saisis en front: le login, le nom, le prenom … et un attribut password non persistant (en back mettre des valeurs par défaut ou calculées sur les autres attributs non accessibles du front)

  2. valider le password dans le postValidate, genre:

String password = getFieldValue("myNonPersistentPasswordField");
if (!Tool.isEmpty(password)) {
	List<String> errs = PlatformHooks.getInstance().validatePassword(Grant.getSystemAdmin(), password);
	if (!Tool.isEmpty(errs))
		msgs.addAll(errs);
}
  1. positionner le password dans le postCreate:
String password = getFieldValue("myNonPersistentPasswordField");
if (!Tool.isEmpty(password)) {
	String login = getFieldValue("usr_login");
	HashPassword.setPassword(login, password);
	Grant.getSystemAdmin().removeUserSystemParam(login, Globals.FORCE_CHANGE_PASSWORD, false);
}

Je n’arrive pas à faire fonctionner dans le postCreate ni le positionnement du mot de passe ni le retrait du paramètre FORCE_CHANGE_PASSWORD.
Pourriez-vous me donner le même exemple en Rhino svp ?
Merci d’avance.

L’exemple ci-dessus fait ça… (j’avais juste oublié le Grant.getSystemAdmin() devant le removeUserSystemParam, c’est corrigé ci-dessus)

Sinon pour traduire du Java en Rhino il suffit, en général de mettre var à la place des types déclarés et ajouter des this. explicites là où ils sont implicites en Java, ça donne donc ça à priori:

var password = this.getFieldValue("myNonPersistentPasswordField");
if (!Tool.isEmpty(password)) {
	var errs = PlatformHooks.getInstance().validatePassword(Grant.getSystemAdmin(), password);
	if (!Tool.isEmpty(errs))
		msgs.addAll(errs);
}

et

var password = this.getFieldValue("myNonPersistentPasswordField");
if (!Tool.isEmpty(password)) {
	var login = this.getFieldValue("usr_login");
	HashPassword.setPassword(login, password);
	Grant.getSystemAdmin().removeUserSystemParam(login, Globals.FORCE_CHANGE_PASSWORD, false);
}

Il doit me manquer qq choses car le traitement passe sans pb, mais ne fait rien :

////////////////////////////////////////////////////////////////////////////////////////
ImmoCustomUser.preValidate = function() {
	this.setFieldValue("usr_home_id", View.getViewId("ImmoHomeInvestor"));
	this.setFieldValue("usr_lang", Globals.LANG_FRENCH);
	this.setFieldValue("usr_image_id", '');
};
////////////////////////////////////////////////////////////////////////////////////////
ImmoCustomUser.postValidate = function() {
	var password = this.getFieldValue("usr_pwd");
	if (!Tool.isEmpty(password)) {
		var errs = PlatformHooks.getInstance().validatePassword(Grant.getSystemAdmin(), password);
		if (!Tool.isEmpty(errs))
			return (errs);
	}
};
////////////////////////////////////////////////////////////////////////////////////////
ImmoCustomUser.postCreate = function() {
	var password = this.getFieldValue("usr_pwd");
	if (!Tool.isEmpty(password)) {
		var login = this.getFieldValue("usr_login");
console.log("je lance le HashPassword.setPassword pour : login =" + login + " / password = "+password);			
		HashPassword.setPassword(login, password);
console.log("je retire l'obligation de modifier le mot de passe à la première connexion");		
		Grant.getSystemAdmin().removeUserSystemParam(login, Globals.FORCE_CHANGE_PASSWORD, false);
	}
};
////////////////////////////////////////////////////////////////////////////////////////

Dans la console :

2022-06-24 11:50:24,518|SIMPLICITE|INFO||http://a402edc584ae:8080||INFO|designer|com.simplicite.util.engine.ScriptInterpreter|ImmoCustomUser/the_ajax_ImmoCustomUser||Evénement: je retire l'obligation de modifier le mot de passe à la première connexion
2022-06-24 11:50:24,508|SIMPLICITE|INFO||http://a402edc584ae:8080||INFO|designer|com.simplicite.util.engine.ScriptInterpreter|ImmoCustomUser/the_ajax_ImmoCustomUser||Evénement: je lance le HashPassword.setPassword pour : login =testAbed / password = testAbed1

Et pourtant, c’est le mot de passe qui a été généré par simplicité qui est utilisé et qui me permet de me connecter avec ce nouvel utilisateur (et non pas celui saisie par l’utilisateur dans l’attribut non persistant) :

Pareil pour le paramètre de changement de mot de passe qui reste présent aussi :

J’ai relu le code du cas dont je me suis inspiré, le forçage du password y est fait après le create du user custom (dans ce cas métier la création du user est déclenché depuis le postSave un autre objet exposé au front, aucun objet user n’étant exposé au front ici)

Mais bon, le password standard est positionné dans le postCreate de l’objet SimpleUser donc si on surchage ce postCreate sans appeler le super.postCreate (ou en l’appelant avant de forcer le password) ça devrait le faire.

Ou alors il y a une subtilité liée aux appels de hooks des objets pères dans le contexte d’un objet avec héritage et codé en Rhino… j’avoue ne pas maitriser ce cas là.

C’est peut être l’occasion d’implémenter le code de cet objet user custom en Java

A quelle moment, où et comment on surchage le postCreate de SimpleUser ? Auirez-vous un exemple ? est-ce que c’est dans ImmoCustomUser.postCreate

C’est quoi un super.postCreate ? (je n’ai pas encore eu l’occasion de m’en servir)

En dernière solution, on essaiera de migrer l’objet en Java (au lieu de Rhino)

Merci d’avance.
Abed.

Quand un objet B hérite d’un objet A la classe java de l’objet B doit extend la classe de l’objet A

Les hooks de B surchargent (@Override) ceux de A et doivent (en général) appeler ceux de A genre:

public class B extends A {
  @Override
  public void postLoad() {
    super.postLoad(); // appel du postLoad de A
    // des choses specifiques à B
  }
}

Ici aussi rien de spécifique à Simplicité ou même à Java => c’est valable pour tout langage orienté objet.

En Rhino, l’héritage de code n’est pas géré nativement comme en Java. Donc la notion de super n’existe pas. Comme ici vous faites hériter un objet code en Rhino d’un objet codé en Java (SimpleUser) j’avoue ne pas savoir ce qui se passe exactement.

C’est, à mon avis, vraiment l’occasion de passer à Java sur cet objet. On a bien compris que vous avez un historique Rhino à gérer mais sur un nouvel objet autant faire du Java

Vous pouvez très bien avoir un objet autre user custom distinct qui hérite aussi de SimpleUser mais dédié au front genre ImmoCustomUserForFrontend avec sa logique propre.