Transcript
Page 1: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

MMiiggrreerr ddee VVBB66 àà VVBB..NNEETT –– DDeeuuxxiièèmmee ppaarrttiiee JJ--MM RRaabbiilllloouudd

wwwwww..ddeevveellooppppeezz..ccoomm.. PPuubblliiccaattiioonn ssuurr uunn aauuttrree ssiittee WWeebb iinntteerrddiittee ssaannss ll''aauuttoorriissaattiioonn ddee ll''aauutteeuurr..

Remerciements J'adresse ici tous mes remerciements à l'équipe de rédaction de "developpez.com" et tout

particulièrement à Cécile Muno pour le temps qu’elle a bien voulu passer à la correction et à l'amélioration de cet article.

INTRODUCTION ...........................................................................................................3

CODE EXEMPLE ..........................................................................................................3

Choisir son modèle de programmation................................................................................................................... 3 Une approche erronée............................................................................................................................................. 3 L'approche objet typé ............................................................................................................................................. 5 Construire une deuxième classe ............................................................................................................................. 9 Construire un application de test .......................................................................................................................... 12

LES NOUVEAUTES DE LA PLATE FORME..............................................................13

LES METAS DONNEES..............................................................................................13

Les attributs ............................................................................................................................................................ 14 Généralités............................................................................................................................................................ 14 Attributs d’Assembly ........................................................................................................................................... 14 Attributs du Framework ....................................................................................................................................... 15

La réflexion.............................................................................................................................................................. 18 Récupération d’information.................................................................................................................................. 18 Liaison tardive...................................................................................................................................................... 21 Attributs Personnalisés ......................................................................................................................................... 22 Emission ............................................................................................................................................................... 22

VERSION DES COMPOSANTS..................................................................................23

1

Page 2: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Concept des versions............................................................................................................................................... 23 Le déploiement local ............................................................................................................................................ 23 Le déploiement global .......................................................................................................................................... 23

Version des assemblages......................................................................................................................................... 24 Le numéro de version ........................................................................................................................................... 24 La culture.............................................................................................................................................................. 24 La signature .......................................................................................................................................................... 24

Utilisation du Global Assembly Cache (GAC) ..................................................................................................... 25

Gestion des dépendances ........................................................................................................................................ 25

LA SECURITE.............................................................................................................29

Notions de base........................................................................................................................................................ 29 Les privilèges ou autorisations ............................................................................................................................. 29 Droit d’accès (ACL) ............................................................................................................................................. 29 Permissions........................................................................................................................................................... 29 Authentification.................................................................................................................................................... 29 Chiffrement .......................................................................................................................................................... 30 Données secrètes .................................................................................................................................................. 30 Code malicieux..................................................................................................................................................... 30

Sécurité des applications VB.NET......................................................................................................................... 31 Validation des données entrantes ......................................................................................................................... 31 Gestion de l’authentification ................................................................................................................................ 36 Gestion des exceptions ......................................................................................................................................... 36 Privilèges & autorisations..................................................................................................................................... 37

Sécurité des données en VB.NET .......................................................................................................................... 42 Hachage complexe ............................................................................................................................................... 42 Chiffrement des données ...................................................................................................................................... 45

Revue de code.......................................................................................................................................................... 47 Conclusion sur la sécurité..................................................................................................................................... 50

SERIALISATION .........................................................................................................50

Sécurité du processus de sérialisation ................................................................................................................... 52

CONCLUSION.............................................................................................................52

2

Page 3: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Introduction Ce cours va aborder plus spécifiquement les nouveautés de VB.NET dues à la plate-forme DotNet. Le

sujet étant particulièrement vaste, nous n'allons aborder que les points principaux à l'aide d'un exemple. Selon votre culture de développement, certaines parties peuvent aborder des sujets que vous maîtrisez car ce cours est écrit principalement à destination des développeurs VB6.

Dans la première partie de ce cours, nous allons procéder ensemble à une révision générale, en construisant le code que nous utiliserons par la suite.

La plate-forme DotNet constitue un environnement managé. Si vous avez déjà commencé à vous pencher sur VB.NET, vous avez pu constater un certain temps de chargement très supérieur à celui de vos programmes VB 6. Ceci est dû à la compilation Just-In-Time (JIT) induite par la plate-forme. Il n’est pas envisageable d’attendre les mêmes performances d’un code managé et d’un code natif, mais en échange, la plate-forme va vous fournir des fonctionnalités intéressantes pour peu qu’on sache comment s’en servir.

De fait, ce que nous allons voir est fondamentalement l'utilisation et les implications des métas données. Celles-ci existaient déjà dans VB 6 mais étaient entièrement cachées par l’environnement de développement.

Nous verrons ensemble aussi une approche de la sécurisation du code et des données, puisque hélas ce sujet est maintenant incontournable. Bien qu'assez long, ce cours n'est qu'une approche de base des nouvelles fonctionnalités que vous propose VB.NET. Je vous donnerai à la fin de ce cours des liens vers des articles vous permettant d'approfondir certains sujets.

Bonne lecture

Code exemple Tout au long de ce cours nous allons nous baser sur un exemple, en créant un composant faisant des

statistiques simple. Pour créer ma solution, je commence par créer un projet de type bibliothèque de classes, que je nomme

‘LibStat’.

Choisir son modèle de programmation Pour construire notre premier objet, nous allons voir deux approches de la problématique, et en quoi

l’approche objet va simplifier notre travail. Commençons par l'approche VB 6 de la création. Mon premier objet doit faire des statistiques sur une série unique. Je vais utiliser un objet ArrayList pour

stocker ma série de données. Cet objet est un tableau qui implémente une interface IList. Rappelons-nous qu'une interface peut être vue comme un contrat entre l'objet et le développeur. Lorsque

vous implémentez une interface dans un de vos objets, vous devez développer le code des membres de cette interface. Lorsque vous utilisez un objet qui implémente une interface, vous avez la garantie que les membres de celle-ci existent.

Une approche erronée Une approche classique de cet objet serait la suivante :

Public Class SerieVB6 Protected Friend Structure ResulStat Dim Moyenne As Single Dim Variance As Double Dim Somme As Double Dim SommeCarre As Double Dim Population As Int32 End Structure Private Result As ResulStat Private Serie As ArrayList

3

Page 4: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Public Sub New(ByVal MaSerie As ArrayList) If MaSerie.Count = 0 Then Throw New MonException("Vous ne pouvez pas passer un ArrayList vide comme paramètre") Exit Sub End If Serie = MaSerie Me.Calculate() End Sub Public Sub Add(ByVal Value As Single) Serie.Add(Value) Me.Calculate() End Sub Public Sub RemoveAt(ByVal Index As Int32) Serie.RemoveAt(Index) Me.Calculate() End Sub Public Property Item(ByVal Index As Int32) As Single Get Return CType(Serie.Item(Index), Single) End Get Set(ByVal Value As Single) Serie.Item(Index) = Value Me.Calculate() End Set End Property Private Sub Calculate() ………………………………………….. End Sub

Dans cet objet, nous exposons un constructeur qui attend un objet ArrayList comme paramètre, une méthode Add pour ajouter des éléments, une méthode RemoveAt pour supprimer un élément dont la position est donnée en paramètre, et une propriété item qui permet de récupérer un élément en fonction de sa position.

Bien que cette approche soit correcte, nous voyons bien ses défauts. En effet, nous réalisons une double implémentation des membres publics de l'objet ArrayList. Qui plus est, comme nous ne les avons pas tous écrits, nous perdons des fonctionnalités intéressantes de cet objet. Par exemple, je ne pourrai pas faire appel à une méthode ‘Clear’ pour vider la liste. Il n'existait pas de méthode avec VB 6 pour contourner ce problème si ce n'est de rendre la liste publique. Seulement faire cela, c’est aller contre les règles de l'encapsulation. VB.NET nous permet une approche plus logique grâce à l'héritage.

Imaginons le code suivant : Public Class SerieUnique Inherits ArrayList Protected Friend Structure ResulStat Dim Moyenne As Single Dim Variance As Double Dim Somme As Double Dim SommeCarre As Double

4

Page 5: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Dim Population As Int32 End Structure Private Result As ResulStat Public Sub New() MyBase.New() End Sub Public Sub New(ByVal MaSerie As Single()) MyBase.New(MaSerie.GetLength(0)) If MaSerie.Rank > 1 Then Throw New MonException("Le tableau passé en paramètre doit être unidimensionnel") Exit Sub End If Dim cmpt As Int32 For cmpt = MaSerie.GetLowerBound(0) To MaSerie.GetUpperBound(0) MyBase.Add(MaSerie.GetValue(cmpt)) Next End Sub Private Sub Calculate() ………………………………………….. End Sub

Comme vous le voyez, j'ai surchargé le constructeur. Pour mémoire, la surcharge d’une méthode consiste à écrire plusieurs méthodes ayant le même nom mais une signature différente de paramètre. Dans ce cas, je peux créer mon objet vide ou en lui passant un tableau de type single. En théorie, nul besoin de créer des méthodes pour ajouter ou supprimer les éléments puisque celles-ci sont déjà implémentées par l'objet ArrayList dont j'hérite. En réalité, il va falloir le faire car, en l'état, ma série n'est pas typée.

Je pourrais évidemment faire un contrôle de type dans la méthode de calcul mais cette approche est erronée. Je vais donc redéfinir la méthode Add et la propriété item.

Pour redéfinir une fonction on utilise le mot clé Overrides, par exemple : Public Overrides Function Add(ByVal value As Object) As Integer End Function

Vous voyez instantanément le problème qui se pose à nous. Je ne vais pas pouvoir typer correctement ma fonction car celle-ci attend un paramètre de type Object. Je pourrais construire une usine à gaz, en masquant la méthode Add à l’aide du mot-clé Shadows et réécrire la méthode en lui passant un paramètre de type single. Cette construction est une approche erronée du problème. En fait, quel est notre problème ?

L'approche objet typé Nous voulons créer une collection typée pour notre série de données. En héritant de l’objet ArrayList,

nous récupérerons des méthodes d'un objet par nature non typé. Il nous faudra alors un travail non négligeable pour typer correctement notre série. Lorsque des problèmes de cette nature surviennent, c’est que le choix de l'objet hérité est mauvais. Il s'agit d'une faute de conception. J'ai sciemment pris cet exemple car lorsqu'on commence à manipuler l'héritage c'est une erreur qu'on commet très souvent. On a toujours tendance à hériter d'objets trop complexes qui ne répondent pas parfaitement à la problématique.

Dans notre cas, l’étude statistique sur une série de données se résume à créer une collection fortement typée de Single auquel nous ajouterons une méthode de Calcul et une propriété pour renvoyer les résultats.

Pour créer une collection fortement typée, on hérite généralement de la classe de base " CollectionBase".

5

Page 6: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Interface, Classe de base et Classe abstraite Nous allons nous pencher sur ce point qui pose facilement des problèmes lorsqu’on arrive de VB 6.

Lorsqu’on implémente une interface, on doit écrire le code de ses membres. Dans VB6 les interfaces avaient parfois la dénomination « Classe abstraite » dans le sens qu’une interface ne possède pas de code.

Dans VB.NET, on entend par classe de base une classe qui doit être héritée ou si vous préférez dont on ne peut créer d’instance publique. C’est ce type de classe qu’on retrouve maintenant parfois sous la dénomination classe abstraite. Or ce n’est pas du tout la même chose. Etudions la classe de base CollectionBase telle qu’elle apparaît dans MSDN.

Si je retire tous les membres hérités d’Object, je vois que toute collection qui hérite de CollectionBase hérite des membres publics :

Count Obtient le nombre d'éléments contenus dans la collection Clear Supprime tous les objets de la collection GetEnumerator Retourne un énumérateur qui peut itérer sur la collection RemoveAt Supprime l'élément à l'index spécifié de l'instance de la collection Comme la classe CollectionBase est une classe de base, elle ne possède pas de constructeur public. Elle

possède un constructeur protégé (Protected) qui ne sera accessible que par héritage de la classe. La classe CollectionBase fournit aussi d’autres membres protégés, c’est-à-dire accessible par l’objet

héritant. Parmi ceux-ci, la propriété List permet de travailler sur la collection. A la fin, et c’est là que je voulais en venir, vous avez une liste intitulée "Implémentations d'interface

explicites". C'est la liste des méthodes d'interface implémentées par l'objet CollectionBase. Elles ne sont donc accessibles que par l'objet héritant. Comme les interfaces ne sont pas héritées, la collection que vous créez n'expose pas d'interface, sauf si vous le faites explicitement.

Présenté ainsi, cela a l'air complexe, mais vous verrez que c'est très simple.

Constructeur La première chose à faire est donc de créer un constructeur. Celui-ci est indispensable pour pouvoir

instancier l'objet. Rappelez-vous bien qu'il est toujours préférable de créer explicitement le constructeur. Un constructeur est une méthode dont le nom est New. Ce constructeur peut accepter ou non des paramètres. Lorsque vous êtes dans une classe héritante, le constructeur doit toujours appeler le constructeur de la classe de base en premier. Dans notre cas, nous aurons donc : Public Class SerieUnique Inherits CollectionBase Public Sub New() MyBase.New() End Sub

6

Page 7: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Méthodes surchargées La surcharge de méthodes consiste à créer des méthodes ayant le même nom mais pas la même

signature, c’est-à-dire nombre et/ou type de paramètre différent. Attention de ne pas confondre Surcharge et substitution, car la substitution remplace une méthode par une autre alors que la surcharge fait cohabiter les méthodes.

Dans notre exemple, je vais créer des constructeurs surchargés, c’est-à-dire plusieurs méthodes pour instancier mon objet. Mon code deviendra alors : Public Class SerieUnique Inherits CollectionBase Public Structure ResulStat Dim Moyenne As Single Dim Variance As Double Dim Somme As Double Dim SommeCarre As Double Dim Population As Int32 End Structure Private Result As ResulStat Public Sub New() MyBase.New() End Sub Public Sub New(ByVal Value As Single) MyBase.New() MyBase.List.Add(Value) End Sub Public Sub New(ByVal MaSerie As Single()) MyBase.New() If MaSerie.Rank > 1 Then Throw New System.ArgumentOutOfRangeException("MaSerie", "Le tableau MaSerie doit être unidimensionnel") Exit Sub End If Dim cmpt As Int32 For cmpt = MaSerie.GetLowerBound(0) To MaSerie.GetUpperBound(0) Me.Add(CType(MaSerie.GetValue(cmpt), Single)) Next End Sub

J'ai donc créé trois méthodes New acceptant respectivement aucun paramètre, un Single ou un tableau de Single. Comme vous le voyez, dans le cas d'un tableau de Single, je dois vérifier que le tableau est unidimensionnel et lever dans le cas contraire une Exception.

7

Page 8: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Exceptions En l'état, je lève une Exception de type ArgumentOutOfRange ce qui est tout-à-fait valide mais pas

forcément pertinent. Pourquoi ? Si mon application est autonome, c’est-à-dire si ce n'est pas un composant programmable ou si

l'application n'a pas pour but d'être pilotée, l'approche précédente est correcte. Par contre s'il s'agit comme dans notre exemple d'un composant, rien ne distinguera une erreur due à un bug du composant d'une erreur levée par le composant. Les règles de bonne pratique indiquent qu'une erreur levée par votre composant doit hériter de ApplicationException. Ceci permettra aux utilisateurs de votre composant de séparer les erreurs héritant de SystemException et celle héritant d'ApplicationException. Dans mon cas, je vais donc créer une classe d'exception de la forme : Public Class MonException Inherits ApplicationException Public Sub New() MyBase.New() End Sub Public Sub New(ByVal message As String) MyBase.New(message) End Sub End Class

Ma levée d'exception sera alors : Throw New MonException("Le tableau passé en paramètre doit être unidimensionnel")

Créer des méthodes et des propriétés Pour finir ma collection, je dois lui écrire une méthode pour ajouter des éléments, et une propriété

permettant d'accéder aux éléments. La méthode Add est généralement la suivante : Public Sub Add(ByVal Value As Single) MyBase.List.Add(Value) End Sub

A première vue, on ne voit pas l'intérêt. Pourtant maintenant votre collection est fortement typée puisqu'il n'est plus possible de passer autre chose qu'un Single.

Je vais maintenant créer une propriété Item qui permet d'accéder à un élément en fonction de son index. Default Public Property Item(ByVal Index As Int32) As Single Get Return CType(MyBase.List.Item(Index), Single) End Get Set(ByVal Value As Single) MyBase.List.Item(Index) = Value End Set End Property

Là encore, vous voyez que la propriété est correctement typée. Le mot clé Default me permet juste de définir le membre par défaut de la classe.

Il ne me reste plus qu'à créer une méthode de calcul et une propriété pour la récupération des résultats. Public Sub Calculate() Result = New ResulStat Try Result.Population = MyBase.Count Dim MonEnum As IEnumerator = MyBase.GetEnumerator While MonEnum.MoveNext Result.Somme = Result.Somme + CType(MonEnum.Current, Single)

8

Page 9: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Result.SommeCarre = Result.SommeCarre + System.Math.Pow(CType(MonEnum.Current, Single), 2) End While Result.Moyenne = CSng(Result.Somme / Result.Population) While MonEnum.MoveNext Result.Variance = Result.Variance + (CType(MonEnum.Current, Single) - Result.Moyenne) End While Result.Variance = Result.Variance / (Result.Population -1) Catch ex As DivideByZeroException Throw New MonException("Vous devez avoir au moins deux éléments dans votre série") Catch ex As Exception Throw New MonException("Une erreur est survenue lors du calcul") End Try End Sub Public ReadOnly Property Resultat() As ResulStat Get Return Result End Get End Property

J'ai fini pour l'instant ma collection fortement typée. Il reste que l'utilisation d'une structure de résultat n'est pas une bonne approche, mais nous étudierons tout cela un peu plus loin.

Construire une deuxième classe Je vais maintenant créer une classe de calcul de régression simple sur une série XY. Je vais pour l'instant

reprendre la même logique de travail à l'aide d'une structure. Dans cet objet je ne vais pas hériter car je vais pratiquer une approche plutôt simpliste. Mon deuxième objet sera de la forme Public Class RegressionSimple Private SerieX, SerieY As SerieUnique Public Structure ResultReg Dim SumXY As Double Dim CoeffDeter As Double Dim Ord_Ori As Double Dim Pente As Double End Structure Private Regression As ResultReg Public Sub New() SerieX = New SerieUnique SerieY = New SerieUnique End Sub Public Sub New(ByVal Tableau As Single(,)) SerieX = New SerieUnique SerieY = New SerieUnique Dim cmpt As Int32 If (Tableau.GetUpperBound(0) = 2 AndAlso Tableau.GetUpperBound(1) > 2) Then For cmpt = Tableau.GetLowerBound(1) To Tableau.GetUpperBound(1)

9

Page 10: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

SerieX.Add(Tableau(0, cmpt)) SerieY.Add(Tableau(1, cmpt)) Next ElseIf (Tableau.GetUpperBound(1) = 2 AndAlso Tableau.GetUpperBound(0) > 2) Then For cmpt = Tableau.GetLowerBound(0) To Tableau.GetUpperBound(0) SerieX.Add(Tableau(cmpt, 0)) SerieY.Add(Tableau(cmpt, 1)) Next Else Throw New MonException("Une des deux dimensions du tableau doit contenir au moins trois éléments") Exit Sub End If End Sub Public Sub Add(ByVal ValeurX As Single, ByVal ValeurY As Single) SerieX.Add(ValeurX) SerieY.Add(ValeurY) End Sub Public Sub Clear() SerieX.Clear() SerieY.Clear() End Sub Public Sub RemoveAt(ByVal Index As Int32) Me.SerieX.RemoveAt(Index) Me.SerieY.RemoveAt(Index) End Sub Public Property item(ByVal Index As Int32, ByVal NumSerie As Int32) As Single Get If NumSerie = 1 Then Return SerieX.Item(Index) Else Return SerieY.Item(Index) End Get Set(ByVal Value As Single) If NumSerie = 1 Then SerieX.Item(Index) = Value Else SerieY.Item(Index) = Value End Set End Property Public Sub Calculate() Dim cmpt As Integer, SommeXY As Double If SerieX.Count < 2 Then Throw New MonException("Il faut au moins deux valeurs dans la série pour effectuer le calcul") Exit Sub End If For cmpt = 0 To SerieX.Count - 1 SommeXY = SommeXY + (SerieX.Item(cmpt) * SerieY.Item(cmpt)) Next Dim ResSerieX, ResSerieY As Statistique.SerieUnique.ResulStat

10

Page 11: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

SerieX.Calculate() SerieY.Calculate() ResSerieX = SerieX.Resultat ResSerieY = SerieY.Resultat With Regression

eXY .SumXY = Somm .CoeffDeter = (ResSerieX.Population * SommeXY - ResSerieX.Somme * ResSerieY.Somme) / (Sqrt((ResSerieX.Population * ResSerieX.SommeCarre - Pow(ResSerieX.Somme, 2)) * (ResSerieY.Population * ResSerieY.SommeCarre - Pow(ResSerieY.Somme, 2)))) .Pente = (ResSerieX.Population * SommeXY - ResSerieX.Somme * ResSerieY.Somme) / (ResSerieX.Population * ResSerieX.SommeCarre - Pow(ResSerieX.Somme, 2)) .Ord_Ori = ResSerieY.Moyenne - .Pente * ResSerieX.Moyenne End With End Sub

Public ReadOnly Property Resultat() As ResultReg Get

Regression Return End Get End Property

End Class Afin de finir nos révisions, regardons ensemble la fonction New acceptant un tableau

Public Sub New(ByVal Tableau As Single(,)) SerieX = New SerieUnique SerieY = New SerieUnique Dim cmpt As Int32

rBound If (Tableau.GetUppe (0) = 2 AndAlso Tableau.GetUpperBound(1) > 2) Then For cmpt = Tableau.GetLowerBound(1) To Tableau.GetUpperBound(1) SerieX.Add(Tableau(0, cmpt)) SerieY.Add(Tableau(1, cmpt)) Next

bleau.GetUpperBound(1) = 2 ElseIf (Ta AndAlso Tableau.GetUpperBound(0) > 2) Then For cmpt = Tableau.GetLowerBound(0) To Tableau.GetUpperBound(0) SerieX.Add(Tableau(cmpt, 0)) SerieY.Add(Tableau(cmpt, 1)) Next Else

row Th New MonException("Une des deux dimensions du tableau doit contenir au moins trois éléments") Exit Sub End If End Sub

danCommece q

s ce cas j'attends un tableau à deux dimensions, je peux préciser Single(,) pour le paramètre

bloc If bien que je ne l'utilise pas à l'ext

perBound(0) = 2 AndAlso Tableau.GetUpperBound(1) > 2)

ui interdira le passage d'un tableau n'ayant pas deux dimensions. Notez aussi que je déclare ma variable cmpt à l'extérieur duérieur. Si j'écrivais :

