Article
· Fév 25 13m de lecture

Création de pages de connexion personnalisées avec %CSP.Login

La classe %CSP.Login est la classe utilitaire fournie par InterSystems IRIS pour créer des pages de connexion personnalisées. Si vous souhaitez contrôler l'interface utilisateur d'authentification de votre application IRIS, vous devez étendre %CSP.Login et surcharger certaines méthodes en fonction de vos besoins. Cet article va détailler ces méthodes et ce que vous pouvez faire avec elles. En outre, vous obtiendrez une explication du mécanisme d'authentification déléguée fourni par la routine ZAUTHENTICATE.mac. Enfin, vous serez en mesure de créer une logique d'authentification personnalisée, y compris la possibilité de valider les utilisateurs existants dans d'autres référentiels de données non IRIS et différentes règles d'authentification.

Exemple de demande pour cet article

Pour illustrer et apprendre à connaître %CSP.Login et ZAUTHENTICATE.mac, installez l'exemple d'application en suivant les étapes suivantes :

1. Si vous souhaitez effectuer l'installation avec ZPM :

zpm:USER>install custom-login

2. Si vous voulez installer avec Docker

Clonez/git pull le repo dans n'importe quel répertoire local :

git clone https://github.com/yurimarx/custom-login.git

Ouvrez le terminal dans ce répertoire et exécutez :

docker-compose up -d --build

Comment développer une page de connexion à partir de %CSP.Login

Pour développer une page de connexion, suivez les étapes suivantes :

1. Créez une classe étendant %CSP.Login :

Class dc.Sample.CustomLogin Extends %CSP.Login
{
}

