Si l’application contient des informations commerciales ou privées, il est judicieux de restreindre l’accès aux données en ajoutant un formulaire d’authentification.

My Visual Database dispose d’un système de contrôle d’accès intégré. Pour l’activer, allez dans l’onglet «Tables de la base de données (Database Tables) » et cliquez sur le bouton « Contrôle d’accès » (1). Dans la fenêtre qui s’ouvre, cochez la case “Activer le contrôle d’accès” (2), puis créez une liste de rôles (4) à l’aide des boutons d’édition (3). Pour que les modifications prennent effet, n’oubliez pas de cliquer sur le bouton “OK” (5).

L’onglet “Paramètres” contient des options supplémentaires qui déterminent l’apparence de l’élément d’entrée de connexion (1) et la possibilité de saisir un mot de passe vierge (2).

Après avoir enregistré les paramètres, deux tables système apparaîtront dans le projet : _role et _user.

Pour chaque utilisateur du programme, vous devez créer un compte dans la table _user et attribuer un rôle qui détermine les droits d’accès. En plus du mot de passe et de la connexion, le compte stocke des informations sur le nom de l’utilisateur, son adresse e-mail, la date de création du compte et l’heure de la dernière connexion au programme. Le système d’autorisation intégré est décrit plus en détail dans mon livre “Visual Programming”.

Vous ne pouvez pas supprimer ou modifier les champs de service dans ces tables, mais vous pouvez ajouter vos propres champs si nécessaires.

Au premier lancement du programme, une entrée sera automatiquement ajoutée à la table _user afin que vous puissiez vous connecter au programme en tant qu’administrateur et ajouter des comptes avec des rôles.

Maintenant, avant l’apparition du formulaire principal de l’application, le formulaire d’autorisation s’affiche :

À mon avis, le formulaire souffre d’un faible contenu informatif et sort du style habituel. Mais avec l’aide de scripts, la situation peut être corrigée. Examinons de plus près le formulaire à l’aide de l’outil « Explorateur de composants », qui fait partie de la « Référence du développeur ».

Connaissant les noms du formulaire et des composants, vous pouvez les modifier, rendant le formulaire plus compréhensible et fonctionnel à l’aide de la procédure App_LoginForm_OnActivate(), qui sera appelée à l’aide de l’événement onActivate. Habituellement, l’événement onShow est utilisé à ces fins, mais dans l’application, il est utilisé par le code système, et si vous affectez votre propre gestionnaire à l’événement onShow, le formulaire d’autorisation cessera de fonctionner

procedure App_LoginForm_OnActivate(Sender: TObject);
// activation du formulaire de connexion
// l'événement OnShow est utilisé par le système, il ne peut donc pas être remplacé
var 
  frmpForm:TForm;
  tmpImage:TImage;
  tmpButton: TButton;
  tmpPanLogin: TPanel;
  tmpPanPassword: TPanel;
  tmpEdtLogin: TEdit;
  tmpEdtPassword: TEdit;
  tmpCombo: TdbCombobox;
  i: integer;
  tmpImageFileName :string;
  tmpLabel:TLabel;
  tmpCheck: TdbCheckBox;
  tmpLogin:string;
  // pour l'optimisation du code (moins de copier-coller)

  procedure SetAttr( ACont: TControl; AParent:TWinControl; AMTop: integer; AMBottom: integer; AMLeft:
 integer; AMRight: integer; );
  begin
    with ACont do
    begin
      Parent := AParent;
      AlignWithMargins := True;
      Margins Top := AMTop;
      Margins.Bottom := AMBottom;
      Margins.Left := AMLeft;
      Margins.Right := AMRight;
      // Align := // non implémenté dans MVDB, mais TControl le fait
    end;
  end;
begin
  FindC(frmdbCoreLogin,'labLogin',tmpLabel,False);
  // ce qui est ci-dessous est exécuté une fois, lorsque le formulaire est ouvert pour la première fois
  if tmpLabel = nil then
  begin
    // les composants principaux peuvent être trouvés par leur nom
    FindC(frmdbCoreLogin,'Image1',tmpImage);
    FindC(frmdbCoreLogin,'bLogin',tmpButton);
    FindC(frmdbCoreLogin,'pnPassword',tmpPanPassword);
    FindC(frmdbCoreLogin,'pnLogin',tmpPanLogin);
    FindC(frmdbCoreLogin,'edPassword',tmpEdtPassword);
    FindC(frmdbCoreLogin,'edLogin',tmpEdtLogin);
    //
    FindC(frmdbCoreLogin,'cmbLogin',tmpCombo,False);
    if tmpCombo = nil then
    begin // le composant déroulant peut être trouvé par type
      // mais il peut ne pas être là si la méthode de saisie utilisateur - l'option Champ de texte est sélectionnée dans les options de contrôle d'accès
      for i:=0 to frmdbCoreLogin.ComponentCount - 1 do
        if frmdbCoreLogin.Components[i] is TdbCombobox then
        begin
          tmpCombo := TdbCombobox(frmdbCoreLogin.Components[i]);
          tmpCombo.Name := 'cmbLogin';
          tmpCombo.Text := '';
          break;
        end;
    end;
    // lit le login enregistré si cette option était active
    tmpLogin := IniFile_Read(APP_LOGIN_SECTION,APP_LOGIN_NAME, '' );
    // le titre et la version sont affichés dans l'en-tête du formulaire
    frmdbCoreLogin.Caption := APP_NAME + ' ' + App_GetVersion(False);
    tmpImageFileName := ExtractFilePath(Application.ExeName)+APP_LOGIN_LOGO_FILE_NAME;
    if not FileExists(tmpImageFileName) then
      RaiseException('App_InitLoginForm() File not found '+tmpImageFileName)
    else
      tmpImage.Picture.LoadFromFile(tmpImageFileName);
    // la forme sera rectangulaire, horizontale
    frmdbCoreLogin.Width := 470;
    frmdbCoreLogin.Height := 250;
    // il y aura un panneau sur la droite
    with tmpPanPassword do
    begin
      Width := 220;
      Align := alRight;
    end;
    // autre panneau - espace restant
    with tmpPanLogin do
    begin
      Align := alClient;
    end;
    // gauche - image
    SetAttr( tmpImage, tmpPanLogin, 8, 8, 8, 0 );
    with tmpImage do
    begin
      Align := alClient;
      Proportional := False;
    end;
    // transfert tout le reste vers le panneau tmpPanPassword
    // étiquette pour le champ de saisie de connexion
    tmpLabel := TLabel.Create(frmdbCoreLogin);
    SetAttr( tmpLabel, tmpPanPassword, 8, 0, 8, 8 );
    with tmpLabel do
    begin
      Name := 'labLogin';
      Font.Size := 11;
      Caption := 'Login:';
      top := 0;
      Align := alTop;
    end;
    //
    SetAttr( tmpEdtLogin, tmpPanPassword, 2, 0, 8, 8 );
    with tmpEdtLogin do
    begin
      Font.Size := 11;
      Height := 24;
      BorderStyle := bsSingle;
      Top := tmpLabel.Top + tmpLabel.Height + 1;
      Align := alTop;
    end;
    //
    if tmpCombo <> nil then
    begin
      SetAttr( tmpCombo, tmpPanPassword, 2, 0, 8, 8 );
      with tmpCombo do
      begin
        Font.Size := 11;
        Top := tmpLabel.Top + tmpLabel.Height + 1;
        Align := alTop;
      end;
    end;
    //
    tmpLabel := TLabel.Create(frmdbCoreLogin);
    SetAttr( tmpLabel, tmpPanPassword, 8, 0, 8, 8 );
    with tmpLabel do
    begin
      Name := 'labPassword';
      Font.Size := 11;
      Caption := 'Password:';
      Top := tmpEdtLogin.Top + tmpEdtLogin.Height + 1;
      Align := alTop;
    end;
    //
    SetAttr( tmpEdtPassword, tmpPanPassword, 2, 0, 8, 8 );
    with tmpEdtPassword do
    begin
      Font.Size := 11;
      Height := 24;
      BorderStyle := bsSingle;
      Top := tmpLabel.Top + tmpLabel.Height + 1;
      Align := alTop;
    end;
    //
    if APP_LOGIN_SAVE_LAST_LOGIN then
    begin
      tmpCheck := TdbCheckBox.Create(frmdbCoreLogin);
      SetAttr( tmpCheck, tmpPanPassword, 0, 8, 8, 8 );
      with tmpCheck do
      begin
        Name := 'chbSaveLogin';
        Caption := 'Save login';
        Font.Size := 11;
        Top := frmdbCoreLogin.ClientHeight - Height;
        Align := alBottom;
        Checked := tmpLogin <> '';
      end;
    end;
    //
    SetAttr( tmpButton, tmpPanPassword, 8, 8, 8, 8 );
    with tmpButton do
    begin
      //
      if tmpCheck <> nil then
        Top := tmpCheck.Top - Height
      else
        Top := frmdbCoreLogin.ClientHeight - Height;
      Align := alBottom;
    end;
    // si le mode de sélection dans la liste est activé
    if tmpCombo <> nil then
    begin
      tmpCombo.TabOrder := 0;
      tmpCombo.ItemIndex := tmpCombo.Items.IndexOf(tmpLogin);
      if tmpLogin = '' then
        tmpCombo.SetFocus;
    end
    else
    begin
      tmpEdtLogin.TabOrder := 0;
      tmpEdtLogin.Text := tmpLogin;
      if tmpLogin = '' then
        tmpEdtLogin.SetFocus;
    end;
    //
    tmpEdtPassword.TabOrder := 1;
    if tmpLogin <> '' then
      tmpEdtPassword.SetFocus;
    tmpButton.TabOrder := 2;
    if tmpCheck <> nil then
    tmpCheck.TabOrder := 3;
    //
    frmdbCoreLogin.OnHide := @App_LoginForm_OnClose;
  end;