If (Tableau.GetUpThen Dim cmpt As Int32 For cmpt = Tableau.GetLowerBound(1) To Tableau.GetUpperBound(1)

11

Page 12: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

SerieX.Add(Tableau(0, cmpt)) SerieY.Add(Tableau(1, cmpt)) Next

bleau.GetUpperBound(1) = 2 ElseIf (Ta AndAlso Tableau.GetUpperBound(0) > 2) Then For cmpt = Tableau.GetLowerBound(0) To Tableau.GetUpperBound(0) SerieX.Add(Tableau(cmpt, 0)) SerieY.Add(Tableau(cmpt, 1)) Next

e erreur dans le ElseIf puisque les variables de bloc existent maintenant (cf. premJ'obtiendrais unpart

ière

Construire un application de test

ie)

Pou projet fonctionnel à ma solution. Je vais dans le

menmer 'AppliDeTest'. Mon explorateur de

solu

r pouvoir tester mon composant, je dois ajouter unu ‘Fichier’, et je choisis ‘Ajouter un projet’ – ‘Nouveau Projet’. Je choisis un projet de type application Windows que je vais nomtion va être comme ci-dessous :

En l’état mon test ne va pas fonctionner sans faire deux petites opérations. Je dois aller dans les

prop

Pri nder As System.Object, ByVal e As

riétés du projet, aller définir WindowsApplication1 comme étant le projet de démarrage, et ajouter une référence à ‘LibStat’ dans mon projet Windows (Menu Projet – Ajouter une référence – Onglet Projet).

Mon code de test sera de la forme suivante : vate Sub Button1_Click(ByVal se

System.EventArgs) Handles Button1.Click Dim MonTab(,) As Single, cmpt As Integer ReDim MonTab(2, 10) Randomize()

To 9 For cmpt = 0 MonTab(0, cmpt) = cmpt MonTab(1, cmpt) = (5 * cmpt + CInt(Int((3 * Rnd()) - 1))) Next

sResult As LibStat.Statistique.RegressionSimple.ResultReg Dim Me Dim CalcStat As New LibStat.Statistique.RegressionSimple(MonTab) CalcStat.Calculate()

esultat MesResult = CalcStat.R Me.txtCoeff.Text = MesResult.CoeffDeter.ToString Me.txtOrd.Text = MesResult.Ord_Ori.ToString Me.txtPente.Text = MesResult.Pente.ToString End Sub

u composant que nous allons faire évoluer dans ce cours. C’est le code d

12

Page 13: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Les nouveautés de la plate forme Impossible de parler correctement de VB.NET sans d’abord définir correctement la notion d’assembly.

Pour cela nous allons faire une analogie avec le fonctionnement COM, bien que ce type de comparaison soit assez risquée puisqu’il s’agit d’analogie de comportement mais non de fonctionnement, ou autrement dit, cela semble fonctionner à l’identique mais tel n’est pas le cas.

Un assembly est la plus petite unité logique de déploiement. L’IDE ne produit que des assemblies mono fichier mais il est possible de créer un assembly multi fichier par le biais de l’utilitaire Assembly Linker (Al.exe). Comme dans COM, une application autonome sera générée sous une forme EXE, un jeu de composant sous une forme DLL.

Un assembly contient évidemment du code exécutable, mais aussi un certains nombres d’informations complémentaires dans ce que l’on appelle le manifeste de l’assembly. Ce manifeste contient des informations sur lesquelles repose le fonctionnement du FrameWork tel que nous le verrons lorsque nous parlerons de réflexion ou de sécurité. Ces informations se retrouvent englobées dans les métas données, concept que nous allons aborder maintenant.

Les métas données On entend par méta donnée, une donnée qui coexiste avec le code sans en faire directement partie. Cette

notion n’est pas stricto sensu une nouveauté puisque VB6 en utilisait déjà, comme par exemple la propriété ‘Instancing’ des composants activeX. On peut dire qu’une méta donnée sert à décrire tout ou partie d’un code sans intervenir dans l’exécution de celui-ci. On retrouve d’une certaine manière ce concept dans les composants programmables du monde COM qui savent se décrire à l’extérieur (IDL).

Le Framework définit trois types de métas données : • Description de l'assembly.

• Description des types.

• Attributs. On trouve très souvent dans les articles traitant de DotNet un amalgame entre attributs et méta donnée. Ce mélange vient du fait que dans le Framework, les métas données arrivent sous forme d’attribut, dans

le sens ‘qui hérite de la classe System.Attribute’ alors que la documentation induit plus une notion de personnalisation dans le concept d’attribut. Pour ma part, je parlerai d’attribut pour tout ce qui hérite de ‘System.Attribute’.

Pour bien comprendre cette notion de méta donnée, je vais donner un exemple que tout le monde connaît. Imaginons la fonction : Private Function DeuxFois(ByVal MaVar As Integer) As Integer Return MaVar * 2 End Function

13

Page 14: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Si je déclare cette fonction comme ‘Public’, cela ne change rien à la réalité de son exécution, qui renvoie une valeur égale au double du paramètre entré. On peut considérer les mots clés de portée comme des attributs de visibilité de la fonction ; c'est-à-dire permettant au compilateur de savoir si la fonction est visible de son point d’appel.

Les attributs Comme je vous l’ai dit plus haut, un attribut dans DotNet hérite de System.Attribute. Il s’agit donc d’un

objet, traité comme tel par le compilateur. Ceci implique que les attributs appartiennent à des espaces de nom que vous devez spécifier ou importer.

Il est possible de créer ses propres attributs, ce qui est d’ailleurs assez simple. Nous le verrons plus loin dans ce cours.

Généralités Les attributs se placent entre les caractères ‘<’ et ‘>’, en tête de la déclaration de la partie de code sur

laquelle ils s’appliquent. Il est possible d’appliquer plusieurs attributs à une même partie de code en utilisant le séparateur virgule ‘,’. Par convention, les attributs finissent par le terme Attribute, néanmoins celui-ci n’est pas obligatoire dans l’appel. Ce qui autrement dit signifie que vous pouvez appeler l’attribut ‘System.ObsoleteAttribute’ en tapant juste ’Obsolete’.

Il convient toutefois de respecter cette convention lorsque vous créez vos propres attributs, ceci afin de faciliter la lecture du code.

Les attributs gèrent des paramètres. Cela vient du fait que l’appel d’un attribut est en fait l’appel du constructeur de celui-ci. Les paramètres peuvent être nommés ou positionnels. Les paramètres nommés sont généralement les propriétés de l’objet attribut invoqué.

Les attributs sont indépendants du langage utilisé. Les attributs de l’assembly utilisent le mot clé Assembly. On entend par élément marqué, le membre de la classe, la classe ou l’assemblage qui supporte l’attribut.

Attributs d’Assembly Les attributs d’un assembly sont un peu particuliers puisqu’en soit un assembly n’est pas un élément de

code mais une unité de déploiement. Ils ont toutefois une utilité majeure dans la gestion des versions que nous verrons plus loin.

On les trouve dans le fichier AssemblyInfo.vb de votre projet LibStat, qui se présente sous la forme : Imports System Imports System.Reflection Imports System.Runtime.InteropServices ' Les informations générales relatives à un assembly dépendent de l'ensemble ' d'attributs suivant. Pour modifier les informations associées à un assembly, 'changez les valeurs de ces attributs.

' Vérifiez les valeurs des attributs de l'assembly <Assembly: AssemblyTitle("")> <Assembly: AssemblyDescription("")> <Assembly: AssemblyCompany("")> <Assembly: AssemblyProduct("")> <Assembly: AssemblyCopyright("")> <Assembly: AssemblyTrademark("")> <Assembly: CLSCompliant(True)>

14

Page 15: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

'Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM <Assembly: Guid("507D3CBD-DB8E-4BA8-BC5D-15A971490549")> ' Les informations de version pour un assembly se composent des quatre valeurs suivantes : ' ' Version principale ' Version secondaire ' Numéro de build ' Révision ' ' Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut ' en utilisant '*', comme indiqué ci-dessous : <Assembly: AssemblyVersion("1.0.*")>

Dans ce cas, les attributs sont définis mais non valorisés. Comme je vous l’ai dit dans les généralités, vous voyez qu’ils sont placés entre crochets, qu’ils utilisent

le mot clé Assembly et qu’ils gèrent, dans ce cas, un paramètre. Leur écriture stricte serait de la forme : <Assembly: System.Reflection.AssemblyTitleAttribute("")> Regardons dans l’aide AssemblyTitleAttribute par exemple. La déclaration du constructeur est : Public Sub New(ByVal title As String) C’est donc bien le constructeur de l’objet attribut que je passe comme attribut. C’est ce qui permet de

définir des paramètres aux attributs.

Attributs du Framework

Groupes d’attributs Le Framework définit plusieurs groupes d’attributs dont les attributs d’assembly sont un de ceux-ci. Le groupe d'attributs correspond en fait à un espace de nom, sur lequel porte ces attributs. Par exemple

groupe ComponentModel contient tous les attributs utilisés lors de la création de composants autonomes de code. On retrouve dans le Framework les groupes suivants :

• ComponentModel : ce groupe concerne tout ce qui sert au comportement des composants et des contrôles tant au moment de la création que de l'exécution. Nous ne nous étendrons pas ici sur cet espace de nom qui fera l'objet de notre troisième cour.

• Diagnostics : ce groupe concerne tout ce qui a trait au débogage, au traitement des journaux d'événements, au compteur de performance etc.

• EntrepriseServices : les services d'entreprise concernent la mise à disposition de services COM+ pour les classes du Framework. Nous ne verrons pas cette partie en détail dans ce cours mais nous traiterons de la notion de service Windows.

• Management.Instrumentation : Traite de l'exposition des informations et des événements des classes vers des clients via WMI

• Reflection : la réflexion est le principe qui permet de donner une vue managée des types, champs ou des méthodes chargées, ainsi que la création dynamique de type. Nous verrons ce point largement dans le chapitre suivant.

• Runtime.CompilerServices : Hors sujet • Runtime.InteropServices : on entend par InteropServices tout ce qui touche à l'interopérabilité

entre le monde COM et le monde managé. Si le sujet vous intéresse, veuillez consulter le cours http://dotnet.developpez.com/cours/interopCOM/

• Remoting : Cet espace de nom contient tout ce qui touche à l’accès distant pour la création d’application distribuée.

• Security : Contient la structure du système de sécurisation apporté par le FrameWork.

15

Page 16: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

• Xml.Serialization : La sérialisation consiste à transformer un ou des objets en flux et inversement.

Nous verrons beaucoup de ces concepts dans la suite de ce cours, mais sachez dès à présent qu’ils utilisent des attributs.

Attributs système Nous allons voir ici quelques attributs existant, que l’on utilise le plus fréquemment.

CLSCompliantAttribute(ByVal IsCompliant As Boolean) Définit si l’élément marqué est conforme à la CLS (Common Language Specification). Bien que Visual

Basic ne déclenche pas d’avertissement si un élément n’est pas conforme, il est utile de le marquer. Le paramètre IsCompliant est vrai si l’élément est conforme.

La CLS est définie dans l’aide dans le chapitre « Qu'est-ce que la spécification CLS (Common Language Specification) ? »

ContextStaticAttribute() Réduit la portée statique de l’élément marqué à son contexte. La notion de contexte est propre au

fonctionnement de DotNet, mais pour schématiser largement, disons qu’il est possible de créer plusieurs objets identiques ayant un contexte différent dans une même application en utilisant des objets ContextBoundObject.

FlagsAttribute() Permet de définir si une énumération peut être traitée comme un champ de bits. Cette notion existait déjà

dans VB6. Généralement, les éléments d’une énumération sont mutuellement exclusifs. Dans certain cas, ils peuvent être combinés dans un masque de bits que l’on obtient par combinaison à l’aide de OR.

Toutefois attention. C’est à vous de définir des valeurs permettant de traiter correctement un masque de bits. Prenons l’exemple suivant :

Module Module1 <Flags()> Enum MonEnum As Integer PosUn = 1 PosDeux = 2 PosTrois = 3 PosQuatre = 4 End Enum Sub Main() Dim MaVar As MonEnum = MonEnum.PosUn Or MonEnum.PosDeux Console.WriteLine(MaVar.ToString) End Sub End Module Dans ce cas la console renvoie PosTrois, ce qui est faux puisque le fait de mettre les bits 1 et 2 à vrai

n’induit pas que le bit 3 l’est. Vous devez déclarer l’énumération telle que : <Flags()> Enum MonEnum As Integer PosUn = 1 PosDeux = 2 PosTrois = 4 PosQuatre = 8 End Enum Pour que la console renvoie bien PosUn, PosDeux.

Si vous n’avez pas l’habitude de manipuler des masques de bits, le FrameWork met à votre disposition une structure BitVector32 plus facile à appréhender.

16

Page 17: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

NonSerializedAttribute() Indique que l’élément marqué ne doit pas être sérialisé lorsque la classe qui le contient l’est. Cet attribut

permet de ne pas transférer certains membres lors du transfert de l’objet. Nous le reverrons lors du chapitre sur la sérialisation.

ObsoleteAttribute (Surchargé) L’élément marqué se signale à l’utilisateur comme ne devant plus être utilisé. L’attribut peut avoir deux

paramètres : Un message de type String, qui contient le message d’avertissement Un booléen indiquant que l’appel d’une méthode marquée obsolète doit être traité comme une erreur

Bien que les problèmes de version soient différents entre le monde COM et DotNet, le marquage obsolète peut vous permettre d’indiquer à l’utilisateur de votre composant quelle méthode de remplacement utiliser. Méfiez-vous tout de même du déclenchement d’erreur.

Par exemple, je peux déjà marquer ma classe SerieVB6 comme étant obsolète, ce qui me donnera : <Obsolete("N'utilisez pas cette classe", True)> Public Class SerieVB6

ParamArrayAttribute()

Essayez d’abord de traiter vos problèmes par la surcharge avant de vous jeter sur celui-là (je vous connais).

Permet d’obtenir l’équivalence du ParamArray de VB6, c’est-à-dire de passer un tableau de paramètres de taille indéfinie. Tout comme lui, cela ne peut s’appliquer qu’au dernier paramètre de la méthode, et le tableau doit être à une dimension.

Attention, ParamArray est un mot clé de Visual Basic. La notation suivante provoquera une erreur Public Function MaFonction(ByVal Msg As String, <ParamArray()> ByVal TabParam As Integer) As String

Vous devez impérativement utiliser le nom complet (avec le suffixe Attribute) Public Function MaFonction(ByVal Msg As String, <ParamArrayAttribute()> ByVal TabParam As Integer) As String

SerializableAttribute() Spécifie que l’élément marqué peut être sérialisé.

Attributs Visual Basic L’espace de nom Visual Basic contient trois attributs. Essayez de ne pas trop les utiliser, sauf si vous ne

pouvez faire autrement. ComClassAttribute (Surchargé)

Permet de simplifier la création de classes managées accessibles depuis le monde COM. Pour créer une telle classe il vous suffit de générer trois GUID qui représente ClassID, InterfaceID et EventsID puis d’utiliser l’attribut comme dans : <ComClass(RegressionLineaire.ClassId, RegressionLineaire.InterfaceId, RegressionLineaire.EventsId)> Public Class RegressionLineaire Public Const ClassId As String = "D9CF76CD-72C3-417d-9242-4BE2FD0BC922" Public Const InterfaceId As String = "0070A9DB-4B24-428d-9092-4FAB49A78BBC" Public Const EventsId As String = "EA2F5AB8-F039-4e08-B1C4-4E255976E4B5"

Il ne faut pas oublier toutefois de cocher dans les options du projet « Inscrire pour COM Interop » VBFixedArrayAttribute(Byval UpperBound1 As Integer)

Permet de signaler un tableau comme étant un tableau de taille fixe, ce qui peut être nécessaire pour l’utilisation de certaines API. Attention, cela ne rend pas le tableau fixe pour autant, et il faut quand même allouer l’espace avec un Redim.

17

Page 18: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

VBFixedStringAttribute(Byval Length As Integer) Identique à l’attribut précédent, mais pour les chaînes.

La réflexion La réflexion est le mécanisme qui permet de travailler avec les métas données à l’exécution. La réflexion a de nombreuses utilisations. Pour notre part, nous allons en aborder que quelques aspects.

Récupération d’information La récupération d'information repose donc sur la lecture des attributs à l'exécution. Cette méthodologie

va nous emmener assez loin puisque nous en profiterons pour étudier aussi les attributs personnalisés. Si vous avez beaucoup manipulé les contrôles ActiveX et autres bibliothèques dans VB6, ce que nous

allons voir ici est assez familier. En effet, vous avez déjà vu fréquemment des composants programmables, c’est-à-dire des composants qui savent décrire leur interface. Dans le Framework, toutes les classes conformes CLS peuvent être décrites par réflexion. Prenons l'exemple de notre objet, et disons que pour l'instant je ne connais rien d'autre de lui que son emplacement physique sur le disque. Pour pouvoir faire cela, je dois impérativement charger la classe, et non instancier l'objet comme je l'ai vu écrit une fois. Il existe deux possibilités. Le chargement 'physique' qui correspond au chargement du fichier dll qui contient la classe à l'aide de sa position, ou le chargement logique qui utilise les noms forts que nous verrons après.

Nous allons donc travailler avec l'espace de nom System.Reflection. Il est possible d'aller très loin dans la récupération, nous nous limiterons à des fondamentaux.

Imaginons le code de réflexion suivant : Private Sub cmdReflexion_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdReflexion.Click Dim MonAssemblage As System.Reflection.Assembly = System.Reflection.Assembly.LoadFrom("D:\User\Tutos\migration\migration2\LibStat\LibStat\bin\LibStat.dll") Dim FicText As New System.IO.StreamWriter("D:\User\Tutos\migration\migration2\lectClass.txt", False) Dim eModule As System.Reflection.Module Dim eType As System.Type Dim eMembre As System.Reflection.MemberInfo For Each eModule In MonAssemblage.GetModules FicText.WriteLine("Module " & eModule.Name) For Each eType In eModule.GetTypes Select Case True Case eType.IsClass FicText.WriteLine("Classe : " & eType.Name) Case eType.IsArray FicText.WriteLine("Tableau : " & eType.Name) Case eType.IsEnum FicText.WriteLine("Enumeration : " & eType.Name) End Select For Each eMembre In eType.GetMembers Dim Params As System.Reflection.ParameterInfo(), Param As System.Reflection.ParameterInfo Dim MaChaine As String = eMembre.MemberType.ToString & " : " & eMembre.Name If eMembre.MemberType = Reflection.MemberTypes.Method Then Params = eType.GetMethod(eMembre.Name).GetParameters() If Params.GetLength(0) > 0 Then MaChaine = MaChaine & "("

18

Page 19: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

For Each Param In Params MaChaine = MaChaine & Param.Name & " As " & Param.ParameterType.ToString & ", " Next MaChaine = MaChaine.Substring(0, MaChaine.Length - 2) MaChaine = MaChaine & ")" End If ElseIf eMembre.MemberType = Reflection.MemberTypes.Constructor Then Dim constructs As System.Reflection.ConstructorInfo(), Construct As System.Reflection.ConstructorInfo constructs = eType.GetConstructors If constructs.GetLength(0) > 0 Then MaChaine = MaChaine & "(" For Each Construct In constructs MaChaine = MaChaine & Param.Name & " As " & Param.ParameterType.ToString & ", " Next MaChaine = MaChaine.Substring(0, MaChaine.Length - 2) MaChaine = MaChaine & ")" End If ElseIf eMembre.MemberType = Reflection.MemberTypes.Property Then Dim Props As System.Reflection.ParameterInfo(), Prop As System.Reflection.ParameterInfo Props = eType.GetProperty(eMembre.Name).GetIndexParameters() If Props.GetLength(0) > 0 Then MaChaine = MaChaine & "(" For Each Prop In Props MaChaine = MaChaine & Param.Name & " As " & Param.ParameterType.ToString & ", " Next MaChaine = MaChaine.Substring(0, MaChaine.Length - 2) MaChaine = MaChaine & ")" End If End If FicText.WriteLine(MaChaine) Next Next Next FicText.Flush() FicText.Close() MsgBox("done") End Sub

Le fichier résultat obtenu donnera (pour la première classe) : Module libstat.dll Classe : SerieUnique Method : GetEnumerator Method : get_Count Method : RemoveAt(index As System.Int32)

19

Page 20: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Method : Clear Method : GetHashCode Method : Equals(obj As System.Object) Method : ToString Method : Add(Value As System.Single) Method : get_Item(Index As System.Int32) Method : set_Item(Index As System.Int32, Value As System.Single) Method : Calculate Method : get_Resultat Method : GetType Constructor : .ctor(Value As System.Single, Value As System.Single, Value As System.Single) Constructor : .ctor(Value As System.Single, Value As System.Single, Value As System.Single) Constructor : .ctor(Value As System.Single, Value As System.Single, Value As System.Single) Property : Item(Value As System.Single) Property : Resultat Property : Count NestedType : ResulStat Field : Moyenne Field : Variance Field : Somme Field : SommeCarre Field : Population Method : GetHashCode Method : Equals(obj As System.Object) Method : ToString Method : GetType Comme je vous l'ai dit, je suis loin d'être allé aussi loin que la réflexion me le permet. Regardons un peu l'ossature du code utilisé :

Dim MonAssemblage As System.Reflection.Assembly = System.Reflection.Assembly.LoadFrom("D:\….\LibStat.dll") Dim eModule As System.Reflection.Module Dim eType As System.Type Dim eMembre As System.Reflection.MemberInfo For Each eModule In MonAssemblage.GetModules For Each eType In eModule.GetTypes Select Case True Case eType.IsClass FicText.WriteLine("Classe : " & eType.Name) Case eType.IsArray FicText.WriteLine("Tableau : " & eType.Name) Case eType.IsEnum FicText.WriteLine("Enumeration : " & eType.Name) End Select For Each eMembre In eType.GetMembers Dim Params As System.Reflection.ParameterInfo(), Param As System.Reflection.ParameterInfo If eMembre.MemberType = Reflection.MemberTypes.Method Then Params = eType.GetMethod(eMembre.Name).GetParameters() For Each Param In Params ….. Next End If Next

20

Page 21: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Next Next

Pour étudier mon assemblage, je suis obligé de suivre sa structure. Un assemblage peut être subdivisé en module bien que généralement il n'y en ait qu'un ce qui est notre cas. On peut assimiler le module à chaque fichier ".vb" présent dans votre assemblage à l'exception d'AssemblyInfo.

Ce module est donc divisé en type, dont les classes font parties. Comprendre cet aspect des choses est important. Comme une classe est un type, on peut travailler de façon fortement typée avec tout type d'objet. Vous retrouverez très souvent cette approche typée au cours de vos développements car elle permet des solutions fonctionnelles très performantes.

Les classes sont subdivisées en membres (champs, méthodes, propriété, constructeur….). Il n'est pas possible d'accéder aux paramètres par le biais direct du membre, j'utilise donc la méthode

GetParameters() exposée par le type en passant le nom du membre en paramètre. Tout cela n'est pas forcément bien nouveau. Bien que cela ne repose pas sur le même principe, les

composants scriptables du monde COM savaient eux aussi se décrire grâce à l'utilisation de l'IDL. Cette visibilité d'un objet par réflexion n'est toutefois pas très utile directement comme dans cet

exemple. Nous allons maintenant voir des cas classiques d'utilisation de la réflexion.

Liaison tardive Les manipulations par liaisons tardives vous sont peut-être familières. Si tel n'est pas le cas, résumons de

quoi il s'agit. En VB6, on travaille généralement sur des composants dont on a ajouté la référence. Le compilateur

peut alors vérifier la validité du code, on dit qu'on est en liaison précoce. Dans ce cas, on peut utiliser indifféremment 'New' ou 'CreateObject' pour instancier l'objet. Il est aussi possible d'instancier un objet sans utiliser son type, c’est-à-dire en le déclarant As object. On utilisait beaucoup cette technique pour contourner les problèmes de version, notamment pour le pilotage office.

Avec VB.NET c'est la même chose, mais on peut faire plus sophistiqué. Au lieu de CreateObject obligatoire en VB6, nous pouvons utiliser plusieurs techniques dont nous allons

voir deux exemples un peu différents. Je peux décider de pratiquer la liaison tardive identique à celle de VB6. Celle-ci sous-entend

évidemment que je connais le fonctionnement de l'objet. Private Sub cmdLateBinding_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdLateBinding.Click Dim MonAssemblage As System.Reflection.Assembly = System.Reflection.Assembly.LoadFrom("D:\User\Tutos\migration\migration2\LibStat\LibStat\bin\LibStat.dll") Dim MonObj As Object = MonAssemblage.CreateInstance("LibStat.Statistique.SerieUnique") Dim cmpt As Int32 For cmpt = 1 To 100 MonObj.Add(cmpt) Next MonObj.calculate() MsgBox(MonObj.Resultat.Moyenne.ToString) End Sub

En VB.NET, on utilise plus souvent un objet Activator pour instancier à la volée. De même, il est plus intéressant de récupérer l'instance dans un objet interface si on connaît les interfaces implémentées. Par exemple : Private Sub cmdLateBinding_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdLateBinding.Click Dim MonObjHdl As System.Runtime.Remoting.ObjectHandle MonObjHdl = Activator.CreateInstanceFrom("D:\User\Tutos\migration\migration2\LibStat\LibStat\bin\LibStat.dll", "LibStat.Statistique.SerieUnique") Dim MaIList As IList = CType(MonObjHdl.Unwrap, IList)

21

Page 22: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Dim cmpt As Int32 For cmpt = 1 To 100 MaIList.Add(cmpt) Next MsgBox(MaIList.Count.ToString) End Sub

Attributs Personnalisés Un des intérêts les plus évident de la réflexion reste la possibilité de travailler avec des attributs

personnalisés. On peut créer toutes sortes d'attributs, adaptés à ce que l'on souhaite faire, en sachant qu'on pourra les lire à l'exécution par réflexion. Ne perdez pas de vue toutefois qu'il s'agit d'information de compilation. La valeur d'un attribut ne change généralement pas dans une session. Je n'ai pas dans mon exemple l'utilité d'un attribut personnalisé. Cependant, sachez qu'un tel attribut se construit tel que : <AttributeUsage(AttributeTargets.Method)> Class MonAttribut Inherits System.Attribute Private Dispo As Boolean Public Sub New(ByVal arg As String) Dispo = arg End Sub Public ReadOnly Property Valeur() As string Get Return Dispo End Get End Property End Class

Emission C'est en fait la génération dynamique de code qui est caché sous ce terme. Si vous avez bien compris la

hiérarchie qui part de l'assemblage, la génération de code ne pose aucun problème particulier, si ce n'est qu'il faut comprendre le fonctionnement du code intermédiaire pour pouvoir utiliser les OpCodes correctement.

Je ne vais pas entrer dans le détail ici de la génération dynamique d'assemblage, car cela nous entraîneraient un peu loin pour un cours de migration.

22

Page 23: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Version des composants

Concept des versions Le monde DotNet ne gère plus ces composants comme le faisait COM. Bien que très similaire sur de

nombreux points, les implications sont tout-à-fait différentes. Comme nous l'avons vu dans la première partie, nous retrouvons bien la notion de référence, c’est-à-dire

d'appel à des composants externes. Toutefois, les composants managés sont gérés de manière différente. La base de registre n'est plus utilisée sauf si le composant managé doit être accessible par des objets COM.

Les composants managés se déploient de deux façons :

Le déploiement local Si le composant est strictement conçu pour une seule application ou si vous ne souhaitez pas partager le

composant, le déploiement local est la règle de bonne pratique. Cela consiste généralement à mettre votre composant dans le répertoire de l'application ou dans un de ses sous répertoire. En effet, votre programme va chercher par défaut ses composants dans le cache Global, puis dans son répertoire. Il est possible d'utiliser d'autre répertoire mais il faut alors utiliser un fichier XML de configuration. Ce type de déploiement présente donc un seul inconvénient, le partage de composants.

Le déploiement global Pour pouvoir partager des composants entre plusieurs applications, on doit procéder à un déploiement

dit global. Le principe consiste à utiliser un répertoire particulier pour mettre les composants. Celui ci s'appelle le Global Assembly Cache(GAC). Le fait de mettre le composant dans le GAC n'est pas suffisant en soi. En effet, si on utilisait le simple nom de la classe, on obtiendrait l'équivalent du registre Windows ou il ne peut exister qu'un composant du même nom. Or dans le déploiement DotNet, on souhaite qu'un programme travaille avec la version du composant pour laquelle il a été prévu. Cette technique s'appelle exécution côte à côte. Evidemment, il ne peut exister de toute façon qu'un seul composant ayant le même nom. Toute l'astuce consiste à enrichir la notion de nom afin de pouvoir déployer côte à côte des composants similaires. Dans l'univers DotNet, cela s'appelle le nom fort.

On utilise pour les versions des assemblages, une gestion de son identité par la notion de nom fort. Celui-ci se composant de quatre ans élément :

le nom de fichier sans extension le numéro de version de l'assemblage la culture de l'assemblage sa signature

Vous n'êtes pas sans avoir remarqué, que ces informations sont des attributs d'assemblage que nous avons vu précédemment.

23

Page 24: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Version des assemblages

Le numéro de version Le numéro de version se construit de façon similaire à ce que vous utilisiez avec VB 6, c'est-à-dire : Major.Minor.Build.Révision Les conventions de cette numérotation sont définies telles que : Major → un changement du numéro majeur, signifie une perte probable de la compatibilité

ascendante, dans tous les cas une réécriture importante. Minor → un changement de numéro mineur, garantit le maintien de la compatibilité ascendante. Le

composant a cependant été modifié. Build → chaque recompilation doit normalement incrémenter le numéro de build. Par convention,

une modification de numéro majeur ou mineur entraîne une remise à zéro de ce numéro. Revision → le numéro de révision est modifié lorsqu'on opère des modifications mineures sur

l'assemblage. On modifie aussi ce numéro, lorsqu'on modifie les ressources utilisées par l'assemblage, sans recompiler celui-ci.

Il n'est pas possible de créer un assemblage fortement nommé sans utiliser le numéro de version. Vous pouvez décider de gérer vous-même ce numéro, en remplissant manuellement l’attribut AssemblyVersion, ou vous pouvez laisser l'environnement de développement générer le numéro de build et de révision en écrivant une chaîne de type :

Major.Minor.* Comme vous l’aurez donc sûrement compris, c’est à vous de gérer les deux numéros majeur et mineur.

Faites très attention, si vous gérez manuellement les numéros de build et de révision, vous risquez d'écraser une version précédente si vous oubliez d’incrémenter ceux-ci.

La culture Nous reviendrons dans la quatrième partie sur la notion de culture, que l'on retrouve aussi souvent sous

le terme localisation. Sachez cependant dès à présent que si vous utilisez le concept de localisation pour vos assemblages, celui-ci fera partie de son identité. Ceci implique aussi, que vous pouvez déployer plusieurs versions d'un composant, pour peu que sa version de culture diffère. L'attribut qui définit la culture est l'attribut assemblyCultureInfo

La signature La signature de l'assemblage est constituée d'une paire de clé publique/privé selon l'algorithme de

cryptage RSA. Pour générer cette clé, on utilise l'utilitaire sn.exe que vous trouverez généralement dans C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Bin.

Vous trouverez dans ce répertoire de nombreux outils. La plupart d'entre eux sont des outils en ligne de commande. Afin de simplifier le travail, il peut être intéressant de rajouter ce répertoire dans votre variable Path. Pour faire cela, allez dans votre panneau de configuration, puis double cliquez sur l'icône système. Vous trouverez dans l'onglet « avancé », un bouton « variable d'environnement » qui vous donne accès à cette variable Path.

Pour générer la paire de clé publique/privé, il vous suffit alors d'appeler « Exécuter » dans le menu « démarrer », pour écrire par exemple la ligne :

sn -k d:\tutoriel\migration\migration2\LibStat\LibStat.snk Pour pouvoir associer les clés à l'assemblage, vous devez alors renseigner l'attribut AssemblyKeyFile tel

que : <Assembly: AssemblyKeyFile("D:\chemin\LibStat\LibStat.snk")>

24

Page 25: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Utilisation du Global Assembly Cache (GAC) Dans le cas où on procède à un déploiement global d’un composant, on utilise le Global Assembly

Cache(GAC). On peut imaginer celui-ci comme une sorte de registre managé. Pour gérer ce GAC, vous trouverez un utilitaire (en fait deux) "Gacutil.exe" (ShFusion.dll). Vous

pouvez aussi accéder à l'ensemble des outils de déploiement en allant dans : panneau de configuration – Outils d'administration - Configuration Microsoft .NET Framework 1.1.

Vous obtenez alors un écran comme :

La liste de gauche montre les opérations de configuration que vous pouvez réaliser, celle de droite est

une vue du GAC. De cet écran, vous pouvez facilement ajouter ou supprimer des assemblages. Evidemment, vous ne pouvez déployer dans le GAC que les assemblages ayant un nom fort.

Gestion des dépendances C’est en fait cette notion de dépendances que l'on retrouve sous l'anglicisme de versionning, dans la

littérature. En effet, la possibilité de l'exécution côte à côte induit que le programme appelant des composants puisse identifier ceux-ci grâce à leur nom fort. Lorsqu'on ajoute un assemblage ayant un même nom mais une signature globale différente, le programme appelant continue d'utiliser le composant initialement prévu. Cette nouvelle stratégie de dépendances, supprime la diabolique gestion des DLL du monde COM.

Il est pourtant facilement envisageable de vouloir qu'une application profite des nouvelles fonctionnalités d'un composant. Il faut aller alors modifier des dépendances du programme tel que nous allons le voir maintenant.

Je génère mon composant LibStat avec les attributs d’assemblage suivant : <Assembly: AssemblyTitle("LibStat.dll")> <Assembly: AssemblyDescription("Calcul statistique")> <Assembly: AssemblyCompany("developpez.com")> <Assembly: AssemblyProduct("")> <Assembly: AssemblyCopyright("")> <Assembly: AssemblyTrademark("")> <Assembly: CLSCompliant(True)> <Assembly: AssemblyKeyFile("D:\tutoriel\migration2\LibStat\LibStat.snk")> <Assembly: Guid("7E780276-DE9D-4894-A8E4-D172EE9802D7")> <Assembly: AssemblyVersion("1.0.0.0")>

Je le génère une seconde fois avec un numéro de version valant 2.0.0.0 Je possède donc deux DLL identiques aux numéros de version près. Puis je génère mon projet AppliDeTest faisant référence au composant 1.0.0.0 après avoir ajouté la ligne

25

Page 26: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

MsgBox(System.Reflection.Assembly.GetAssembly(CalcStat.GetType).FullName.ToString)

Qui me permet d’afficher la version du composant appelé par mon application. A l’exécution pas de problème. Maintenant je remplace ma version 1.0.0.0 par ma version 2.0.0.0. J’obtiens à l’exécution

Comme mon composant Libstat existe bien, c’est bien que le programme gère les dépendances en

fonction du nom fort. J’enregistre alors mes deux composants dans le GAC, en passant par l’assistant configuration

A partir de là, plus de problèmes d’exécution. Cependant je pourrais vouloir utiliser mon composant

2.0.0.0 dans mon application. Pour cela, je n’ai pas besoin de recompiler, car dans mon assistant configuration, je peux aller modifier les applications managées dans l’entrée « Application ».

Pour cela je sélectionne l’application, je choisis « Assembly configurés », puis je sélectionne le composant dont je veux modifier la stratégie de liaison.

J’aurais donc les écrans suivants :

26

Page 27: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Et à l’exécution, tout se passe correctement

27

Page 28: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Comme vous le voyez, la gestion des versions n’est pas trop complexe. Cependant cela a necessité

plusieurs interventions sur le poste de travail. Ceci est une transition parfaite pour un des gros morceaux du sujet, la sécurité.

28

Page 29: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

La sécurité Impossible de ne plus aborder ce point. Il faut tout d’abord tordre le cou à une légende. Le code managé

n’est pas du code sécurisé par nature. Si le Framework vous donne accès plus facilement aux fonctions de sécurité encore faut-il les employer. Le terme de sécurité regroupe en fait deux aspects assez différents, la sécurité des données et la sécurité de l’application

Notions de base Dans cette partie, nous allons aborder la sécurité de façon basique. Il n’est pas question pour nous de

traiter un sujet aussi vaste de façon détaillée, car cela justifierait un cours entier. Néanmoins comme le sujet est sûrement nouveau pour de nombreux développeurs VB6, nous allons couvrir les fondamentaux.

Comme nous allons le voir, la sécurité présente plusieurs aspects. Bien que ceux-ci soit très différents, ils forment un tout et il n’est guère possible de considérer la sécurité sans envisager le problème de base.

On peut considérer que la sécurité doit tendre vers plusieurs objectifs o Confidentialité o Sûreté d’exécution o Validité des résultats

Dans cette partie, nous nous cantonnerons à l'étude de la sécurité des applications Windows.

Les privilèges ou autorisations C’est une notion assez récente sur les systèmes Windows puisqu’elle arrive avec NT4. La notion de

privilège aborde la sécurité sous l’angle de l’autorisation d’accès aux ressources. Si vous avez travaillé avec des bases de données, vous connaissez une approche similaire qui est la sécurité selon le rôle. Dans le concept, un administrateur a accès à toutes les ressources, un utilisateur à quelques unes et les visiteurs à très peu. On entend par ressource : l’accès aux fichiers, au registre, aux imprimantes, le droit de debugger un process, etc…

C’est le premier et le plus coriace des aspects de la sécurité à gérer. En effet, par habitude on se met généralement dans une session administrateur sur son poste ce qui fait qu’on travaille avec tous les privilèges ainsi que tout code s’exécutant dans la session. En faisant cela, on supprime presque entièrement toute possibilité de faire de la sécurité sérieusement.

Droit d’accès (ACL) En fait, toutes les ressources utilisent le principe des ACL (Access Control List) dont on peut résumer

ainsi le principe. Chaque groupe de sécurités c'est-à-dire d’utilisateurs possède un certain nombre d'autorisations sur la ressource. L’ACL de la ressource regroupe l'ensemble de ses autorisations. Lorsqu'un utilisateur demande l'accès à celle-ci, il y a vérification de l'autorisation en fonction de l'identité du groupe et des permissions définies par la liste d'accès. Cette notion d’ACL, est indispensable pour une bonne maîtrise de la sécurité comme nous le verrons plus loin.

Permissions le Framework utilise la notion de permissions basées sur des preuves pour définir si un assemblage peut

ou non utiliser une ressource. Toutefois, vous pouvez demander explicitement l'accès à des ressources et dans la négative gérer le comportement de votre composant si ces accès lui sont refusés. Aussi perfectionné que soit ce système, il ne peut être suffisant, sans une véritable réflexion du développeur sur la sécurité de son code. En effet, dans la plupart des cas, les composants disposent de plus de permissions que ce qui leur est véritablement nécessaire. Il convient alors de réduire l’obtention d’autorisations.

29

Page 30: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Authentification Si tout le monde connaît le principe de base de l’authentification, on ne réalise pas toujours sa portée.

Dans un concept d'applications sécurisées, c'est-à-dire avec des ressources protégées, des données protégées, et une gestion correcte des autorisations, tout démarre par l'authentification correcte de l'utilisateur exécutant.

Dans l'univers des bases de données, comme dans le monde Windows, la stratégie de sécurité repose sur l'appartenance de l'utilisateur à un groupe de sécurité, ce groupe possédant des droits et des limitations.

Dans la version Windows NT et supérieures, il est possible de récupérer l'authentification Windows. Dans les autres cas, vous devrez gérer vous-même celle-ci, sachant que c'est extrêmement complexe puisque les ressources ne sont pas sécurisées.

L'authentification repose sur l'association d'un nom d'utilisateur et d'un mot de passe pour accéder à un groupe de sécurité.

Chiffrement Le chiffrement des données ou des informations, parfois rencontré sous le terme cryptage, consiste à

protéger les données en les rendant inutilisables par quiconque ne pouvant les déchiffrer. Il existe globalement trois types de chiffrement utilisés dans les applications informatiques :

Le hachage Le hachage est un chiffrement particulier puisqu'il ne peut être déchiffré. Il est principalement utilisé

dans la gestion des données secrètes, puisque même la lecture d'une valeur hachée par quelqu'un ne devant pas y avoir accès, ne permettra pas de retrouver la valeur d'origine. Bien utilisé, le hachage est une technique très intéressante quant au contrôle de la validité des données.

Le chiffrement à clés symétriques Les algorithmes de chiffrement à clés symétriques utilisent une même clé pour chiffrer et pour déchiffrer

des données. Ce sont des techniques de chiffrement rapides, fiables mais relativement vulnérables du fait de l'obligation de divulguer la clé pour pouvoir déchiffrer les données. Seule cette technique de chiffrement peut être envisagée lorsque le volume de données est important.

Le chiffrement à clés asymétriques Ces algorithmes sont basés sur l'utilisation d'une paire de clés publique/privée définies telles que la clé

publique chiffre les données et la clé privée les déchiffrent. Cette technique de chiffrement semble pour beaucoup de développeurs être la recette miracle de la sécurité. Pourtant comme nous allons le voir maintenant, tout n’est pas si évident.

Données secrètes Comme le disait Sun Tzu, le bon attaquant sait éviter les défenses solides pour attaquer le point faible.

J'ai vu une fois un développeur très fier de sa sécurité, parce qu'il avait chiffré deux fois ses données, en utilisant des clés elles-mêmes chiffrées. J'ai cependant bien ri, en constatant que le mot de passe administrateur, était « maman ». Tout cela pour vous rappeler que la qualité de la sécurité se juge à son point le plus faible. Dans le cas du cryptage, quelle que soit la complexité des techniques utilisées, le point faible est forcément la clé de déchiffrement. En effet cette clé doit forcément exister en clair quelque part pour être utilisable. Le fait de chiffrer la clé pour la protéger ne fait que reporter le problème d'une étape. Sans l'utilisation de contrôle d'accès, vous ne pourrez empêcher qu'un pirate suffisamment tenace ne retrouve la clé. Il y aura de toute façon à un moment des compromis à faire. La notion de données secrètes est en fait un des points de rencontre des concepts de sécurité.

30

Page 31: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Code malicieux La notion de code malicieux, consiste à détourner un composant de ses fonctionnalités de base soit pour

le faire exécuter autre chose, soit pour retirer des informations censées être confidentielles. C'est le but du code malicieux que de trouver des failles de sécurité du code, failles dont la plus connue est le débordement de mémoire tampon. Nous entrons dans un domaine qui fait intrinsèquement partie du métier de développeurs qu’est la sécurité d'une application pendant son exécution. Le Framework vous facilite grandement la tâche puisque son modèle exécution managé prend en charge un certain nombre de vérifications. Toutefois, ce n'est pas parce que tout est fait qu'il ne faut rien faire comme disait Marie. On retrouve encore les domaines fondamentaux du développement dans cette partie, c'est-à-dire le traitement des erreurs et la validation des données.

Nous allons maintenant voir ensemble tous ces concepts de sécurité dans une application VB.NET.

Sécurité des applications VB.NET La sécurité des applications repose toujours sur les mêmes principes que dans le cadre des applications

VB 6. Toutefois, nous allons voir que le Framework nous donne les moyens d’aller beaucoup plus loin.

L’ensemble de ce chapitre est à prendre comme un tout. Vous verrez que la revue de code est indispensable.

Validation des données entrantes Bien que ce soit un des grands principes fondamentaux du développement, l'expérience prouve qu'il fait

défaut dans de nombreuses applications. Le principe général est simple. Toutes les données entrantes doivent être valides. Qu’elles arrivent d’un fichier, d’un utilisateur ou autre, que la source soit jugée fiable ou non, toutes les données entrantes doivent subir un test de validation. Il n’existe à ma connaissance aucune raison valable pour déroger à cette règle. Ce test doit avoir lieu évidemment le plus tôt possible et un test négatif doit obligatoirement conduire à un rejet de la donnée. Une règle couramment admise dit que plus une donnée invalide parcourt de code, plus celui-ci est mauvais.

Quelques erreurs souvent commises doivent être évitées : Il n’existe pas de donnée partiellement valide. Une donnée valide possède forcément un type

défini mais peut aussi avoir, un signe, une longueur définie, un validateur ou tout autre critère qui vous permet de vérifier sa validité.

Ce n’est pas parce que l’on a écrit un fichier que celui-ci est valide. Sans un système de vérification pertinent, vous ne pouvez pas savoir si ce fichier n’a pas été modifié par ailleurs. En aucun cas le seul nom de fichier ne peut être considéré comme une preuve.

Il ne faut pas confondre la validité des données avec leurs pertinences. Vérifier la validité des données est un problème de sécurité alors que vérifier leurs pertinences n’a de sens que dans un contexte. Pour prendre un exemple simple, si vous demandez la saisie d’une épaisseur, tout chiffre strictement positif est valide. Maintenant, s’il s’agit de l’épaisseur d’une feuille de papier, 3 km n’est certainement pas pertinent.

Ce sont principalement les données de type chaîne qui sont vulnérables. Dans leur livre « écrire du code sécurisé1 » les auteurs préconisent l’utilisation d’une classe de validation. Cela peut être en effet une bonne approche du problème.

Contrôle spécifique, expression régulière éventuellement contrôle utilisateurs, ce ne sont pas les moyens qui manquent pour orienter vos utilisateurs lors de leurs saisies. Cette approche vous aidera aussi en diminuant votre code de validation. Si vous avez en tête cet aspect validation lors de la création de vos formulaires, vous n’en aurez qu’une application plus sure.

1 Ecrire du code sécurisé - Michael Howard & David LeBlanc - Microsoft

31

Page 32: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Exemple 1 : Pièges de la validation Prenons le cas d’un formulaire de base de données classique permettant l’ajout d’un enregistrement

employé par exemple. Le formulaire ressemble à :

Déjà à l’œil, on peut dire que ce formulaire n’est pas bien fait. Utiliser des TextBox pour saisir des

données de type date ou numérique, c’est donner le bâton pour se faire battre. Maintenant procédons à la revue du code de validation suivant :

<Flags()> Private Enum Validation As Integer Nom = 1 Prenom = 2 Naissance = 4 Coefficient = 8 Salaire = 16 End enum Private MaValid As Validation Private Sub txtNom_Validating(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles txtNom.Validating If txtNom.Text.Length > 0 Then 'donnée valide MaValid = MaValid Or Validation.Nom Else If [Enum].IsDefined(GetType(Validation), "Nom") Then MaValid = MaValid Xor Validation.Nom e.Cancel = True End If End Sub Private Sub txtPrenom_Validated(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtPrenom.Validated If txtPrenom.Text.Length > 0 Then 'donnée valide MaValid = MaValid Or Validation.Prenom Else If [Enum].IsDefined(GetType(Validation), "Prenom") Then MaValid = MaValid - 2 End If End Sub Private Sub txtDateNaissance_Validating(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles txtDateNaissance.Validating If IsDate(txtDateNaissance.Text) Then 'donnée valide MaValid = MaValid Or Validation.Naissance Else

32

Page 33: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

If [Enum].IsDefined(GetType(Validation), "Naissance") Then MaValid = MaValid Xor Validation.Naissance e.Cancel = True End If End Sub Private Sub txtCoefficient_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtCoefficient.KeyPress Dim c As Char = e.KeyChar If Not (Char.IsNumber(c) OrElse Char.IsControl(c)) Then e.Handled = True End If End Sub Private Sub txtCoefficient_Validating(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles txtCoefficient.Validating If Me.txtCoefficient.Text.Length > 0 Then 'donnée valide MaValid = MaValid Or Validation.Coefficient Else If [Enum].IsDefined(GetType(Validation), "Coefficient") Then MaValid = MaValid Xor Validation.Coefficient e.Cancel = True End If End Sub Private Sub txtSalaire_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtSalaire.KeyPress Dim Numbers As String = "0123456789." If Not (Numbers.IndexOf(e.KeyChar) > 0 OrElse Char.IsControl(e.KeyChar)) Then e.Handled = True End If End Sub Private Sub txtSalaire_Validating(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles txtSalaire.Validating If Me.txtSalaire.Text.Length > 0 Then 'donnée valide MaValid = MaValid Or Validation.Salaire Else If [Enum].IsDefined(GetType(Validation), "Salaire") Then MaValid = MaValid Xor Validation.Salaire e.Cancel = True End If End Sub Private Sub cmdValider_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdValider.Click MsgBox(MaValid.ToString) End Sub End Class

33

Page 34: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

J’espère que vous appréciez la beauté de ce code de validation car une chose est sure, il ne valide quasiment rien correctement. Sur son architecture, il aborde le problème dans le bon sens puisqu’il privilégie une validation au niveau des contrôles à une validation au niveau du formulaire. Malheureusement, c’est bien la seule chose qu’il fasse correctement. A ce sujet, attention de ne pas mal interpréter mes propos. Utiliser du code de validation au niveau du formulaire est souvent indispensable, puisque c’est le seul endroit où on peut vérifier que tous les contrôles ont bien été validés.

Commençons par le champ Nom ou Prénom puisque la problématique est la même. Certes il vérifie bien que le champ n’est pas vide, mais c’est loin d’être suffisant. En effet, un nom de famille ne contient que des lettres, des espaces, des apostrophes ou des tirets. Personne à ma connaissance ne s’appelle « Marcel ?* Dupont66 ». Qui plus est, en l’état, rien ne m’empêche de saisir que des espaces.

Le champ date de naissance est lui correctement validé. La fonction IsDate vérifie bien si la saisie peut être interprétée comme une date ce qui est la seule validation possible. Néanmoins ce type de contrôle contient un problème car les dates peuvent être écrites assez différemment. Par exemple, la date « 04/06/02 » peut être le 4 juin 2002 comme le 6 avril selon le contexte. Enfin, mais il s’agit plus de pertinence, il faudrait mettre des bornes à la saisie.

Le champ coefficient est sensé contenir un entier. J’utilise une restriction à la frappe pour m’assurer que seuls des chiffres peuvent être saisis. Ceci est pourtant loin d'être suffisant. En effet, il peut se poser des problèmes suivants :

Je n'ai aucune garantie que le nombre saisi ne soit pas supérieur à un entier 32 bits Il n'y a pas de vérification du fait que le coefficient doit être strictement positif Rien ne m'empêche de faire un copier coller d'une chaîne de caractères

Utiliser des restrictions est une bonne habitude, faire reposer la validation uniquement sur celle-ci en est une mauvaise. À un moment ou à un autre, il doit y avoir un contrôle strict du type. Ce moment est généralement l'événement Validating du contrôle. À ce propos, vous pouvez constater que j'ai utilisé l'événement Validated pour le champ prénom. S'il s'agit d'une erreur vénielle, cela n'en demeure pas moins une erreur. Normalement, l'événement Validated suppose que le code de validation du contrôle a déjà été effectué.

Le champ salaire, est sensé contenir des données de type Currency. Le Framework ne gère pas les types Currency. On utilise le type décimal en lieu et place. J'ai utilisé une restriction, plutôt issue du monde VB 6, pour la saisie d'un nombre décimal. En plus des problèmes vus précédemment, rien ne m'empêche avec ce type de restriction de saisir un nombre à deux virgules.

Validation des chaînes Ce qui suit n'est évidemment pas vrai pour toutes les chaînes, mais dans de nombreux cas il existe des

règles permettant de restreindre les chaînes valides. Comme je l'ai dit plus haut, un nom de famille ne contient pas n'importe quels caractères.

VB.NET utilise des expressions régulières pour contrôler dans une certaine mesure les chaînes de caractères. Je n'entrerai pas dans le détail de l'utilisation des expressions régulières, d'autres cours sur www.developpez.com dont vous trouverez la référence plus bas le faisant très bien par ailleurs.

Dans notre cas, le code suivant propose une validation correcte de la chaîne : Private Sub txtNom_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtNom.KeyPress Dim c As Char = e.KeyChar If Char.IsDigit(c) Then e.Handled = True End If End Sub Private Sub txtNom_Validating(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles txtNom.Validating If txtNom.Text.Trim.Length > 0 AndAlso System.Text.RegularExpressions.Regex.IsMatch(txtNom.Text, _ "^[a-zA-Z]+([a-zA-Z])?(\')?([a-zA-Z]+)?(\-)?([a-zA-Z]+)?(\ )?([a-zA-Z]+)?$") Then 'donnée valide MaValid = MaValid Or Validation.Nom

34

Page 35: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Else If [Enum].IsDefined(GetType(Validation), "Nom") Then MaValid = MaValid Xor Validation.Nom e.Cancel = True End If End Sub

Notez que j'ai aussi ajouté une restriction à la saisie, pour empêcher que l'utilisateur ne puisse entrer des nombres. Si cela n'est pas indispensable, c'est toujours une orientation pour un utilisateur distrait, espèce qui n'est pas en voie de disparition.

Validation des nombres Comme nous l’avons vu, il doit y avoir un contrôle strict du type lorsqu'une chaîne est censée

représentée une donnée d'un autre type. La plupart des types de base exposent une méthode Parse qui permet de vérifier l'interprétation correcte

de la chaîne. On peut ainsi, avec un petit nombre d'instructions, vérifier si le type est conforme et dans le même temps, contrôler d'autres restrictions.

Dans cet exemple, je vais contrôler le nombre saisi comme coefficient est bien de type entier 32 bits mais aussi qu'il est compris entre zéro et 1000. Il faut évidemment gérer l'erreur qui sera déclenchée par la méthode Parse au cas où la donnée n'est pas du bon type. Private Sub txtCoefficient_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtCoefficient.KeyPress Dim c As Char = e.KeyChar If Not (Char.IsNumber(c) OrElse Char.IsControl(c)) Then e.Handled = True End If End Sub Private Sub txtCoefficient_Validating(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles txtCoefficient.Validating If Me.txtCoefficient.Text.Length > 0 Then 'donnée non vide Try If Int32.Parse(Me.txtCoefficient.Text) > 0 AndAlso Int32.Parse(Me.txtCoefficient.Text) < 1000 Then 'c'est un entier compris entre 0 et 1000 MaValid = MaValid Or Validation.Coefficient Else 'c'est un entier hors des limites MsgBox("Le coefficient doit être un entier entre 0 et 1000", MsgBoxStyle.Critical And MsgBoxStyle.OKOnly, "ERREUR") If [Enum].IsDefined(GetType(Validation), "Coefficient") Then MaValid = MaValid Xor Validation.Coefficient e.Cancel = True End If Catch ex As Exception 'ce n'est pas un entier MsgBox("Le coefficient doit être un nombre entier entre 0 et 1000", MsgBoxStyle.Critical And MsgBoxStyle.OKOnly, "ERREUR") If [Enum].IsDefined(GetType(Validation), "Coefficient") Then MaValid = MaValid Xor Validation.Coefficient e.Cancel = True End Try End If End Sub

35

Page 36: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Utiliser d'autres contrôles Même si nous n'allons pas le voir maintenant, il aurait été beaucoup plus pertinent d'utiliser des

contrôles « fortement typés » afin d'alléger le code de validation. Ces contrôles présentent de plus l'avantage d'orienter correctement l'utilisateur.

Gestion de l’authentification Il existe globalement deux sortes de traitements d’authentification pour les applications Winforms.

L’authentification basée sur les rôles. Dans ce type d’applications, chaque utilisateur appartient à un groupe possédant des droits et des

restrictions. Chaque utilisateur doit donc posséder un identifiant unique afin que l’application puisse l’affecter à son groupe.

Éric Vernié de Microsoft France vous propose un exemple d’application utilisant l’authentification basée sur les rôles. Vous pourrez trouver cet exemple à l’adresse :

http://www.microsoft.com/france/vbasic/ressources/application.mspx

Les applications à accès restreint Dans ce type d’applications, il n’existe qu’un seul compte permettant de définir si vous pouvez ou non

exécuter l’application. Dès lors, la gestion est assez différente du cas précédent puisque tous les utilisateurs s’authentifient avec un même mot de passe.

La méthode est assez simple. Je vais donc créer une fenêtre de saisie de mot de passe. Dans cet exemple, j’ai haché le mot de passe « password » avec l’algorithme SHA1. Je connais donc la valeur de hachage pour ce mot (sensible à la casse).

Le code suivant compare la valeur hachée du mot de passe saisi à la valeur précédemment calculée : Private Sub cmdValid_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdValid.Click Dim bitHachage As Byte() Dim wCode As New System.Text.UnicodeEncoding Dim bitPass() As Byte = wCode.GetBytes(Me.txtPass.Text) Dim sha1Hachage As New Security.Cryptography.SHA1CryptoServiceProvider bitHachage = sha1Hachage.ComputeHash(bitPass) If Convert.ToBase64String(bitHachage).Equals("6Pl/upEE0epQR5SObftn+s2fW3M=") Then MsgBox("Vous êtes entré") Else MsgBox("caramba, encore raté") End If End Sub

Arrêtons-nous quelques instants sur les aspects sécurité. Certes la valeur hachée est assez facilement accessible puisque écrite en dur dans le code. Toutefois, il s’agit d’une valeur hachée, c'est-à-dire qu’on ne peut retrouver la valeur d’origine par la connaissance de la valeur finale. Fondamentalement, le hachage est une technique sure. Le point faible repose principalement sur la faiblesse des mots de passe généralement employés. En effet, les attaques par dictionnaire ou par la force brutale retrouve assez bien les mots ayant une signification ou les mots de passe à plage de caractères restreints (que des minuscules, que des chiffres, etc.). En fait, un mot de passe efficace est difficile à mémoriser.

36

Page 37: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Gestion des exceptions Outre le fait que tout bon développeur doit gérer les exceptions de son programme pour en assurer un

fonctionnement correct, il s’agit aussi d’un problème de sécurité. Les messages d’erreur renvoyés par le runtime peuvent contenir des informations que vous ne souhaitez pas divulguer aux utilisateurs. Il convient alors d’intercepter ces erreurs afin de restreindre les informations renvoyées. Cette gestion peut vite s’avérer assez lourde, aussi faut-il définir ce qui doit être protégé impérativement.

Généralement, toutes informations pouvant conduire à divulguer la structure de l’information doit être supprimée. Dans tous les cas, sauf coupure de courant, votre application doit se terminer correctement. Autrement dit, vous savez quelles lignes de code auront été exécutées avant l’arrêt.

Un exemple typique est l’accès à un fichier. Si le fichier n’existe pas l’exception déclenchée va renvoyer l’emplacement du fichier posant problème. Cela peut être votre volonté de divulguer cette information, mais dans certains cas cela peut ne pas être souhaitable. En interceptant le message d’erreur et en modifiant le message voir le code de l’erreur vous pouvez sécuriser votre application.

Privilèges & autorisations Nous allons aborder ici la sécurité du code vu par le runtime. Il s’agit d’un domaine un peu complexe,

nous allons donc l’étudier ensemble afin que vous voyiez bien l’utilisation et les implications de ces mécanismes. Donc, commençons par le commencement, qu’est-ce qu’un privilège ?

Un privilège est une ressource du poste de travail. Accéder au registre, travailler sur des fichiers, utiliser les imprimantes sont des privilèges.

Les concepts de sécurité sont fondamentalement basés sur l’accès aux ressources. Un code qui n’utilise aucune ressource ne demande pas de vérification de sécurité, si ce n’est la lutte contre la substitution.

La substitution consiste à remplacer une DLL non dangereuse par une autre portant le même nom. Comme nous l’avons vu, l’utilisation de son nom fort est une garantie contre ce risque pour peu que la clé privée le soit réellement.

Dès lors, étudions la sécurité pour les assemblages ayant besoin de privilèges. Ce n’est pas très compliqué une fois qu’on a saisi le principe fondamental suivant :

On n’obtient pas de privilèges par le code. Dans cette règle, tout est dit. Les privilèges sur un poste de travail sont définis d'une part dans la

stratégie locale de sécurité du poste, dans les systèmes d’exploitation NT4 et supérieurs et d'autre part dans la configuration de la sécurité du Framework. Ces deux ensembles sont accessibles dans les outils d'administration du panneau de configuration. Je ne vais pas entrer ici dans les règles à stratégie locale de sécurité puisqu'il s'agit d'administration système. Regardons par contre l'administration de la sécurité du Framework.

37

Page 38: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Il existe quatre niveaux de sécurité pour le runtime :

L'entreprise L’ordinateur L’utilisateur Le domaine d'application

À chaque niveau, on peut définir des jeux d’autorisations permettant de spécifier ce qu'un assemblage pourra ou ne pourra pas faire. Dans la figure donnée en exemple, vous voyez les autorisations dont dispose un assemblage qui s'exécute depuis Internet. Vous voyez par ailleurs qu'il existe des jeux d’autorisations prédéfinis. Par exemple, le jeu d'autorisations SkipVerification permet d'ignorer la vérification du code.

Quelques soient les droits définis à ce niveau, la sécurité du Framework ne peut préempter sur la stratégie locale définie par le système. Autrement dit, un code qui s'exécute dans la session d'un utilisateur qui n'a pas accès au registre n'aura pas accès à celui-ci même si le Framework lui en donne l'autorisation.

Fonctionnement général Lors de l'exécution d'un assemblage, le runtime construit un jeu de preuve (Evidences) en fonction de

l'authentification et de la provenance de l'assemblage. Ce jeu de preuve définit les autorisations auxquelles l’assemblage à droit. Comme je vous l'ai dit auparavant dans la règle générale, un assemblage ne peut obtenir par le code des privilèges supplémentaires.

Toutefois, il convient aussi de vérifier qu’un assemblage ne puisse pas augmenter ses privilèges en appelant un autre assemblage possédant plus d’autorisation que lui. Pour cela, le runtime contrôle toujours, sauf dans le cas d’une assertion, les privilèges de l’appelant. Le contrôle de sécurité peut donc se schématiser ainsi.

Un assemblage possède des permissions. Celles-ci lui viennent de sa provenance (local, intranet, Internet, etc.) et de son authentification (nom fort, valeur de hachage). Un assemblage peut vérifier les permissions dont il dispose afin d’adapter son comportement. Enfin un assemblage doit pouvoir définir son fonctionnement s’il est exécuté par un autre assemblage en fonction des permissions de celui-ci.

38

Page 39: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Il existe deux types de vérification : La vérification déclarative, se gère par des attributs. L’attribut se porte au niveau de l’assemblage pour

la vérification des droits de l’assemblage, au niveau de la méthode ou de la classe pour les vérifications de l’appelant.

La vérification impérative se fait à l’exécution. Elle ne concerne que la vérification des appelants

Demande d’autorisations Si vous avez suivi le principe de fonctionnement, la demande d'autorisation ne peut être utilisée pour

obtenir une permission que votre assemblage n'a pas. La demande d'autorisation se gère par un attribut au niveau de l'assemblage. C'est donc une indication de fonctionnement au moment de l'attribution de droits. Elle existe sous trois formes :

La demande minimum, consiste à spécifier les droits indispensables au bon fonctionnement de l'assemblage. Si l'une des autorisations minimum est refusée, votre code ne s'exécutera pas et une exception sera générée à l'appel de l'assemblage.

La demande optionnelle permet de préciser les ressources nécessaires à l'utilisation de certaines fonctionnalités de l'assemblage. Si ces autorisations sont refusées, ces fonctionnalités ne seront accessibles, et généreront une exception lors de leur appel.

La demande de refus stipule les autorisations que votre assemblage ne doit pas avoir quand bien même le Framework les lui donnerait. Refuser des autorisations n'a pas grand sens sauf dans le cas d'application hautement sécurisée afin de pouvoir disculper l'assemblage lors de la découverte d'une faille de sécurité. En effet, un assemblage qui n'écrit pas de fichier, n'en n’écrira pas qu'il ait l'autorisation ou non.

La construction de l'attribut est la suivante : <Assembly : NomDeLaPermissionAttribute(SecurityAction.RequestMinimum|Optional|Refuse,

Paramètres optionnels)> Par exemple :

<Assembly: Security.Permissions.RegistryPermission(Security.Permissions.SecurityAction.RequestMinimum, Read:="HKEY_CURRENT_USER\Volatile Environment\APPDATA")>

Demande comme conditions d'utilisation minimum la possibilité de lire la clé de registre spécifiée. En cas de refus, l’application ne s'exécutera pas. <Assembly: Security.Permissions.FileIOPermission(Security.Permissions.SecurityAction.RequestOptional, Write:="C:\Documents and Settings\All Users\Application Data\")>

Demande de façon optionnelle la possibilité d'écrire des fichiers dans le répertoire spécifié. En cas de refus, une exception sera générée lors de l'appel de la fonctionnalité. <Assembly: Security.Permissions.EnvironmentPermission(Security.Permissions.SecurityAction.RequestRefuse, Unrestricted:=True)>

Refuse explicitement tout accès aux variables d'environnement.

Demande de vérification Ces demandes ont pour but de vérifier que le code appelant possède bien les privilèges que votre

assemblage va mettre en place. En effet, si un assemblage peut appeler un assemblage possédant des privilèges supérieurs aux siens, il y a potentiellement faille de sécurité. Pour éviter ce cas, on procède à une demande de la vérification de la pile des appels. Ces demandes peuvent prendre les formes suivantes :

Demande (Demand), précise que tous les appelants de la hiérarchie doivent avoir la permission demandée.

Permission seule (PermitOnly), définit les ressources auxquelles les appelants ont droit. Autrement dit, cela agit comme un refus pour toutes les ressources qui ne sont pas explicitement définies.

39

Page 40: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Demande de liens (LinkDemand), interdit la liaison à votre assemblage si la vérification n'est pas satisfaite.

Demande d'héritage (InheritanceDemand), interdit d'héritage par tout élément n'ayant pas l'autorisation demandée.

Ces vérifications ne sont pas forcément déclaratives. Elles ne portent pas non plus obligatoirement sur l'assemblage. Vous pouvez restreindre ces demandes à une classe, un membre, ou gérer la demande par le code.

Refus et assertion Il existe deux cas particuliers qu’il convient d'utiliser avec précaution. L'assertion (Assert), consiste à utiliser un privilège que le code appelant n'a pas. Cela revient donc à

interdire la vérification de sécurité par le runtime. Ceci peut permettre à un assemblage hautement sécurisé de fonctionner quelque soit l'appelant. Cela revient à dire que vous vous portez garants de la non dangerosité de votre assemblage. Comme l'assertion est dangereuse, vous devez toujours l’annuler explicitement dès lors que vous n'en avez plus besoin en utilisant RevertAccess.

Le refus (Deny), consiste à interdire un privilège que le code appelant peut avoir. Cela revient à restreindre arbitrairement les autorisations du code appelant. Notez donc que PermitOnly revient à faire un Deny sur tout ce qui n’est pas autorisé explicitement.

Exemple Je vais donc modifier mon composant pour que le résultat de la régression puisse être enregistré dans un

fichier. J’ajoute donc à ma classe RegressionSimple la méthode suivante : Public Function SaveAsText(ByVal Path As String) As Boolean Dim MonFichier As System.IO.StreamWriter Try Dim FilePerm As New System.Security.Permissions.FileIOPermission(Security.Permissions.FileIOPermissionAccess.Write, Path) FilePerm.Demand() MonFichier = New System.IO.StreamWriter(Path, False) Dim cmpt As Integer For cmpt = 0 To SerieX.Count - 1 MonFichier.WriteLine(SerieX.Item(cmpt).ToString & vbTab & SerieY.Item(cmpt).ToString) Next MonFichier.WriteLine("Coefficient = " & Regression.CoeffDeter.ToString) MonFichier.WriteLine("Pente = " & Regression.Pente) MonFichier.WriteLine("Ordonnée = " & Regression.Ord_Ori) MonFichier.Flush() MonFichier.Close() Catch ex1 As Security.SecurityException Throw New MonException("bloquage de Sécurité") Catch ex2 As Exception Throw New MonException("fichier moisi") Finally If Not (MonFichier Is Nothing) Then MonFichier.Close() End Try End Function

Ceci est une utilisation de la demande de vérification impérative. Cela revient à dire, le code appelant a-t-il le droit d’écrire un fichier à l’emplacement Path donné en paramètre. S’il ne l’a pas, je déclenche une exception en récupérant la SecurityException que le runtime va lever. Je ne peux pas à l’évidence utiliser une demande RequireOptional puisque je ne connais pas à priori le nom de fichier qui va m’être passé.

Dans mon application appelante, j’ai donc le code suivant :

40

Page 41: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Private CalcStat As LibStat.Statistique.RegressionSimple Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim MonTab(,) As Single, cmpt As Integer ReDim MonTab(2, 10) Randomize() For cmpt = 0 To 9 MonTab(0, cmpt) = cmpt MonTab(1, cmpt) = (5 * cmpt + CInt(Int((3 * Rnd()) - 1))) Me.ListBox1.Items.Add(MonTab(0, cmpt).ToString & vbTab & MonTab(1, cmpt).ToString) Next Dim MesResult As LibStat.Statistique.RegressionSimple.ResultReg CalcStat = New LibStat.Statistique.RegressionSimple(MonTab) CalcStat.Calculate() MesResult = CalcStat.Resultat Me.txtCoeff.Text = MesResult.CoeffDeter.ToString Me.txtOrd.Text = MesResult.Ord_Ori.ToString Me.txtPente.Text = MesResult.Pente.ToString End Sub Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click Try CalcStat.SaveAsText("d:\tutoriel\migration2\result.txt") Catch ex As LibStat.Statistique.MonException MsgBox(ex.Message, MsgBoxStyle.Critical, "") End Try End Sub

Avec un click sur le bouton2, je tente de sauvegarder les résultats précédemment calculés dans le fichier texte "d:\tutoriel\migration2\result.txt" et je récupère l’erreur éventuelle. Dans ce cas évidemment tout fonctionne puisque mon application locale dans ma session utilisateur à le droit de créer un tel fichier.

Je vais maintenant m’amuser à restreindre mes droits à l’aide d’un attribut. Je vais écrire : <Security.Permissions.FileIOPermissionAttribute(Security.Permissions.SecurityAction.PermitOnly, Read:="C:\")> Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

L’attribut au niveau du membre est valide, et je n’autorise que la lecture du disque C. Si j’exécute mon code et que je trace à l’intérieur de LibStat, je vais bien voir se lever l’exception de sécurité lors de la demande de vérification. Néanmoins ma procédure Click va planter lors de l’appel à MsgBox dans le Catch.

Cela vient du fait, que comme je vous l’ai dis, PermitOnly agit comme un déni de toutes les ressources non précisées explicitement dans le PermitOnly. En écrivant l’attribut tel que je l’ai fais, j’ai bloqué la ressource interface utilisateur qui est indispensable pour pouvoir afficher la boite de message. Pour obtenir un fonctionnement correct je dois écrire : <Security.Permissions.FileIOPermissionAttribute(Security.Permissions.SecurityAction.PermitOnly, Read:="C:\"), _ Security.Permissions.UIPermission(Security.Permissions.SecurityAction.PermitOnly, unrestricted:=True)> Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

N.B. : Ce n’est pas une bonne façon de procéder. On ne doit pas utiliser PermitOnly, Deny ou Assert sans avoir de bonne raison de le faire, et il vaut mieux éviter de s’en servir dans des attributs. Car normalement pour contrôler correctement ces actions, je dois pouvoir faire un Revert dès lors que je n’en ai plus besoin. Une écriture correcte serait par exemple :

41

Page 42: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click Dim FilePerm As New Security.Permissions.FileIOPermission(Security.Permissions.FileIOPermissionAccess.Read, "C:\") FilePerm.PermitOnly() Try CalcStat.SaveAsText("d:\tutoriel\migration2\result.txt") Catch ex As LibStat.Statistique.MonException FilePerm.RevertPermitOnly() MsgBox(ex.Message, MsgBoxStyle.Critical, "") End Try End Sub

Sécurité des données en VB.NET

Hachage complexe Le hachage étant une sorte de calcul d’identité, on l’utilise fréquemment pour la gestion de la sécurité

des données. Comme souvent, on peut facilement placer une confiance trop grande dans le hachage. Si la valeur hachée ne permet pas de remonter à la valeur claire, on peut procéder à une analyse d’identité pour retrouver cette valeur. C’est généralement ce que font les pirates pour découvrir les mots de passe.

On utilise le hachage dans d’autre cas à des fins de vérification d’intégrité.

Haché et salé Lorsqu’on transporte des données, on utilise le hachage pour garantir que ces données n’ont pas été

corrompues pendant le transport. Lorsqu’on transporte des données volumineuses, on découpe le transfert par paquet et on hache les paquets. Il y a une possibilité de mesure identitaire puisque deux paquets identiques vont avoir une valeur de hachage identique. Pour durcir le système, on procède au salage. Celui-ci consiste à hacher avec le paquet une valeur aléatoire que l’on transmettra en clair avec les données. Comme chaque paquet aura sa propre valeur aléatoire, deux paquets identiques n’auront pas la même valeur hachée. Comme cette technique est rarement utilisée dans les applications WinForms je n’entrerai pas dans le détail, mais si le sujet vous intéresse, sachez qu’il existe dans l’espace de nom System.Security.Cryptography une classe PasswordDeriveBytes qui gère le salage.

Contrôle de validité Comme nous l’avons dit précédemment, ce n’est pas parce que votre application a créé un fichier que

celui-ci sera valide lors de son prochain accès. Une chose est sûre, le simple nom de fichier ne saurait être une garantie. Dès lors, on voit bien une solution évidente, stocker une valeur hachée de ce fichier et vérifier que celle-ci correspond bien. C’est parfaitement efficace à un bémol près, si un attaquant devine ce que vous avez haché, rien ne l’empêche de modifier le fichier puis de recalculer la valeur hachée. Votre contrôle de validité sera ainsi leurré. Par expérience, je peux vous dire ce qu’il ne faut pas faire car ce seront les premiers tests d’un hacker fut-il boutonneux :

Le nom du fichier Une date du fichier Le fichier entier Une composition des cas précédents

Cela ne veut pas dire que vous ne devez pas utiliser une des valeurs précédentes dans votre chaîne d’empreinte, mais elle ne doit pas être composée uniquement de ces données.

Je ne vais évidemment pas vous dire ma façon de procéder, mais nous allons imaginer ensemble une technique pour contrôler que notre fichier de statistique est valide.

42

Page 43: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Tout d'abord, nous n’allons certainement pas utiliser le nom du fichier, car cela m'interdirait la lecture de fichier pourtant valide situé dans un autre emplacement. Comme vous le voyez, le fichier est suffisamment petit pour qu'il ne soit pas incohérent pour un pirate de tester toutes les combinaisons possibles afin de découvrir comment nous avons construit l'empreinte. Si je construis ma chaîne d'empreinte en utilisant la deuxième ligne de données, la valeur de la pente et la date de création du fichier, il ne faudra pas longtemps à un attaquant déterminé pour trouver la solution.

La première et la plus fiable des méthodes consiste à hacher une valeur secrète avec ma chaîne d'empreinte. Cela va nous faire retomber sur le problème que nous verrons après : la conservation des valeurs secrètes.

Une deuxième méthode, qui peut être particulièrement efficace pour la gestion de fichier personnel, consiste à utiliser dans la chaîne d'empreinte un paramètre de la session utilisateur. Dès lors, seul un utilisateur connecté dans cette session pourra vérifier le hachage correct de la chaîne d'empreinte.

Dans notre cas, comme nous aimons la difficulté, nous voulons produire une chaîne d'empreinte difficile à deviner tout en étant assez souple pour pouvoir lire un fichier valide à partir d'un poste quelconque.

Comme fondamentalement notre fichier stocke des nombres, nous allons utiliser une manipulation numérique pour produire la chaîne d'empreinte. Regardons le code suivant : Public Function SaveAsText(ByVal Path As String) As Boolean Dim MonFichier As System.IO.StreamWriter Try Dim FilePerm As New System.Security.Permissions.FileIOPermission(Security.Permissions.FileIOPermissionAccess.Write, Path) FilePerm.Demand() 'création de l'empreinte Dim CalcInter As Double Dim Empreinte As New System.Text.UnicodeEncoding CalcInter = Me.Regression.SumXY + 222 + Me.Regression.Ord_Ori Dim bitPass() As Byte = Empreinte.GetBytes(CStr(CalcInter)) Dim sha1Hachage As New Security.Cryptography.SHA1CryptoServiceProvider MonFichier = New System.IO.StreamWriter(Path, False) MonFichier.WriteLine(Convert.ToBase64String(sha1Hachage.ComputeHash(bitPass))) Dim cmpt As Integer For cmpt = 0 To SerieX.Count - 1 MonFichier.WriteLine(SerieX.Item(cmpt).ToString & vbTab & SerieY.Item(cmpt).ToString) Next ‘Etc… ….. Catch ex1 As Security.SecurityException Throw New MonException("bloquage de Sécurité") Catch ex2 As Exception Throw New MonException("fichier moisi") Finally If Not (MonFichier Is Nothing) Then MonFichier.Close() End Try End Function

Comme vous le voyez, j’utilise trois composants pour générer mon empreinte : Une donnée que je pourrais recalculer en lisant mon fichier Une donnée arbitraire Une donnée présente dans le fichier

Pourquoi cette technique est-elle sûre ?

43

Page 44: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Parce que l’empreinte n’est pas la seule garantie de l’intégrité du fichier. En effet, je connais la structure de mon fichier. Comme celui-ci contient des données fortement typées, j’aurai une indication de validité du fait de la validation des données entrantes.

Imaginons le code de lecture suivant : Private Sub cmdReadFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdReadFile.Click Dim LecteurFichier As System.IO.StreamReader Dim Empreinte As String, LigneLue As String, Test As Double Dim TabX As New ArrayList, TabY As New ArrayList Try LecteurFichier = New System.IO.StreamReader("d:\tutoriel\migration2\result.txt") Empreinte = LecteurFichier.ReadLine Convert.FromBase64String(Empreinte) LigneLue = LecteurFichier.ReadLine Do While Not (LigneLue.StartsWith("Coefficient")) TabX.Add(Single.Parse(LigneLue.Substring(0, LigneLue.IndexOf(vbTab)))) TabY.Add(Single.Parse(LigneLue.Substring(LigneLue.IndexOf(vbTab) + 1))) LigneLue = LecteurFichier.ReadLine Loop Test = Double.Parse(LigneLue.Substring(LigneLue.LastIndexOf(" "))) Dim cmpt As Integer For cmpt = 0 To TabX.Count - 1 Test = Test + (CDbl(TabX.Item(cmpt)) * CDbl(TabY.Item(cmpt))) Next Test = Test + 222 Dim Decodeur As New System.Text.UnicodeEncoding Dim bitPass() As Byte = Decodeur.GetBytes(CStr(Test)) Dim sha1Hachage As New Security.Cryptography.SHA1CryptoServiceProvider If Not (Empreinte = Convert.ToBase64String(sha1Hachage.ComputeHash(bitPass))) Then Throw New Security.SecurityException("le controle de sécurité à échoué") End If 'je peux remplir mon formulaire si j'ai atteint ce point Me.txtCoeff.Text = LigneLue.Substring(LigneLue.LastIndexOf(" ")) LigneLue = LecteurFichier.ReadLine Me.txtPente.Text = LigneLue.Substring(LigneLue.LastIndexOf(" ")) LigneLue = LecteurFichier.ReadLine Me.txtOrd.Text = LigneLue.Substring(LigneLue.LastIndexOf(" ")) For cmpt = 0 To TabX.Count - 1 Me.ListBox1.Items.Add(TabX.Item(cmpt).ToString & vbTab & TabY.Item(cmpt).ToString) Next Catch ex As Exception MsgBox("impossible de lire le fichier") Finally If Not LecteurFichier Is Nothing Then LecteurFichier.Close() End Try End Sub

C’est beau hein !

44

Page 45: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Le principe est simple, j’utilise des parse pour faire déclencher une exception si les données ne sont plus correctement typées. Je réalise ainsi dans la même fonction, la validité des données entrantes et un contrôle supplémentaire de validité du fichier.

Chiffrement des données Mon fichier a beau être valide, il est tout de même lisible par tous. Supposons que je veuille maintenant

rendre les données inutilisables. Je vais devoir les chiffrer. Le chiffrement diffère largement du hachage puisqu’il faut aussi pouvoir les déchiffrer. Et c’est bien là que le bât blesse. Car il va falloir que je trouve un moyen d’utiliser la clé sans pour autant la rendre accessible.

On peut être tenté de créer son propre algorithme en pensant ainsi compliquer la tâche de l’attaquant. La construction d'un mécanisme de chiffrement efficace est particulièrement complexe. Mieux vaut utiliser un algorithme fiable qui a fait ses preuves qu'un bricolage personnalisé. Pour l'anecdote, sachez que je garde précieusement un fichier que j'ai chiffré il y a quelques années et que je n'ai toujours pas pu déchiffrer.

Il n'est pas utile de multiplier les chiffrements des mêmes données, car la problématique attaquant défenseur est connue. Ce n'est pas le nombre de couches de chiffrement qui garantira la sécurité de vos données mais la qualité de protection de la clé de déchiffrement.

Il y a plusieurs approches possibles, mais elles dépendent de paramètres qui ne sont pas uniquement dépendant de la sécurité. Je peux par exemple décider d’utiliser une clé de registre pour stocker ma clé. Pour peu que ma clé de registre possède une ACL ad hoc, la clé sera correctement protégée. Par contre je peux oublier le déploiement XCopy si je procède ainsi.

Je peux aussi utiliser des paramètres de la configuration machine. Néanmoins cela demande que l’utilisateur ait accès au registre hardware dans sa session. Et le jour où un périphérique change, c’est le désastre.

Dans notre cas, j’ai toutefois une solution simple, le mot de passe de connexion. En effet, celui-ci n’est normalement que dans la tête de l’utilisateur autorisé. Cette méthode n’est toutefois pas aisément utilisable dans le cas d’une authentification basée sur les rôles puisque chaque utilisateur possède un mot de passe différent. Cependant dans notre cas, c’est tout-à-fait adapté.

Sachez enfin qu’il est possible d’appeler des API Windows pour protéger la clé, mais cela demande de faire appel à du code non managé ce qui demande de veiller à la sécurité de cet appel.

Dérivation du mot de passe La dérivation du mot de passe consiste à générer une clé de longueur spécifiée en partant d’un texte, en

l’occurrence le mot de passe. On utilise la classe PasswordDerivedBytes, utilisée aussi pour le salage, pour générer la clé. Celle-ci accepte plusieurs algorithmes de chiffrement et plusieurs paramétrages pour générer une clé. Comme il n’est pas question de transmettre le mot de passe à une fonction je vais dériver ma clé le plus vite possible, c'est-à-dire dans mon formulaire de contrôle d’accès. Le but étant d’activer un service de chiffrement, je vais donc devoir utiliser ensuite une variable globale pour transmettre mon service à ma feuille principale.

Donc dans mon module principal j’ai déclaré mon fournisseur de chiffrement tel que : Public DES3Code As New System.Security.Cryptography.TripleDESCryptoServiceProvider

Dans mon formulaire de connexion je modifie ma procédure telle que : Private Sub cmdValid_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdValid.Click Dim bitHachage As Byte() Dim wCode As New System.Text.UnicodeEncoding Dim bitPass() As Byte = wCode.GetBytes(Me.txtPass.Text) Dim sha1Hachage As New Security.Cryptography.SHA1CryptoServiceProvider bitHachage = sha1Hachage.ComputeHash(bitPass) If Convert.ToBase64String(bitHachage).Equals("6Pl/upEE0epQR5SObftn+s2fW3M=") Then Me.DialogResult = DialogResult.OK

45

Page 46: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

DES3Code.IV = New Byte(7) {} Dim pdb As New Security.Cryptography.PasswordDeriveBytes(Me.txtPass.Text, New Byte(-1) {}) DES3Code.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, New Byte(7) {}) Else MsgBox("caramba, encore raté") Me.DialogResult = DialogResult.Cancel End If Me.Close() End Sub

Le but est de générer la clé de l’objet DES3CODE. Dans ce cas je lui demande pour cela de me générer une clé avec l’algorithme de chiffrement symétrique « RC2 », l’algorithme de hachage « MD5 » pour fournir une clé de 128 bits.

Ensuite il me suffit d’implémenter mes fonctions de chiffrement/déchiffrement dans ma feuille principale. Private Function Encode(ByVal value As String) As String Dim ms As New IO.MemoryStream((value.Length * 2) - 1) Dim CryptStream As New Security.Cryptography.CryptoStream(ms, DES3Code.CreateEncryptor(), Security.Cryptography.CryptoStreamMode.Write) Dim StrBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(value) CryptStream.Write(StrBytes, 0, StrBytes.Length) CryptStream.FlushFinalBlock() Dim CryptedBytes(CInt(ms.Length - 1)) As Byte ms.Position = 0 ms.Read(CryptedBytes, 0, CInt(ms.Length)) CryptStream.Close() Return Convert.ToBase64String(CryptedBytes) End Function Public Function Decode(ByVal value As String) As String Dim CryptedBytes As Byte() = Convert.FromBase64String(value) Dim ms As New IO.MemoryStream(value.Length) Dim DecryptStream As New Security.Cryptography.CryptoStream(ms, DES3Code.CreateDecryptor(), Security.Cryptography.CryptoStreamMode.Write) DecryptStream.Write(CryptedBytes, 0, CryptedBytes.Length) DecryptStream.FlushFinalBlock() Dim DecryptBytes(CInt(ms.Length - 1)) As Byte ms.Position = 0 ms.Read(DecryptBytes, 0, CInt(ms.Length)) DecryptStream.Close() Return System.Text.Encoding.UTF8.GetString(DecryptBytes) End Function

Comme vous le voyez le chiffrement travaille sur des flux. Là je travaille sur des flux mémoire mais je pourrais le faire de la même façon sur un flux de fichier. A ce propos, il y a une erreur à toujours éviter. Lorsque vous chiffrez un fichier, il faut directement écrire le fichier chiffré. Une erreur traditionnelle consiste à écrire le fichier en clair puis à le chiffrer avant de détruire le fichier en clair. Si votre application s’interromp au cours de la manipulation, vous risquez de laisser une version claire du fichier ou pire les deux versions ce qui permettrait de déduire la clé.

46

Page 47: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Revue de code Autant le dire tout de suite, c’est une partie du métier de développeur que je n’apprécie guère. Je trouve

cela rapidement fastidieux. Pour autant, reconnaissons que c’est indispensable et que les bogues détectés à ce niveau là éviteront de vous bouffer du temps au moment des tests. Le mieux est toujours de faire faire la revue par un autre qui lit plus facilement le code avec l’inertie du bovidé face à une locomotive, attitude à avoir pour ce genre d’exercice. Lorsqu’on doit le faire soi-même, il faut impérativement « sortir du code ». J’entends par là oublier le contexte de codage, les réflexions du développement, etc.

Sans quoi, l’expérience prouve qu’on ne lit pas réellement le code, mais un code tel qu’on le pense. J’ai d’ailleurs un jour, suite à un problème de santé, procédé à deux revues du même code à une semaine d’intervalle. A chaud, je n’avais trouvé qu’un bogue, une semaine après sept de plus.

Je ne vais pas ici vous construire de plan de revue ou de plan de test car cela nous écarterait un peu trop du sujet principal, cependant si cela vous intéresse, h’hésitez pas à me contacter.

Le composant LibStat Il comprend donc deux classes. La classe SerieUnique est une collection fortement typée sur laquelle j’ai

greffée une méthode de calcul et une structure de résultat. La revue est assez simple puisque tout ce qui entre ou sort doit être soit du type Single, soit la structure. D’un autre coté, je n’ai pas géré les erreurs spécifiques à la collection de base. C'est-à-dire qu’il est assez facile de déclencher une erreur, en appelant RemoveAt d’un élément qui n’existe pas, par exemple. Il s’agit là d’un choix que nous reverrons mieux lorsque nous parlerons des composants, mais sachez dès à présent que si mon composant doit être hautement sécurisé, un tel comportement est un bogue.

Regardons maintenant la classe Regression. Je ne vais pas vous remettre tout le code, mais uniquement les parties qui posent problème : Public Property item(ByVal Index As Int32, ByVal NumSerie As Int32) As Single Get If NumSerie = 1 Then Return SerieX.Item(Index) Else Return SerieY.Item(Index) End Get Set(ByVal Value As Single) If NumSerie = 1 Then SerieX.Item(Index) = Value Else SerieY.Item(Index) = Value End Set End Property

Je pense que vous voyez la faute. Il n’existe que deux séries dans mon objet mais le paramètre NumSerie n’est pas borné. Evidemment, tel que c’est écrit, cela ne plantera pas, mais c’est tout de même une faute. Une correction possible serait d’utiliser un paramètre booléen, une autre de limiter les valeurs admissibles pour le paramètre. Public Sub Calculate() Dim cmpt As Integer, SommeXY As Double If SerieX.Count < 2 Then Throw New MonException("Il faut au moins deux valeurs dans la série pour effectuer le calcul") Exit Sub End If For cmpt = 0 To SerieX.Count - 1 SommeXY = SommeXY + (SerieX.Item(cmpt) * SerieY.Item(cmpt)) Next Dim ResSerieX, ResSerieY As Statistique.SerieUnique.ResulStat SerieX.Calculate() SerieY.Calculate() ResSerieX = SerieX.Resultat ResSerieY = SerieY.Resultat With Regression

47

Page 48: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

.SumXY = SommeXY .CoeffDeter = (ResSerieX.Population * SommeXY - ResSerieX.Somme * ResSerieY.Somme) / (Sqrt((ResSerieX.Population * ResSerieX.SommeCarre - Pow(ResSerieX.Somme, 2)) * (ResSerieY.Population * ResSerieY.SommeCarre - Pow(ResSerieY.Somme, 2)))) .Pente = (ResSerieX.Population * SommeXY - ResSerieX.Somme * ResSerieY.Somme) / (ResSerieX.Population * ResSerieX.SommeCarre - Pow(ResSerieX.Somme, 2)) .Ord_Ori = ResSerieY.Moyenne - .Pente * ResSerieX.Moyenne End With End Sub

Le bogue numéro 2 est tellement classique qu’on le rate fréquemment. Il n’y a pas d’interception d’erreur. Si une de mes séries est remplie de 0 par exemple, il y a une faute mathématique de type division par zéro et le composant va se gaufrer lamentablement. Public Function SaveAsText(ByVal Path As String) As Boolean Dim MonFichier As System.IO.StreamWriter Try Dim FilePerm As New System.Security.Permissions.FileIOPermission( Security.Permissions.FileIOPermissionAccess.Write, Path) FilePerm.Demand() 'création de l'empreinte Dim CalcInter As Double Dim Empreinte As New System.Text.UnicodeEncoding CalcInter = Me.Regression.SumXY + 222 + Me.Regression.CoeffDeter Dim bitPass() As Byte = Empreinte.GetBytes(CStr(CalcInter)) Dim sha1Hachage As New Security.Cryptography.SHA1CryptoServiceProvider MonFichier = New System.IO.StreamWriter(Path, False) MonFichier.WriteLine(Convert.ToBase64String( sha1Hachage.ComputeHash(bitPass))) Dim cmpt As Integer For cmpt = 0 To SerieX.Count - 1 MonFichier.WriteLine(SerieX.Item(cmpt).ToString & vbTab & SerieY.Item(cmpt).ToString) Next MonFichier.WriteLine("Coefficient = " & Regression.CoeffDeter.ToString) MonFichier.WriteLine("Pente = " & Regression.Pente) MonFichier.WriteLine("Ordonnée = " & Regression.Ord_Ori) MonFichier.Flush() MonFichier.Close() Catch ex1 As Security.SecurityException Throw New MonException("bloquage de Sécurité") Catch ex2 As Exception Throw New MonException("fichier moisi") Finally If Not (MonFichier Is Nothing) Then MonFichier.Close() End Try End Function

Là j’ai fait très fort puisqu’il y a cinq bogues de sécurité, ce qui pour une fonction aussi courte est presque un sommet.

Une faute de validation. Je ne contrôle pas la chaîne du paramètre Path. Celle-ci peut représenter tout et n’importe quoi, à aucun moment je ne cherche à savoir si elle représente une chaîne valide dans mon système de fichier.

48

Page 49: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Une faute de sécurité majeure. Dans le cas ou l’appelant a le privilège d’écrire dans des répertoires stratégiques, je peux aller écraser n’importe quel fichier système avec mon fichier résultat.

Une faute de hachage. Si vous avez bien suivi les chapitres précédents, vous l’avez peut être remarquée. Sinon, je vous la montrerai dans la revue du code de l’application

Une faute d’écriture. Bien qu’il s’agisse d’une fonction, elle retourne toujours False puisqu’il n’y a pas de return valide en cas de succès.

Le dernier bogue est le plus vicieux. Examinons la ligne : Dim bitPass() As Byte = Empreinte.GetBytes(CStr(CalcInter))

Lors de l’appel CStr(CalcInter) j’ai créé une variable intermédiaire de type chaîne qui contient l’empreinte. Les chaînes dans DotNet sont inaltérables. Cela veut dire que durant tout le temps de l’écriture du fichier, il va exister une version claire de l’empreinte utilisée pour le hachage dans la mémoire. On ne doit pas utiliser de chaîne pour stocker des secrets dans DotNet. Il faut forcément passer par des structures que l’on peut détruire.

L’application La première faute est un problème d’architecture. C’est une erreur que l’on commet souvent en VB6.

Pour l’instant, j’utilise un module pour gérer l’enchaînement de mes feuilles tel que : Module ModMain Public DES3Code As New System.Security.Cryptography.TripleDESCryptoServiceProvider Public Sub Main() Dim ControleSecurite As New frmPass If ControleSecurite.ShowDialog() <> DialogResult.OK Then Exit Sub Dim FeuillePrincipal As New frmMain ControleSecurite.Dispose() FeuillePrincipal.ShowDialog() End Sub End Module

Ce module est mon objet de démarrage. L’approche en soit n’est pas forcément mauvaise. Cependant, dans ce cas ce n'est pas la bonne approche. En procédant ainsi j'utilise une variable publique pour stocker mon fournisseur de chiffrement, fournisseur qui contiendra la clé sensée demeurer secrète.

J'aurais bien meilleur compte à démarrer sur mon formulaire principal, en utilisant mon formulaire de connexion comme satellite de celui-ci, afin de restreindre la portée du fournisseur. De manière générale, on doit toujours tenter d'appliquer la règle suivante :

Rapprocher les objets de chiffrement des données à chiffrer. Continuons notre revue de code :

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim MonTab(,) As Single, cmpt As Integer ReDim MonTab(1, 9) Randomize() For cmpt = 0 To 9 MonTab(0, cmpt) = cmpt MonTab(1, cmpt) = (5 * cmpt + CInt(Int((3 * Rnd()) - 1))) Me.ListBox1.Items.Add(MonTab(0, cmpt).ToString & vbTab & MonTab(1, cmpt).ToString) Next Dim MesResult As LibStat.Statistique.RegressionSimple.ResultReg CalcStat = New LibStat.Statistique.RegressionSimple(MonTab) CalcStat.Calculate() MesResult = CalcStat.Resultat Me.txtCoeff.Text = MesResult.CoeffDeter.ToString Me.txtOrd.Text = MesResult.Ord_Ori.ToString

49

Page 50: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Me.txtPente.Text = MesResult.Pente.ToString End Sub

J'ai reproduit ici la même erreur que précédemment mais en pire. De nouveaux, j'ai omis l'interception des erreurs, ce qui est à ce niveau impardonnable. En effet, tout comme les saumons, les erreurs remontent la liste des appelants pour trouver un gestionnaire d'erreur. Au niveau de mon application, il ne risque pas d’y avoir de gestionnaire d'erreur au-dessus. Toutes les erreurs non interceptées causeront donc l'arrêt intempestif du programme.

Je ne vais pas recopier ici le code de la fonction ReadFile, mais nous allons parler de la faute de hachage que je vous ai signalé dans la revue du composant. Si vous reprenez ce code, vous voyez que j'ai placé un commentaire tel que :

'je peux remplir mon formulaire si j'ai atteint ce point Celui-ci sous-entend qu'à partir de ce point, j’ai la certitude que le fichier lu est valide. Il n'en est rien.

Imaginons que j'ai ouvert mon fichier résultat dans un éditeur quelconque, et que j'ai modifié la valeur de la pente. Comme celle-ci n'entre pas dans le calcul de l'empreinte utilisée pour le hachage, sa modification ne sera pas détectée par la mesure d'empreinte. De plus, je n'ai pas fait de Parse sur un type double pour vérifier si la pente était valide. J'ai donc rempli mon formulaire avec un fichier substitué. D'où vient l'erreur ?

Lorsque je vous ai parlé des valeurs qu'il fallait éviter d'utiliser pour un hachage du fait de leur trop grande prédictibilité, j'ai omis une simple règle de bon sens. Quelque part dans le calcul du hachage, on doit toujours avoir l'ensemble du fichier. C'est sensiblement la seule garantie contre les modifications fines de celui-ci. Rappelez-vous donc d'utiliser toujours cette règle :

L'empreinte du fichier doit faire partie de l'empreinte de sécurité.

Conclusion sur la sécurité Nous avons procédé ici à une approche superficielle de la problématique sécurité. Il faudrait une étude

beaucoup plus approfondie pour comprendre sérieusement toutes les implications de l'écriture du code sécurisé. J'essaierai à l'occasion d'écrire un cours plus complet sur le sujet.

Sérialisation La sérialisation est le processus qui permet de transformer un objet en flux, puis de réaliser l'opération

inverse par la désérialisation. On utilise ce mécanisme à des fins de persistance ou de transport des objets. Dans notre cas, par exemple, notre système de stockage par fichier n'est pas élégant. En effet, je

sauvegarde l'ensemble des données, au moment de leur relecture, je ne peux plus les modifier sauf à recréer entièrement un objet. Il serait beaucoup plus efficace, de rendre l'objet persistant dans un fichier et de pouvoir recréer celui-ci à l'aide du fichier. C'est la sérialisation.

Lorsque le processus de sérialisation est simple on utilise simplement des attributs. Par défaut, rien n’est sérialisable. Si je marque une classe à l'aide de l'attribut sérialisable, tous ses membres le deviennent aussi. Ceci n’est pas toujours vrai comme nous allons le voir.

Je vais donc rendre ma classe SerieUnique sérialisable. J’écris donc : <Serializable()> Public Class SerieUnique

Si je tentais de sérialiser en l’état je récupérerai une erreur. Ceci parce que mon type SerieUnique contient une structure ResultStat qui est elle-même un type complexe. Je dois aussi marquer ma structure comme sérialisable pour obtenir un résultat valide.

J’écris donc ma classe telle que : <Serializable()> Public Class SerieUnique Inherits CollectionBase <Serializable()> Public Structure ResulStat

Il existe deux types de sérialisation. La sérialisation binaire, utilise un stockage binaire compact que vous pouvez utilisez pour rendre vos objets persistants. La sérialisation ‘textuelle’ (XML, SOAP) utilise des formats plus adaptés si vous devez transporter vos objets. Dans mon exemple, comme il ne s’agit que de la persistance sur disque, je vais utiliser un format binaire. Les deux fonctionnent à l’identique.

50

Page 51: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

Imaginons le code suivant : Private Sub cmdSerialize_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdSerialize.Click Dim cmpt As Integer Dim MaSerie As New LibStat.Statistique.SerieUnique For cmpt = 1 To 10 MaSerie.Add(cmpt) Me.ListBox1.Items.Add(cmpt.ToString) Next MaSerie.Calculate() Me.txtPop.Text = MaSerie.Resultat.Population.ToString Me.txtMoy.Text = MaSerie.Resultat.Moyenne.ToString Me.txtVar.Text = MaSerie.Resultat.Variance.ToString Dim MonFichier As New IO.FileStream("d:\tutoriel\migration2\serie.ser", IO.FileMode.Create) Dim Formateur As New Runtime.Serialization.Formatters.Binary.BinaryFormatter Formateur.Serialize(MonFichier, MaSerie) MonFichier.Close() End Sub

Je crée mon objet SerieUnique que j’affiche dans mon formulaire, puis je le rends persistant. Pour cela, je ne fais que créer un fichier et j’utilise le formateur binaire standard, l’ensemble du mécanisme est géré par le runtime.

Pour récupérer mon objet, je ferai alors : Private Sub cmdDeserialize_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdDeserialize.Click Me.ListBox1.Items.Clear() Dim MonFichier As New IO.FileStream("d:\tutoriel\migration2\serie.ser", IO.FileMode.Open) Dim Formateur As New Runtime.Serialization.Formatters.Binary.BinaryFormatter Dim MaSerie As LibStat.Statistique.SerieUnique = CType(Formateur.Deserialize(MonFichier), LibStat.Statistique.SerieUnique) MonFichier.Close() MaSerie.Add(11) MaSerie.Calculate() Dim monenum As IEnumerator = MaSerie.GetEnumerator While monenum.MoveNext Me.ListBox1.Items.Add(monenum.Current) End While Me.txtPop.Text = MaSerie.Resultat.Population.ToString Me.txtMoy.Text = MaSerie.Resultat.Moyenne.ToString Me.txtVar.Text = MaSerie.Resultat.Variance.ToString End Sub

Pour bien vous montrez que j’ai bien récupéré l’objet, et non les données, j’ai ajouté une valeur à la série et relancé le calcul. Les résultats affichés sont bien ceux attendus. C’est extrêmement simple.

Il y a parfois une utilité à gérer un peu mieux la sérialisation, et pour cela on utilise les interfaces ISerializable et IDeserializationCallback. Prenons notre exemple. Lorsque j’ai sérialisé ma série, j’ai en fait stocké les données et les résultats. C’est stupide puisque je peux retrouver les résultats en partant des données. Je vais donc modifier ma classe, pour ne pas rendre persistant ce qui n’a pas besoin de l’être.

Je modifie mon composant tel que : <Serializable()> Public Class SerieUnique Inherits CollectionBase Implements Runtime.Serialization.IDeserializationCallback

51

Page 52: Migrer de VB6 à VB.NET – Deuxième partiebidou.ftp-developpez.com/Cours/DotNet/migration/... · 2005-03-21 · Migrer de VB6 à VB.NET – Deuxième partie J-M Rabilloud . Publication

<Serializable()> Public Structure ResulStat Dim Moyenne As Single Dim Variance As Double Dim Somme As Double Dim SommeCarre As Double Dim Population As Int32 End Structure <NonSerialized()> Private Result As ResulStat Public Sub OnDeserialization(ByVal sender As Object) Implements System.Runtime.Serialization.IDeserializationCallback.OnDeserialization Me.Calculate() End Sub

Je marque donc la variable Result avec l’attribut NonSerialized pour spécifier qu’elle ne doit pas être rendue persistante. Pour que lors de la désérialisation, mon objet se reconstruise correctement, je dois appeler sa méthode Calculate. L’interface IDeserializationCallback met à ma disposition une méthode OnDeserialization qui sera appelée chaque fois que l’objet se désérialise. Il me suffit d’agir dans cette méthode pour que l’objet se reconstruise correctement. Notez que tout cela n’a pas d’incidence sur le code appelant. C’est l’objet sérialisable qui gère son processus de reconstruction de façon invisible pour l’extérieur. Si je compare les fichiers créés par les deux méthodes, je constate que le deuxième est plus petit que le premier (347 au lieu de 543 octets).

Sécurité du processus de sérialisation La sérialisation est extrêmement puissante et souvent très pratique. De par ce fait, il convient d’être

particulièrement vigilant quand à sa sécurité. Pourquoi cela ? La sérialisation rend vos objets particulièrement vulnérables dès lors qu'ils sont sous forme de flux.

Même si cela est un peu moins vrai en mode binaire, il est assez facile d'extraire des données d'un objet sérialisé. Vous donner donc garder l'esprit qu'il vous faudra peut-être parfois construire des objets sérialisable chiffré, voire de ne pas utiliser la sérialisation pour les données confidentielles. Vous devez aussi veiller à ce que le droit de sérialiser soit bien considéré comme un privilège important et qu'il ne soit pas accessible à partir de zones non sûres.

Il en est évidemment de même pour la désérialisation. En aucun cas on ne devrait tenter de reconstruire des objets provenant de sources qui ne seraient pas d'une confiance absolue. N'oubliez pas que les objets reconstruits sont du code et que celui-ci peut-être malveillant.

Conclusion Comme je vous l'ai dit dès l'introduction, ce cours ne peut prétendre être exhaustif sur les nouveautés de

la plate-forme. Il s'agit en fait d'une découverte de quelques fonctionnalités intéressantes. Si vous voulez plus d’informations sur certains sujets je vous recommande de lire : Introduction à la réflexion en .NET par Olivier Brin Localisez vos applications Dotnet par Thomas LEBRUN Protection, optimisation et déploiement de code .NET grâce à XenoCode 2005 par LEBRUN

Thomas Utilisation des expressions régulières en .Net par Louis-Guillaume Morand Dans la prochaine partie de ce cours, nous parlerons plus particulièrement de contrôles et

de formulaire.

52


Top Related