2. Surchargez la méthode de classe OnLoginPage et écrivez du code ObjectScript et HTML (entre &html<>). Il est nécessaire de rendre votre interface de connexion personnalisée (vous pouvez copier l'implémentation de OnLoginPage à partir de %CSP.Login et la modifier en fonction de vos besoins) :

Include (%sqlui, %sySystem, %products)

Class dc.Sample.CustomLogin Extends %CSP.Login
{

ClassMethod OnLoginPage() As %Status
{
    // text strings
    Set ConfigName = $PIECE($ZUTIL(86),"*",2)
    // get key, lookup in localization global
    Set tLang = $$$SessionLanguage
    Set tTitle = $$FormatText^%occMessages($$$GetSysMessage(tLang,..#DOMAIN,"logintitle","Login %1"),ConfigName)
    Set tPrompt = $$$GetSysMessage(tLang,..#DOMAIN,"loginenter","Please login")
    Set tUserName = $$$GetSysMessage(tLang,..#DOMAIN,"loginusername","User Name")
    Set tPassword = $$$GetSysMessage(tLang,..#DOMAIN,"loginpassword","Password")
    Set tLogin = $$$GetSysMessage(tLang,..#DOMAIN,"login","LOGIN")
    Set OtherAutheEnabled = $$OtherAuthEnabled^%SYS.cspServer(%request)
    &html<<html>>
    Do ..DrawHEAD(tTitle)

    &html<
        <body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0" onload="pageLoad();">
        <div id="content">>
    Do ..DrawTitle(tTitle)

    &html<
    <div style="background-color:#FBFBFB;">
    <table border="0" cellpadding="10" align="center" class="LayoutTable">
    <tr>
    <td align="center">
    <table border="0" width="100%" cellpadding="5" cellspacing="0">>
    &html<<tr><td style="height:90px;"><br/></td></tr>>
    &html<<tr><td><center>>
  If OtherAutheEnabled = 1 {
    // Show standard login form
    &html<
   
    <form name="Login" method="post" action="#($ZCONVERT($GET(%request.Data("Error:FullURL",1)),"O","HTML"))#" autocomplete="off">>
    Write ..InsertHiddenFields($ZCONVERT($GET(%request.Data("Error:URL",1)),"O","HTML"))

    &html<
    <h1>Custom Login</h1>
    <table class="login" border="0" cellspacing="10" cellpadding="10" >
    <tr valign="bottom">
    <td nowrap class="loginCaption">#(tUserName)#</td>
    <td><input type="text" size="30" name="IRISUsername" autocomplete="off" value="#($ZCONVERT($GET(%request.Data("IRISUsername",1)),"O","HTML"))#"/>
    </td>
    </tr>
    <tr valign="bottom">
    <td nowrap class="loginCaption">#(tPassword)#</td>
    <td><input type="password" size="30" name="IRISPassword" autocomplete="off"/>
    </td>
    </tr>
    <tr><td>&nbsp;</td>
     <td style="text-align:right"><input type="submit" name="IRISLogin" class="button" value="#(tLogin)#" style="width:120px;"></td>
    </tr>
    </table>
    </form></center></div>>
  }  // End OtherAutheEnabled = 1 block
  Else {
      // This is accessed when IRIS is installed with minimum security and user clicked Logout.
      Set msg1 = $$$GetSysMessage(tLang,..#DOMAIN,"loginclickhere","Please click here to log in.")
      Set tLink = ..Link("/csp/sys/UtilHome.csp")
    &html<
    <a href="#(tLink)#" class="loginlink">#(msg1)#</a>
    </center>
    </td>
    </tr>
    </table>
    >
  }

    // test for error
    Set tMsg = $GET(%request.Data("Error:ErrorCode",1))
    If ((tMsg'="")&&($SYSTEM.Status.GetErrorCodes(tMsg)'[$$$ERRORCODE($$$RequireAuthentication))) {
        &html<<tr><td><center>>
        Do ShowError^%apiCSP(tMsg)
        &html<</center></td></tr>>
    }

    &html<</td></tr><tr><td style="height:180px;"><br/></td></tr></table></div></div></body></html>>
    Quit $$$OK
}

}

3. Prêtez attention à certains points liés à la mise en œuvre susmentionnée :

a. Pour écrire du code HTML, utilisez &html<HTML TAGS HERE>.
b. Utilisez $$OtherAuthEnabled^%SYS.cspServer pour tester si une authentification préliminaire est nécessaire.
c. Utilisez Do ..DrawHEAD(tTitle) pour écrire le HEAD de la page HTML.
d. Utilisez Do ..DrawTitle(tTitle) pour écrire la section titre de la page HTML.
e. Utilisez Write ..InsertHiddenFields pour écrire tous les champs HTML cachés requis par votre logique d'authentification sur le backend.
f. tTitle est une variable permettant d'écrire le titre de la page HTML.
g. J'ai composé le code HTML <h1>Custom Login</h1> pour vous montrer un code HTML personnalisé pour la page de connexion de l'interface utilisateur.

4. Vous pouvez remplacer le DrawHEAD si vous souhaitez insérer le code JavaScript et CSS nécessaire à l'exécution de votre page.
5. Attribuez à l'utilisateur de la passerelle Web (CSPSystem) la permission de consulter la base de données pour connaître l'emplacement de la page de connexion personnalisée :

a. Attribuez la ressource de base de données souhaitée au rôle approprié, puis donnez ce rôle à l'utilisateur CSPSystem.
b. Dans cet exemple, le login personnalisé est nécessaire pour l'application csp/sys, de sorte que l'attribution du rôle %DB_IRISSYS à l'utilisateur CSPSystem est cruciale, car csp/sys utilise la base de données %SYS.
c. Si la page de connexion était créée pour une application installée dans l'espace de noms USER, le rôle attribué serait %DB_USER.
d. Dans mon cas, j'ai configuré la permission appropriée dans le script qui s'exécute avec mon fichier iris.script sur cet exemple d'application :

Do ##class(Security.Users).AddRoles("CSPSystem", "%DB_IRISSYS")

6. Définissez votre classe de connexion à l'application requise pour utiliser cette nouvelle page de connexion :

a. Dans mon exemple, j'ai choisi une classe du projet sans mot de passe (https://openexchange.intersystems.com/package/passwordless) appelée SetupUtil. Elle applique la liaison lors de l'appel de la méthode Apply class. J'ai également personnalisé le code de la méthode pour qu'il pointe vers ma classe de connexion :

Class dc.Sample.SetupUtil
{

Parameter GN = "^%ZAPM.AppsDelegate";
/// write ##class(dc.login.SetupUtil).Apply("/csp/sys")
ClassMethod Apply(app = "/csp/sys", user = "", pass = "", mode = "0")
{
    Quit:app="" $$$OK
    New $NAMESPACE
    If $NAMESPACE'="%SYS" {
        Write !,""
        If $$EXIST^%R("ZAUTHENTICATE.mac","%SYS") {
            Set msg="Routine ZAUTHENTICATE.mac in %SYS already installed"
            Write !,msg
            If mode=1 Quit $$$OK
            If mode=2 Quit $$$ERROR($$$GeneralError,msg)
        }
        Set tempFile = ##class(%File).TempFilename("xml")
        Set list("ZAUTHENTICATE.MAC")=""
        Set list("dc.Sample.CustomLogin.CLS")=""
        Set list("dc.Sample.SetupUtil.CLS")=""
        Set st=$SYSTEM.OBJ.Export(.list, tempFile)
        Set $NAMESPACE="%SYS"
        Set st = $SYSTEM.OBJ.Load(tempFile, "c")
        If 'st {
            Set msg=$SYSTEM.Status.GetErrorText(st) Write !,msg
            Quit $$$ERROR($$$GeneralError,msg)
        }
    }
    Set $NAMESPACE="%SYS"
    Set:app="" app= $SYSTEM.Util.GetEnviron("ISC_app")
    Set:app="" app="/csp/sys"
    // Edit Security Authentication/Web Session Options
    Set ss=##class(Security.System).%OpenId("SYSTEM")
    If '$ZBOOLEAN(ss.AutheEnabled, $$$AutheDelegated, 1) {
        Set ss.AutheEnabled = ss.AutheEnabled + $$$AutheDelegated
    }
    Set st=ss.%Save()
    If 'st Quit st
   
    If app="*" {
        Set result=##CLASS(%ResultSet).%New("%DynamicQuery:SQL")
        Set tSC=result.Prepare("select Name FROM Security.Applications")
        Set:tSC tSC=result.Execute()
        If '$$$ISOK(tSC) {
            Set text="Application setup error :"_$SYSTEM.Status.GetErrorText(tSC)  
            Write !,text
            Quit $$$ERROR(text)
        }
        Else {
            While result.Next() {
                Set CSP=result.Data("Name")
                Set csp=$ZCONVERT(CSP,"L")
                Set st=..AddDelegate(csp,user,pass)
            }
        Write !,"OK"
        }  
    }
    ElseIf app["," {
        Set a=""
        For i=1:1:$LENGTH(app,",") { Set a=$PIECE(app,",",i)
            Continue:a=""
            Set st=..AddDelegate(a,$PIECE(user,",",i),$PIECE(pass,",",i))
        }
    }
    Else {
        Set st=..AddDelegate(app,user,pass)
    }
    Quit $$$OK
}

ClassMethod AddDelegate(app, user = "", pass = "")
{
    Set st=##class(Security.Applications).Get(app,.par)
    If st {
        If par("AutheEnabled")=$$$AutheUnauthenticated Quit $$$OK
        // Remove unauthenticated and add delegated
        If $ZBOOLEAN(par("AutheEnabled"), $$$AutheUnauthenticated, 1) {
            Set par("AutheEnabled") = par("AutheEnabled") - $$$AutheUnauthenticated
        }
        If '$ZBOOLEAN(par("AutheEnabled"), $$$AutheDelegated, 1) {
            Set par("AutheEnabled") = par("AutheEnabled") + $$$AutheDelegated
        }
        Set par("LoginPage") = "dc.Sample.CustomLogin.cls"
        Set st=##class(Security.Applications).Modify(app,.par)
        If st {
            Set acc=""
            If user'="" Set acc=$LISTBUILD(user,pass)
            If $EXTRACT(app,*)'="/" Set app=app_"/"
            Set @..#GN@(app)=acc
        }
    }
    Quit st
}

}

b. J'ai utilisé la fonctionnalité IPM (ou ZPM) invoked pour exécuter la méthode Apply (sur le fichier module.xml) juste après avoir démarré IRIS sur mon instance Docker :

<Invokes>
        <Invoke Class="dc.Sample.SetupUtil" Method="Apply">
          <Arg>/csp/sys</Arg>
          <Arg>SuperUser</Arg>
          <Arg>SYS</Arg>
        </Invoke>
</Invokes>

c. Si vous souhaitez le faire manuellement (définir une page de connexion pour une application), suivez les instructions suivantes :

i. Allez dans Administration du système > Sécurité > Applications > Applications Web.


ii. Sélectionnez l'application csp/sys et définissez le champ Login Page avec votre classe de connexion (dans mon cas, il s'agit de dc.Sample.CustomLogin).

7. Vous êtes prêt ! Vous avez maintenant une simple page de connexion personnalisée pour l'application Portail de gestion !
8. Pour plus de détails, consultez la page de documentation ci-dessous :

https://docs.intersystems.com/iris20233/csp/docbook/Doc.View.cls?KEY=ASCWL.

Comment développer un code d'authentification personnalisé (délégué) ?

Très souvent, en plus de la page de connexion personnalisée, il est nécessaire de mettre en œuvre une logique personnalisée pour authentifier l'utilisateur. Dans ce cas, vous devez fournir le code personnalisé dans le fichier AUTHENTICATE.mac. Dans InterSystems IRIS, ce modèle d'authentification est connu sous le nom d'authentification déléguée. Cela signifie qu'InterSystems IRIS vous confie la rédaction et l'application des requêtes et des règles nécessaires pour valider et accepter l'utilisateur en tant qu'utilisateur réel. La logique personnalisée ici fournira des informations personnelles et des permissions d'accès (autorisation) que l'utilisateur doit avoir.
Pour configurer l'authentification déléguée, suivez les instructions suivantes :
1. Allez dans Administration du système > Sécurité > Sécurité du système > Authentification/Options de session web :

2. Cochez l'option Autoriser l'authentification déléguée :


3. Créez un dossier mac dans le dossier src.
4. Créer un fichier ZAUTHENTICATE.mac dans le dossier mac.
5. Écrire la logique suivante :

ROUTINE ZAUTHENTICATE
ZAUTHENTICATE(ServiceName,Namespace,Username,Password,Credentials,Properties) PUBLIC {

#include %occErrors
#include %sySecurity
 Set $ZTRAP="Error"
 Quit $SYSTEM.Status.OK()

Error //Handle any COS errors here
  //Reset error trap to avoid infinite loop
  Set $ZTRAP=""
  //Return the generalized COS error message #5002
  Quit $SYSTEM.Status.Error(5002 /*$$$CacheError*/,$ZERROR)
}

GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public {

    // For console sessions, authenticate as _SYSTEM.
    If ServiceName="%Service_Console" {  
        Quit $SYSTEM.Status.Error($$$GetCredentialsFailed)
    }

    // For bindings connections, use regular prompting.
    If ServiceName="%Service_Bindings" {
        Quit $SYSTEM.Status.Error($$$GetCredentialsFailed)
    }

    Set validUser = 0
    If ServiceName="%Service_WebGateway" {
        set Username=$get(%request.Data("IRISUsername",1))
        set Password=$get(%request.Data("IRISPassword",1))

        if Username '= "" {
            Set validUser = ##class(%SYSTEM.Security).Login(Username, Password)
       
            if validUser = 0 {
                set str=##class(%Stream.FileCharacter).%New()
                do str.LinkToFile("/usr/irissys/lib/csv")
           
                while 'str.AtEnd {
                    set $listbuild(UserFullName,UserNamespace,
                                UserPassword,UserUsername,UserPhone) = $listfromstring(str.ReadLine())
                    if (UserUsername = Username) && (UserPassword = Password)  {
                        If %request.Application="/csp/sys/" {
                            Set validUser = 1
                            Set status = ##class(Security.Users).Create(
                                UserUsername,"%All",UserPassword,
                                UserFullName,UserNamespace,"","",0,1,"",1,0,"",1,1,1)
                            if $$$ISOK(status) {
                                set validUser = 1
                            } else {
                                set validUser = 0
                            }
                                                   }
                    }
                }
            }
        } Else {
            set validUser = 2
        }
    }
   
    if validUser = 1 {
        Quit $SYSTEM.Status.OK()
    } elseif validUser = 2 {
        Quit $SYSTEM.Status.Error($$$GeneralError,"Provide your username and password")
    } else {
        Quit $SYSTEM.Status.Error($$$AccessDenied)
    }

}

a. GetCredentials est le point d'écriture de la logique d'authentification personnalisée dans le ZAUTHENTICATE. Cette méthode a les arguments suivants (extraits de https://docs.intersystems.com/iris20233/csp/docbook/DocBook.UI.Page.cls?KEY=GAUTHN_delegated) :

i. ServiceName - chaîne de caractères représentant le nom du service utilisé pour connecter l'utilisateur à InterSystems IRIS (par exemple, %Service_Console ou %Service_WebGateway).
ii. Namespace - chaîne de caractères définissant l'espace de noms sur le serveur InterSystems IRIS avec lequel une connexion est établie. Elle est conçue pour être utilisée avec des services %Service_Bindings tels que Studio ou ODBC.
iii. Username - chaîne décrivant le nom du compte saisi par l'utilisateur qui doit être validé par le code de la routine.
iv. Password - Chaîne de caractères décrivant le mot de passe saisi par l'utilisateur et qui doit être validé.
v. Credentials - Ils ne sont pas implémentés dans cette version d'InterSystems IRIS et sont transmis par référence.
vi. Propriétés - transmises par référence, elles représentent un tableau de valeurs renvoyées qui définissent les caractéristiques du compte désigné par Username, telles que l'adresse électronique.

b. Nous avons utilisé $ISOBJECT($GET(%request)) pour tester s'il s'agit d'une authentification web.
c. $get(%request.Data("IRISUsername",1)) et $get(%request.Data("IRISPassword",1)) récupèrent le nom d'utilisateur et le mot de passe de la page de connexion.
d. ##class(%SYSTEM.Security).Login(Username, Password) authentifie l'utilisateur existant. Si l'utilisateur est nouveau, il renvoie validUser = 0.
e. set str=##class(%Stream.FileCharacter).%New() and do str.LinkToFile("/usr/irissys/lib/csv") instructions charge la liste des superutilisateurs mentionnés dans le fichier superusers.csv. Ces utilisateurs n'existent pas dans IRIS et sont créés lors de la première connexion.
f. ##class(Security.Users).Create(...) crée l'utilisateur fourni par la page de connexion si cet utilisateur existe dans le fichier superusers.csv. Si l'utilisateur est déjà formé, il est défini à validUser = 1.
g. Si validUser est égal à 1, l'authentification renvoie un succès en utilisant Quit $SYSTEM.Status.OK(). Sinon, elle renvoie Quit $SYSTEM.Status.Error(...)
h. Le contenu du fichier avec les superutilisateurs est illustré ci-dessous :

En savoir plus sur le login personnalisé

Si vous souhaitez en savoir plus sur le login personnalisé, consultez les ressources ci-dessous :

  1. https://docs.intersystems.com/iris20233/csp/docbook/Doc.View.cls?KEY=ASCWL
  2. https://docs.intersystems.com/iris20233/csp/docbook/DocBook.UI.Page.cls?KEY=GAUTHN_delegated
  3. https://openexchange.intersystems.com/package/passwordless
  4. https://openexchange.intersystems.com/package/Google-IRIS-Login
Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer