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ètre
Langage 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