end;Langage du code : Delphi (delphi)

Il y a beaucoup de code, mais le résultat est excellent :

  • le formulaire a acquis un format horizontal familier au bureau ;
  • les champs de saisie ont des étiquettes avec leurs descriptions ;
  • le nom et la version du programme sont affichés dans l’en-tête du formulaire ;
  • l’image est la carte de visite de l’application ;
  • l’option pour enregistrer la dernière connexion sélectionnée a été ajoutée.

L’enregistrement de la dernière connexion sélectionnée réduit le temps d’accès au programme, même si cela réduit légèrement la sécurité. Mais rien de plus que d’utiliser une liste déroulante pour sélectionner une connexion. Cette option peut être désactivée en modifiant la valeur de la variable globale
APP_LOGIN_SAVE_LAST_LOGIN.

Incluez le gestionnaire App_LoginForm_OnActivate() dans le bloc principal :

begin
 // appelé ici, avant l'affichage du formulaire principal et avant le démarrage de la logique d'application principale.
 frmdbCoreLogin.onActivate := @App_LoginForm_OnActivate;
end.
Langage du code : Delphi (delphi)

La connexion est enregistrée à la fermeture du formulaire :

procedure App_LoginForm_OnClose(Sender: TObject; ); 
// ferme le formulaire de connexion
var 
  tmpCheck: TdbCheckBox;
  tmpCombo: TdbCombobox;
  tmpEdtLogin: TEdit;
  tmpLogin:string;
begin
  // si nécessaire, écrivez les paramètres
  FindC(frmdbCoreLogin,'edLogin',tmpEdtLogin);
  FindC(frmdbCoreLogin,'cmbLogin',tmpCombo,False);
  FindC(frmdbCoreLogin,'chbSaveLogin',tmpCheck,False);
  // peut être soit une liste déroulante, soit un champ de saisie de connexion
  if tmpCombo <> nil then
    tmpLogin := tmpCombo.Text
  else
    tmpLogin := tmpEdtLogin.Text;
  // s'il y a un vérificateur et qu'il est défini, enregistrez la connexion
  if (tmpCheck <> nil) and tmpCheck.Checked then
  begin
    IniFile_Write(APP_LOGIN_SECTION,APP_LOGIN_NAME, tmpLogin );
  end
  else
  begin
    IniFile_Write(APP_LOGIN_SECTION,APP_LOGIN_NAME, '' );
  end;
  // réinitialiser le mot de passé
  TEdit( GetC(frmdbCoreLogin,'edPassword') ).Text := '';
end;
Langage du code : Delphi (delphi)

Droits des menus

Dans My Visual Database, des droits de rôle sont attribués aux éléments de formulaire : boutons, tables et champs de saisie de données. Mais la configuration du menu principal par rôle n’est pas fournie. Les procédures suivantes nous aideront à corriger ce défaut :

  • Menu_AddAccessRight() – ajouter des autorisations par rôle
  • Menu_CheckAccessRight() – définir la visibilité du menu par rôle

Il sera également utile d’apporter des améliorations à celle décrite précédemment dans l’article “Mouvements et formulaires simples” (d’une série d’articles “Production”) fonction Menu_Add() en ajoutant un paramètre supplémentaire – liste des rôles pour lesquels cet élément de menu sera disponible.

Var 
  MenuAccessList:TStringList;

procedure Menu_AddAccessRight( AItem: TMenuItem; ARoleList:string );
// ajout de droits à l'élément de menu
// AItem - élément de menu
// ARoleList - liste des rôles autorisés à accéder
Begin // la liste est créée au premier appel
  if MenuAccessList = nil then
    MenuAccessList := TStringList.Create;
   MenuAccessList.AddObject( ARoleList, AItem );
end;

procedure Menu_CheckAccessRight;
// définition de la visibilité des éléments de menu en fonction des droits d'accès - le rôle de l'utilisateur actuel
Var AItem: TMenuItem;
  i: integer;
begin
  if MenuAccessList <> nil then
  begin
    for i:=0 to MenuAccessList.Count - 1 do
    begin
      AItem := TMenuItem( MenuAccessList.Objects(i) );
      AItem.Visible := pos( Application.User.Role, ','+MenuAccessList.Strings(i)+',' ) > 0;
    end;
  end;
end;

function Menu_Add( AName:string; ACaption: string; AParentItem:TMenuItem; AIndex:integer = -1; AOnClick:string = ''; ARoleList:string = ''; ):TMenuItem;
// ajout d'un élément de menu
// AName - nom de l'élément ; l'action et le paramètre sont définis : <action>_<paramètre> ; actions : Afficher -
 afficher le formulaire sur le panneau de travail du formulaire principal, paramètre - nom du formulaire
// ACaption - le nom affiché de l'élément de menu
// AParentItem - élément parent ; si nul, alors l'élément de menu est ajouté au niveau supérieur
// AIndex - l'endroit où nous insérons ; -1 - ajouter à la fin.
// AOnClick - gestionnaire
// ARoleList - liste des rôles pour lesquels l'élément de menu sera visible ; si vide, alors visible par tout le monde
Var 
  tmpForm:TForm;
begin
  tmpForm := MainForm; // travail avec le menu sur le formulaire principal
  Result := TMenuItem.Create( tmpForm );
  // si le nom est spécifié, alors nous le formatons selon la norme de nommage
  if AName <> '' then
    Result.Name := T_MENU_ITEM + AName; // add class prefix
  Result.Caption := ACaption;
  // si le gestionnaire n'est pas spécifié, alors assigne un gestionnaire par défaut
  if AOnClick = '' then
    AOnClick := 'Menu_ItemOnClick';
  // si le gestionnaire n'est pas désactivé, alors ajoutez-le
  if AOnClick <> '-' then
    Result.OnClick := AOnClick;
  // si l'élément parent n'est pas spécifié, alors
  if AParentItem = nil then
  begin // ajoute un élément de menu au niveau supérieur
    if AIndex = -1 then
      tmpForm.Menu.Items.Add(Result)
    else // ou insère à la position spécifiée dans le paramètre
      tmpForm.Menu.Items.Insert(AIndex,Result);
  end
  else // si spécifié, alors
  begin // ajouter en tant qu'enfant
    if AIndex = -1 then
      AParentItem.Add(Result)
    else // ou insère à la position spécifiée dans le paramètre
      AParentItem.Insert(AIndex,Result);
  end;
  if ARoleList <> '' then // ajouter des droits par roles
    Menu_AddAccessRight( Result, ARoleList );
end;Langage du code : Delphi (delphi)

Exemple d’utilisation

J’ai créé trois rôles dans l’application Discount :

  • Directeur
  • Manager
  • Administrateur

Le Gérant a accès à toutes les informations commerciales, à l’exception du guide des réductions qui ne peut être édité que par le Directeur. Le rôle d’administrateur est nécessaire pour restreindre l’accès au concepteur de rapports, à l’exportation et à l’importation de données pour le gestionnaire et le directeur.

Bien que l’utilisateur admin ait le rôle Admin, qui restreint l’accès aux données, vous devez comprendre que l’utilisateur qui a une marque Admin – Oui, peut facilement contourner cette restriction en créant un nouvel utilisateur avec le rôle souhaité. Par conséquent, le rôle d’administrateur est davantage un rôle cosmétique, masquant les éléments du menu principal dont l’administrateur n’a pas besoin.

L’initialisation du menu principal ressemble à ceci :

procedure UserApp_InitForm; 
// initialisation du formulaire
var 
  tmpItem:TMenuItem;
begin
  try
    // menu
    tmpItem := Menu_Add('','References',nil,1, '-','Director,Manager'); // caché de l'admin
    Menu_Add('Show_frmClient','Clients',tmpItem);
    Menu_Add('Show_frmDiscount','Discounts',tmpItem,-1,'','Director'); // available only to directcor
    //
    frmMain.mniFile.Caption := 'Logs';
    Menu_Add('Show_frmSale','Sales log',frmMain.mniFile,0,'','Director,Manager'); // hide from admin
    Menu_Add('','-',frmMain.mniFile,1, '-','Director, Manager');
    //
    tmpItem := Menu_Add('HelpTopic','?',nil,-1, '-');
    Menu_Add('Help','Help',tmpItem,0,'Help_Show');
    Menu_Add('','-',tmpItem,1, '-');
    Menu_Add('AboutEx','About',tmpItem,2,'App_ShowCoreAbout');
    Menu_HideItem('mniAbout');
    // élément très utile - changer d'utilisateur sans redémarrer le programme
    Menu_Add('','Change user ',frmMain.mniOptions,1, 'App_Relogin');
    // cache les éléments de service aux utilisateurs réguliers
    Menu_AddAccessRight( TMenuItem( GetC(frmMain,'mniReport') ) ,'Admin');
    Menu_AddAccessRight( TMenuItem( GetC(frmMain,'mniExportData') ) ,'Admin');
    Menu_AddAccessRight( TMenuItem( GetC(frmMain,'mniImportData') ) ,'Admin');
    //
    Menu_CheckAccessRight; // définit les permissions par roles
  except
    RaiseException('UserApp_InitForm() - '+ExceptionMessage);
  end;
end;Langage du code : Delphi (delphi)

Faites attention au terme 23, dans lequel un élément de menu est créé pour changer d’utilisateur sans
redémarrer le programme. La procédure du gestionnaire App_Relogin() ressemble à ceci :

procedure App_Relogin(Sender:TObject); 
// affichage du formulaire standard "A propos"
var 
  tmpID: integer;
begin
  tmpID := Application.User.id;
  frmdbCoreLogin.ShowModal;
  // L'utilisateur a changé ?
  if tmpID <> Application.User.id then
  begin
    Menu_CheckAccessRight; // appliquer de nouveaux droits
    App_CloseAllWindow; // ferme toutes les fenêtres
  end;
end;
Langage du code : Delphi (delphi)

Si l’utilisateur a changé, nous modifions la visibilité des éléments du menu principal et fermons tous les formulaires précédemment ouverts à l’aide de la procédure App_CloseAllWindow(), qui est écrite en tenant compte de la possibilité d’utilisation du navigateur.

function GetC(AForm: TForm; AName: string;):TComponent; 
// fonction alternative Form.FindComponent
begin
  try
    Result := AForm.FindComponent(AName);
  except
    if AForm = nil then
      RaiseException('GetС() - form does not exist')
    else
      RaiseException('GetС('+AForm.Name+','+AName+') component not found');
  end;
end;
Langage du code : Delphi (delphi)

Note

Des constantes sont requises pour que l’authentification fonctionne :

const
  APP_LOGIN_LOGO_FILE_NAME = 'Images\Logo\logo_190x190.jpg';
  APP_LOGIN_SAVE_LAST_LOGIN = True;
  APP_LOGIN_SECTION = 'LOGIN'; // section du fichier d'initialisation
  APP_LOGIN_NAME = 'login'; // le nom du paramètreLangage du code : Delphi (delphi)

Veuillez noter que certaines procédures et fonctions sont précédées du nom du module dans lequel elles sont stockées. Par exemple, App_CloseAllWindow() est stocké dans le module app.pas et Menu_Add() est stocké dans le module menu.pas . Le préfixe n’est pas spécifié pour les procédures et les fonctions de nature système fréquemment utilisées. Ils sont stockés dans le module utils.pas. Par exemple, la fonction GetC(). Vous pouvez en savoir plus sur l’utilisation des modules dans l’article “Effet papillon”.

Résumé

Le formulaire de connexion standard a été amélioré, un système de droits pour le menu principal a été créé. Pour un bonheur complet, il n’y a pas assez de panneau d’information pour afficher des données sur
l’utilisateur actuel et son rôle. Je considère qu’il est inapproprié de placer ces informations dans l’en-tête du formulaire principal, car cela surchargerait la perception. Je prévois de créer une barre d’état en bas et/ou un beau menu sur la barre latérale gauche. C’est là que les éléments d’affichage des données utilisateur doivent se trouver, de préférence avec une photo et d’autres attributs commerciaux.

A suivre

Traduction : Yann Yvnec

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *