2000 programmes dans un store, ce n’est pas assez ?!

Je lis par ci par sur internet que le Windows Store a un problème parce qu’il n’a pas assez d’applications. Je ne sais pas si je suis tout seul, mais personnellement, 2000 applications, c’est déjà 100 fois plus que ce que j’installerai sur ma tablette Windows 8 quand j’en aurai une.

Déjà, toutes les grosses applications (bureautique complète, graphisme, retouche pro, montage de video, conception, programmation, 3D, etc.) seront de toute façon installées en local et pas nécessairement par le Store.

Ensuite, honnêtement, à part les quelques dizaines d’applications dont tout le monde a besoin (lecteur multimédia, PDF, gestionnaire de fichiers compressés, Antivirus, Capture d’écran, retouche d’image, etc.) et qui représentent 90% des téléchargements, il y a une deuxième frange de quelques centaines maximum d’applications spécialisées et qui sont les deux-trois meilleures dans leur catégorie. Le reste, ce sont des applications qui ne perceront jamais.

Je lis que le Google Play Store dispose de 400 000 applications, mais quel intérêt à part embrouiller l’utilisateur ? Je dispose d’une tablette Nexus 7, et lorsque j’ai voulu installer un lecteur PDF, le magasin d’applications ne m’a été d’aucune utilité. Après quelques essais infructueux, je me suis rabattu sur le web pour trouver des comparatifs, et je n’ai téléchargé que les deux-trois considérés unanimement comme les meilleures, pour au final garder celle qui me convenait le mieux. Or, il y avait plus de cent entrées avec le mot clé PDF. J’aime avoir le choix, mais dans ce cas précis, l’expérience utilisateur était complètement ratée. L’argument mis en avant pour justifier ce nombre abominable est “donner le choix à l’utilisateur”. Mais au final, une sélection entre 90% de jeux pourries, 9% d’applications de seconde zone, et le 1% qui fait parfaitement l’affaire, on ne peut pas vraiment parler d’un choix joyeux…

Autre chose que je trouve assez révélateur : la recherche d’applications dans ce même magasin se limite aux cents premières trouvées dans la recherche. D’ailleurs, même les pages utilisées ne montrent que les “featured” applications et les 100 meilleures applications payantes / 100 meilleures applications gratuites. Je suis prêt à parier que quasi-personne ne va au-delà.

image

Bref, 2000 applications me paraissent déjà largement suffisant, à condition que ce soit de la qualité. Microsoft, par pitié, ne bourrez pas votre Store avec des applications médiocres ou des jeux qui ressemblent à des clones ratés de Rick Dangerous sous émulateur Atari, juste pour faire du nombre !

Posted in Uncategorized | Tagged | 3 Comments

UDDI : mort et enterré…

Ca faisait un bout de temps que je n’avais pas vérifié http://uddi.microsoft.com ou http://uddi.ibm.com, mais il y a quelques années, j’avais déjà remarqué que le contenu était vraiment très limité. Récemment, j’ai du me replonger dans le sujet pour une intervention que je prépare chez un client, et j’ai pu constaté que la situation ne s’était pas améliorée : les sites sont carrément hors ligne, et la technologie UDDI est visiblement abandonnée.

Certains vendeurs de middleware qui se sont réorientés SOA en ont encore dans leur portfolio, mais ils ne doivent pas en vendre des masses. Evidemment, il est possible qu’UDDI ait eu plus de succès en entreprise que pour des annuaires de services sur internet, mais vu le nombre de retours négatifs, il y a peu de doute possible.

Pourquoi UDDI n’a-t-il pas eu le succès initialement escompté ? Après tout, cette technologie était censée être la pierre angulaire de la stratégie web services, au dessus de SOAP et de WSDL, le moyen technique qui allait permettre une réelle gouvernance SOA. L’expansion des services web allait nécessiter une solution de routage pour suivre les changements d’adresse, ainsi que les stratégies d’authentification, de robustesse, etc.

Une des explications les plus souvent avancées est la complexité d’UDDI. Pourtant, WS-Disco est plus simple, mais n’a pas percé non plus.

Personnellement, je pense que le problème était qu’UDDI essayait de solutionner un problème qui n’existait tout simplement pas. Même dans les systèmes informatiques les plus complexes, le nombre de services web n’excède pas quelques centaines, et sont en général répartis sur quelques serveurs. Les URLs changent rarement, de même que les settings de type sécurité ou routage. Quant aux WSDL, les changements de versions sont rarement parfaitement maîtrisés, alors imaginer qu’on pourrait avoir besoin d’un serveur spécifiquement pour les localiser dans leurs mouvements !

Une autre preuve de la trop grande complexité d’UDDI par rapport au besoin initial, surestimé, est que la technologie n’a tout simplement pas été remplacée par une autre : il n’y a rien, et les annuaires de services web sont tous des pages statiques. En interne, une page web sur l’intranet suffit, avec l’URL du serveur et le WSDL, plus une adresse mail du gestionnaire concerné. Les plus avancés pousseront jusqu’aux portions non-techniques du contrat de service comme les polices d’authentification, etc. Et tout cela suffit amplement.

Si ce n’est pas une belle illustration des approches KISS !

Tant que j’y suis, un petit retour sur un sujet connexe : l’utilisation de WADL pour décrire les services REST. Méfiance sur ce genre de normes, qui n’est pas compatible avec l’esprit du REST. La raison pour laquelle un service REST doit renvoyer dans sa grammaire des URI est qu’il doit être possible de revenir à une ressource de manière directe. Or, le principe d’un service REST est de supporter du discovery : l’enrichissement de ces URI doit être dynamique. Si un service REST change d’URL, le programme client qui le consomme doit pouvoir partir de l’URL unique du service et redescendre la structure pour aboutir à la ressource.

Lorsqu’on utilise un fichier WADL, on fixe par avance la structure de ce service REST, typiquement pour pouvoir créer des stubs par génération de code. Cette approche, qui est très paralysante car forçant un couplage, est déjà un problème dans les grammaires SOAP, pourtant très rigides. L’adopter pour des grammaires REST, dont le but est justement de s’éloigner de cette lourdeur, est contre nature.

Posted in Méthodologie | Tagged | 3 Comments

Comprendre Async

Il y a de très bons articles à propos d’Async sur le blog de Stéphanie Hertrich de Microsoft (ici pour expliquer les problèmes sur les actions longues, et pour une explication plus globale de l’asynchronisme et du parallélisme), ainsi que sur de nombreux autres blogs / sites, mais ce n’est pas facile de trouver une explication pour débutant absolu. Si comme moi, vous avez un peu de mal avec la marche conceptuelle pour comprendre Async, j’espère que les notes ci-dessous vous aideront à avancer.

AVERTISSEMENT : les bouts de code ci-dessous sont des tâtonnements, pour comprendre par l’erreur les principes d’Async. Ne vous étonnez donc pas si vous voyez des horreurs, c’est fait pour…

Warning CS1998

Le code ci-dessous lance un avertissement :

image

image

D’après ce que je comprends, le compilateur émet un warning en nous expliquant en gros que ce n’est pas la peine de mettre un mot clé async vu qu’on ne fait pas d’await dans la fonction, et que du coup, le fonctionnement final est équivalent à un fonctionnement synchrone. On constate d’ailleurs ce comportement si on exécute tout de même :

image

Le code est donc complètement équivalent à celui-ci :

image

Retour des fonctions async

Une autre chose qui permet de comprendre un peu mieux le principe est qu’une fonction async ne peut pas retourner directement une valeur. Dans le code ci-dessous, ce n’est plus un avertissement, mais bien une erreur de compilation qu’on obtient :

image

On a encore l’avertissement précédent, ainsi qu’un second sur l’appel de la fonction marquée comme “async” dans la méthode Main :

image

Le plus riche d’enseignement est l’erreur : une fonction marquée async doit renvoyer void ou bien une tâche.

Note : je fais exprès de dire “marquée async” et non pas “une fonction async”, car d’après ce que je comprends pour l’instant, le mot clé ne transforme pas la méthode en méthode asynchrone : il indique que des traitements dans cette fonction vont l’être. Ce qui rend une méthode asynchrone, c’est la présence des await dans son corps)

Pour en revenir au retour, essayons de comprendre à quoi peut correspondre cette Task (simple ou générique) en retour. On imagine que le type générique est peut-être le type du retour éventuellement attendu par les appelants de cette fonction.

Retour d’une Task<T>

Essayons donc l’exemple suivant :

image

On n’a plus d’erreur de compilation, car la signature est Task<string>. Remarquez tout de même au passage que le return renvoie res, qui est déclarée dans le corps comme une variable de type string, et pas Task<string>. Voilà donc un truc qu’Async fait : il y a eu modification du compilateur pour qu’en présence du mot clé aysnc dans la déclaration de la fonction, le return d’un T compile avec une signature renvoyant un Task<T>. Intéressant…

Evidemment, si on n’avait pas async dans la déclaration, on aurait une erreur de compilation sur le return :

image

Au passage, il y aurait d’ailleurs une seconde erreur, qui est le pendant du warning CS1998, parce qu’on utilise un await dans une fonction qui n’est pas marquée “async” :

image

Mais revenons à nos moutons… Si on remet l’async, le code plus haut compile, mais tout de même avec un avertissement, ce coup-ci sur l’appel de la fonction LancementAsync depuis la méthode Main :

image

Si on lance l’application telle quelle, le comportement change. On a le comportement asynchrone du Main, en ce sens où il n’attend pas la fin de la fonction :

image

Utilisation du retour

Effectivement, dans notre cas, on a créé la fonction avec un mot clé await, qui déclenche le comportement asynchrone du traitement d’exécution. Plaçons des traces dans le code de la fonction Executer() comme ceci :

image

On constate effectivement que le lancement est bien réalisé, mais la fin jamais atteinte. Async doit lancer une tâche quelque part dans un thread qui n’empêche pas l’application de se terminer lorsque le thread principal de traitement qui exécute Main arrive à la fin de son travail :

image

Du coup, si on veut vraiment utiliser le résultat de la fonction LancementAsync (pour l’afficher, par exemple), on peut imaginer ce genre de code, qui utilise la tâche retournée comme tâche de continuité de traitement :

image

Ce coup-ci, tout compile, mais…

On ne voit toujours rien !

En effet, quand on lance, on ne voit pas la fin de la fonction Executer, ni le retour du résultat pour autant :

image

Mais pour le coup, il y a de bonnes chances que ce soit juste parce que, Main étant arrivé à la fin (justement parce que l’appel est désormais non bloquant), le processus est interrompu. Faisons quelque chose de bien pourri pour retarder sa fermeture, juste pour confirmer :

image

Au risque de la répétition, je préfère rappeler que tous les bouts de code ici présents ne sont que des essais pour essayer de comprendre PAR L’ERREUR comment fonctionne Async. Donc, ne prenez évidemment pas ce code pour quelque chose de propre ou même simplement utilisable : ce n’est pas le cas !

Là, on commence à avoir quelque chose :

image

L’affichage est un peu bizarre : c’est typiquement le ToString sur une classe qui ne le surcharge pas, et pas l’affichage de notre chaîne en retour… Voyons voir ce qui peut se passer :

image

Visual Studio nous aide : le ContinueWith attend un délégué de type Action, non pas sur un string, mais sur un Task<string>. Il doit bien y avoir quelque chose là-dedans qui nous permet de retrouver notre résultat, comme une  propriété Value ou quelque chose approchant…

Et bien, c’est Result, tout simplement :

image

Ce qui nous donne le résultat attendu :

image

L’intérêt

Arrivé là, on a déjà une première idée de l’intérêt de tout cela, en ajoutant juste une trace qui permet de mieux voir ce qui se passe :

image

Ce qui simule bien ce qui nous intéresse, à savoir continuer le parcours d’un programme (d’une GUI, d’une exécution centrale, d’une boucle d’attente de commande, etc.) tout en ayant une autre partie du programme qui s’exécute, avec un traitement postérieur si nécessaire :

image

Et si on veut attendre le résultat ?

Changeons encore une fois le code, pour ce coup-ci remplir une variable avec le résultat au lieu de l’afficher dans la console :

image

Conformément à ce qu’on attend, on se retrouve avec un premier affichage avec la valeur initiale et un second, lorsque le temps suffisant est écoulé, avec la valeur affectée par l’exécution :

image

Question : dans du vrai code, on ne sait pas à l’avance combien une méthode va prendre de temps, et on peut donc se retrouver avec un endroit (typiquement, notre second affichage de res) où on souhaite utiliser tout de suite la chaîne si elle a été valuée, et sinon bloquer en attendant qu’elle le soit. Comment peut-on réaliser ceci ?

Evidemment, on peut travailler de manière sale (vu comment on se vautre dans le code pourri sur ce post, allons-y gaiement) :

image

Ca n’est évidemment pas la bonne façon de faire, car on boucle en bloquant le thread (et en plus sans donner à un autre thread la main), mais ça a au moins le mérite de supprimer le besoin du Thread.Sleep sur 3000 millisecondes, et on peut constater par le rajouter de l’affichage final de l’heure qu’on est sur les 2000 millisecondes du traitement Executer() :

image

Fin en queue de poisson

Vous aimeriez connaître la solution ? Et bien moi aussi, mais je ne l’ai pas ! Donc rendez-vous au prochain épisode pour la suite. Sachant que, si ça se trouve, la bonne façon de faire est de rester dans le mode ContinueWith qu’on a trouvé au début… Evidemment, les commentaires sont plus que jamais bienvenus pour me faire comprendre Async Sourire. En espérant que cet article avec une approche différente (apprendre par l’erreur) vous aura plus aidé qu’embrouillé. Dans le dernier cas… caveat emptor !

Posted in .NET | Tagged | 8 Comments

Authentification Windows de bout en bout (IE > IIS > ASP.NET > WCF > C# > SQLServer)

Le but

Au programme de ce post, une explication pas-à-pas de tout ce qu’il faut mettre en place pour faire circuler une authentification Windows depuis un navigateur jusqu’à la base de données, en passant par tous les serveurs d’une application n-tier. L’idée est qu’on puisse partir d’une authentification d’un utilisateur sur une machine cliente, et propager cette authentification de manière automatique, et ce jusqu’à la base de données, sur laquelle on sera authentifié sous l’identité de cette personne.

image

Bref, le but est de vous faire voir toutes les étapes d’impersonification, authentification, sécurité, etc. sur chacun des tiers concernés.

Mise en place de la solution

Nous allons créer une solution Visual Studio qui contiendra tous les tiers qui nous intéressent. Il s’agit de trois projets vides, un de type Librairie de classes, et deux autres de type Application Web ASP.NET Vide.

image

Dans le site web, on rajoute une simple page ASPX. Dans la librairie métier, on rajoute une classe qu’on nommera Dossier, et dans le service, une entrée WCF nommée Service.svc. On ajoute une référence du service au métier, et une référence de service du site web vers le service, ce qui nous donne au final ceci dans l’explorateur de solutions :

image

Pour l’instant, nous n’avons créé que le squelette, qui correspond à l’organisation suivante :

image

Astuce pour les projets web

Si vous vous êtes déjà battus avec les droits pour créer un répertoire virtuel directement depuis Visual Studio lors de la création d’un projet, ou bien si vous avez pesté comme beaucoup sur l’absence de cet onglet magique qu’était “Partage Web” dans les nouvelles versions de Windows, si le mode de création d’une application web depuis la console IIS vous donne des envies de sauter par la fenêtre, voici enfin la solution propre et anti-prise de tête pour créer un répertoire virtuel à l’endroit où vous souhaitez, sans avoir de problème de droits ou de prise en compte par Visual Studio (et sans tomber sur l’erreur due à la mauvaise version de .NET dans le pool d’application choisi au hasard)…

Dans Visual Studio, créez un projet de type Application Web ASP.NET Vide : comme ça, il vous laisse choisir l’endroit qui va bien pour votre TFS, etc. (faites bien un Nouveau Projet, et par un Nouveau Site Web) :

image

Ensuite, allez dans les propriétés du projet, sur l’onglet Web, et choisissez Utiliser le serveur Web IIS local plutôt que le choix par défaut qui utilise Cassini (Cher Microsoft, il faudra d’ailleurs penser à le virer au profit de IIS Express), puis cliquez sur le bouton magique “Créer un répertoire virtuel”.

image

Si tout se passe bien, et c’est précisément ce qui est bien avec cette méthode : c’est qu’elle se passe toujours bien, vous vous retrouvez avec une configuration qui vous permet de déboguer, où le répertoire virtuel est bien configuré, sur la bonne version de .NET, et avec le code au non endroit pour votre gestionnaire de code source. Plus de problème de droits sur les fichiers non accessibles par l’utilisateur, etc. La seule condition est de démarrer Visual Studio en mode Administrateur… mais c’est une faible concession au confort apporté par cette solution.

Réalisez cette opération pour les deux sites web que nous avons créés : Service et SiteWeb. J’adore cette fenêtre :

image

Après le temps cumulé perdu à régler des problèmes de répertoires virtuels / droits / versions de .NET / site web bloqué, c’est un plaisir de la voir s’afficher Sourire.

Côté base de données

Je ne vais pas m’attarder sur la base de données : déjà parce que ce n’est pas là qu’il y a le plus de difficulté, mais aussi parce que ce n’est pas ma spécialité et je ne veux pas risquer de raconter des bêtises…

En gros, il faut commencer par déclarer les utilisateurs Windows au niveau de l’instance SQLServer elle-même, dans la section Sécurité / Connexions (attention à ne pas vous tromper avec Sécurité / Utilisateurs de la base de données) :

image

Un clic-droit, puis “Nouvelle connexion”, et vous pourrez utiliser le bouton Rechercher de l’interface ci-dessous (je ne capture qu’une partie) pour retrouver l’utilisateur Windows qui vous intéresse :

image

Une fois cette étape réalisée, vous pourrez passer à la suite, dont on parlait précédemment, et ajouter un nouvel utilisateur dans la base de données cible (section Sécurité / Utilisateurs dans Management Studio) :

image

Là encore, l’interface vous permettra de retrouver facilement la connexion à associer :

image

Il faut ensuite régler tous les droits comme il faut pour que l’utilisateur créé ait bien les droits sur les tables de données qu’on va utiliser. Comme je vous le disais, je ne connais pas bien toutes ces histoires de droits sur les schémas / sur les entités / sur les utilisateurs, donc on va faire au plus simple, plutôt que de risquer de vous embrouiller. En créant la base de données en tant que l’utilisateur cible, il est automatiquement db-owner, ce qui simplifie considérablement les choses (attention, ce n’est bien sûr pas la pratique la plus propre du point de vue de la sécurité) :

image

Pour notre exemple, on crée une simple table de test pour recevoir des entrées, et valider ainsi la bonne authentification de bout en bout :

image

Avertissement

Avant de continuer, il faut préciser que le fait de propager l’authentification du client à la base de données n’est pas nécessairement une bonne idée. Ca l’est si vous avez un nombre limité et peu fluctuant d’utilisateurs et de droits. Dans le cas contraire, la gestion deviendra vite un casse-tête. Mais si cette condition est établie, les avantages existent également : les droits sont gérés au plus bas, donc la sécurité est maximale. Les capacités d’audits des opérations sont optimales, car vous pouvez suivre à la commande SQL près qui fait quoi et d’où vient telle ou telle entrée. Pour qui s’est déjà retrouvé dans l’impasse en observant qu’une ligne problématique avait été créé par l’utilisateur générique d’une application pilotée par quelques centaines d’utilisateurs physiques, c’est un énorme progrès.

Bref, dans tout ce qui touche au bancaire, ou avec des besoins étendus d’accountability, ça peut être intéressant, mais ne vous amusez pas à mettre ça en place pour votre prochaine boutique web Clignement d'œil.

Configuration du lien entre le business layer et la base de données

Cette partie-là est simplissime : insérez simplement “Integrated Security=True” dans votre chaîne de connexion, et l’identité de l’appelant sera celle présentée à la base de données.

Gestion de l’authentification dans le Business Layer

Du côté du métier, l’idée est bien sûr de pouvoir utiliser le système de Principal proposé par la BCL dans System.Security. En plus de la chaîne de connexion (normalement bien au chaud dans le fichier de configuration, mais on la met dans le code pour l’exemple), on voit comment utiliser le principal si on en a besoin, et surtout comment sécuriser la fonction contre tout accès non authentifié :

using System;
using System.Data.SqlClient;
using System.Threading;
using System.Security.Permissions;

namespace Business
{
    public class Dossier
    {
        [PrincipalPermission(SecurityAction.Demand, Authenticated=true)]
        public static void AjouterDossier(string Libelle)
        {
            Libelle += string.Format(" (créé par %s)", Thread.CurrentPrincipal.Identity.Name);

            using (SqlConnection conn = new SqlConnection(@"Data Source=ANTARES\SQLExpress;"
                + "Initial Catalog=AuthentificationBoutEnBout;Integrated Security=True"))
            using (SqlCommand comm = new SqlCommand(
                "INSERT INTO TEST (code, libelle) VALUES (@code, @lb)", conn))
            {
                comm.Parameters.AddWithValue("code", Guid.NewGuid().ToString("N"));
                conn.Open();
                comm.ExecuteNonQuery();
            }
        }
    }
}

 

Ce qui est particulièrement intéressant avec l’attribut PrincipalPermission, c’est qu’on peut jouer avec les rôles, et mettre ainsi en place une sécurité déclarative, qui garantit en plus que, quel que soit le code qu’on rajoute dans la fonction, on obtiendra de toute façon une SecurityException si on n’a pas les habilitations pour l’exécuter :

[PrincipalPermission(SecurityAction.Demand, Role = "ControleurFinancier")]
[PrincipalPermission(SecurityAction.Deny, Name = "domaine\\Johnny-LaFouine")]

 

Impersonification dans WCF

Bon, c’est là que ça devient un peu plus le bazar. C’est bien sympa de dire dans le métier qu’on se base sur le Principal et qu’on gère la sécurité, mais à un moment, il va bien falloir l’affecter, cette authentification. Et en WCF, c’est loin d’être aussi simple qu’en ASMX où un bon vieux <identity impersonate=”true”/> dans le fichier web.config faisait l’affaire.

La première chose à faire est de mettre le mode d’authentification en Windows dans le fichier web.config du site Service :

image

Dans la console IIS, on va changer les modes d’authentification :

image

Plus besoin de l’anonyme, mais on active le mode Windows :

image

Ca devrait suffire, mais accrochez-vous : si vous lancez ainsi le service, vous aurez une erreur vous expliquant qu’il vous manque le mode anonyme, dont on cherche justement à se débarrasser (désolé pour la version anglaise) :

The authentication schemes configured on the host (‘IntegratedWindowsAuthentication’) do not allow those configured on the binding ‘BasicHttpBinding’ (‘Anonymous’). Please ensure that the SecurityMode is set to Transport or TransportCredentialOnly. Additionally, this may be resolved by changing the authentication schemes for this application through the IIS management tool, through the ServiceHost.Authentication.AuthenticationSchemes property, in the application configuration file at the <serviceAuthenticationManager> element, by updating the ClientCredentialType property on the binding, or by adjusting the AuthenticationScheme property on the HttpTransportBindingElement.

La raison est que le endpoint MEX a besoin par défaut du mode anonyme. On va régler le problème en lui apposant le même binding que pour le service, avec l’authentification Windows en mode transport :

image

Enfin, on peut écrire notre implémentation de service, avec l’attribut qu’il faut pour pouvoir mettre en place l’impersonification :

using System.ServiceModel;
using Business;

namespace Service
{
    public class Service : IService
    {
        [OperationBehavior(Impersonation=ImpersonationOption.Required)]
        public void CreerDossier(string Libelle)
        {
            Dossier.AjouterDossier(Libelle);
        }
    }
}

 

Récupération de l’authentification dans le site web

Pour le reste, nous allons commencer par changer l’authentification pour le site web dans la console IIS, exactement de la même manière que pour le site de service, à part que nous ajoutons également l’emprunt d’identité ASP.NET :

image

Dans le fichier web.config du site WebForms, on voit le paramètre d’impersonification qui a été rajouté (quand vous revenez à Visual Studio, il propose de rafraîchir le fichier pour montrer les modifications effectuées par l’outil externe, à savoir la console IIS dans notre cas) :

image

Attention, encore quelque chose à connaître pour que ça fonctionne bien : l’impersonification refuse de fonctionner dans le mode pipeline ASP.NET intégré de IIS 7. Il faudra donc choisir pour ce répertoire virtuel l’exécution sur un autre pool d’application, comme par exemple ASP.NET v4.0 Classic :

image

Le point important étant que le pool d’application soit bien configuré en mode Classic pour le Mode pipeline géré :

image

Il faut également penser à rajouter le mode d’authentification qui va bien, dans ce site web applicatif WebForms, comme dans le site de service :

image

Mise en place du proxy dans le site web

L’étape suivante consiste à s’assurer que l’instance du proxy de service WCF que nous allons placer dans une page ASPX appellera correctement le service WCF, en tout cas d’une façon permettant l’envoi et la prise en compte correcte de l’authentification Windows. On devrait pouvoir enfin utiliser notre instance de proxy pour appeler le service web, là ? Oui… mais pas aussi simplement qu’on pourrait le croire. Il va encore falloir faire un petit ajout pour gérer la sécurité Windows :

using System;
using System.ServiceModel;
using System.Security.Principal;
using System.Net;

namespace SiteWeb
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
            basicHttpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
            basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;

            EndpointAddress endpoint = new EndpointAddress("http://localhost/Service/Service.svc");

            Proxy.ServiceClient client = new Proxy.ServiceClient(basicHttpBinding, endpoint);
            client.ClientCredentials.Windows.AllowedImpersonationLevel 
                = TokenImpersonationLevel.Impersonation;
            client.ChannelFactory.Credentials.Windows.ClientCredential
                = CredentialCache.DefaultNetworkCredentials;

            client.CreerDossier("Coucou");
        }
    }
}

Arrivés là, vous croyez peut-être qu’on est au bout de nos peines ? Eh bien, non ! Il va déjà falloir refaire la référence de service, car on l’avait réalisée au début alors que le service n’utilisait pas l’authentification Windows. Pour une fois, cependant, les choses sont simples : lorsqu’on recrée la référence de service, le fichier web.config du site web est mis au propre, avec le bon more de transport Windows pour les crédentiels dans le binding :

image

Activation éventuelle de l’authentification intégrée dans le navigateur

Allez, il reste encore un tiers à gérer, mais c’est le plus simple : le client. Si vous utilisez Internet Explorer, tout est déjà fait pour vous, et l’authentification de votre session Windows sera automatiquement envoyé sur demande de IIS.

Si vous utilisez Firefox, il faudra faire un tour dans about:config et chercher NTLM, pour activer la bonne option.

Conclusion

Il faut un vache de bout de temps pour tout mettre en place, mais une fois passés tous ces obstacles, vous obtenez une authentification de bout en bout, qui vous permet, dans un LAN, de déléguer complètement le processus d’authentification à Windows, tout en vous donnant la possibilité à n’importe quel endroit de votre code C# de gérer la sécurité de manière déclarative, et sur la base de données d’avoir des audits ultra-fins.

En espérant que ça serve, et que ça évite à quelqu’un les deux heures de recherche pour arriver à faire fonctionner tout ensemble Sourire

Posted in .NET | Tagged , | 2 Comments

A quoi peut bien servir une lambda x=>x ?

A quoi peut bien servir le genre de code ci-dessous ?

ConvertAll<A>(x => x)

A priori, faire une expression lambda de type “identité”, ça parait un peu capillotracté. La première idée qui vient est que ça pourrait servir pour gérer les transformations dans la hiérarchie d’héritage des classes. Par exemple :

class A
{ }

class B : A
{ }

class Program
{
    static void Main(string[] args)
    {
        List<B> liste = new List<B>();
        IEnumerable<A> resultat = liste.ConvertAll<A>(x => x);
    }
}

Mais dans ce genre de situation, il n’y a pas besoin de convertir. La covariance de IEnumerable fait que ça fonctionne correctement si on écrit simplement ceci :

static void Main(string[] args)
{
    List<B> liste = new List<B>();
    IEnumerable<A> resultat = liste;
}

Un cas qui peut expliquer ce recours à une lambda de type “x => x” est lorsque ce n’est pas une relation d’héritage, mais de conversion entre les différents types, qui existe. A l’inverse de la covariance ci-dessus, le code ci-dessous ne compile pas :

class A
{
    public static implicit operator A(B objet)
    {
        return new A();
    }
}

class B
{ }

class Program
{
    static void Main(string[] args)
    {
        List<B> liste = new List<B>();
        IEnumerable<A> resultat = liste;
    }

}

Pour que la compilation passe, il faut écrire :

static void Main(string[] args)
{
    List<B> liste = new List<B>();
    IEnumerable<A> resultat = liste.ConvertAll<A>(x => x);
}

Ou, pour aider les performances :

static void Main(string[] args)
{
    List<B> liste = new List<B>();
    IEnumerable<A> resultat = liste.Select<B, A>(x => x);
}

 

Il est à noter que seule la covariance rend nécessaire cette écriture. Une boucle foreach n’aurait pas posé de problème, car c’est une véritable conversion qui se passe. Ainsi, si on écrit le code ci-dessous, il n’y aura pas de problème de compilation, ni dans un cas ni dans l’autre :

foreach (A instance in liste)

Ce petit quizz a rapporté un paquet de gâteaux à Stéphane B. Félicitations ! Sourire

Posted in C# | Tagged | Leave a comment

Le nouvel Office est “On Air”

Le Microsoft Office nouveau est arrivé :

image

Au menu, principalement : convergence entre les utilisations “traditionnelles” (bureau) et les versions Office 365, ainsi que le contenu de SkyDrive. Plus d’infos sur le lien associé (cliquez sur la bannière) ou sur http://www.blogoffice.fr/professionnels-de-l-it.

image

Bref, Microsoft joue à fond sa carte “rouleau compresseur” : pas de version Office 2012 annoncée pour Mac, tarifs agressifs pour couper l’herbe sous le pied de Google Apps, etc. Historiquement, Office a toujours été une des raisons mises en avant par les DSI pour rester sur des PC Windows. Avec Windows 8 qui arrive et l’interface associée unifiée sur les PC, les tablettes et les smartphones, si Microsoft réussit à faire le même hold-up, le rapport de force dans les tablettes pourrait bien changer radicalement !

Ce qui m’arrangerait fortement, parce que franchement, C# est beaucoup plus sympa à programmer qu’ObjectiveC…

Posted in Veille | Tagged | Leave a comment

Dictionary<TKey, TValue> et IEquatable<TKey>

Aujourd’hui, un article pour expliquer un comportement un peu spécial dans les dictionnaires génériques de .NET. Imaginons que, pour une fonctionnalité particulière, vous ayez besoin d’un dictionnaire qui utilise une clé composite, dont la structure contient plusieurs chaînes.

Souvent, il est possible d’utiliser un séparateur et de revenir à une seule chaîne, mais ce n’est pas toujours le cas. On pourrait utiliser également un tuple, mais supposons pour notre étude que vous ayez besoin d’un nombre non déterminé à l’avance. Nous pourrions donc écrire le code de test suivant, avec dans l’exemple un dictionnaire avec la clé composite basée sur une liste de chaînes, et la valeur étant un decimal :

using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestDicoIEquatable
{
    [TestClass]
    public class TestCleListe
    {
        private Dictionary<List<string>, decimal> Dico = new Dictionary<List<string>, decimal>();

        [TestInitialize]
        public void Initialisation()
        {
            List<string> Cle = new List<string>() { "2012", "A" };
            Dico.Add(Cle, 2501.56M);
        }

        [TestMethod]
        public void TestCleSimilaire()
        {
            List<string> Cle = new List<string>() { "2012", "A" };
            Assert.AreEqual(2501.56M, Dico[Cle]);
        }
    }
}

Ce code plante avec l’erreur suivante :

La méthode de test TestDicoIEquatable.TestCleListe.TestCleSimilaire a levé une exception :
System.Collections.Generic.KeyNotFoundException: La clé donnée était absente du dictionnaire.

Que se passe-t-il ? Il semblerait que le fait de reconstruire une clé, même si son contenu est similaire, ne suffise pas à ce que le dictionnaire soit capable de la localiser. Il n’y a pas égalité entre les deux versions de la clé de notre test. A supposer que nous transformions les List<string> en string[], le problème reste le même.

Un coup d’oeil dans la documentation nous apprend, je cite, que :

Dictionary<TKey, TValue> requiert l’implémentation d’une égalité pour déterminer si les clés sont égales.Vous pouvez spécifier une implémentation de l’interface générique IEqualityComparer<T> en utilisant un constructeur qui accepte un paramètre comparer ; si vous ne spécifiez pas d’implémentation, le comparateur générique d’égalité par défaut EqualityComparer<T>.Default est utilisé.Si le type TKey implémente l’interface générique System.IEquatable<T>, le comparateur d’égalité par défaut utilise cette implémentation.

Voilà a priori la raison de l’échec du test : les classes que nous utilisions n’implémentaient pas IEquatable<TKey>, et l’implémentation par défaut du EqualityComparer<T> ne devait pas faire l’affaire. Qu’à cela ne tienne, nous allons donc implémenter ce qu’il faut dans une classe dédiée pour TKey :

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestDicoIEquatable
{
    [TestClass]
    public class TestCleIEquatable
    {
        public class CleComposite : IEquatable<CleComposite>
        {
            private string[] _Valeurs;

            public CleComposite(string[] valeurs)
            {
                this._Valeurs = valeurs;
            }

            public bool Equals(CleComposite other)
            {
                if (other._Valeurs.Length != this._Valeurs.Length) return false;
                for (int i = 0; i < other._Valeurs.Length; i++)
                    if (other._Valeurs[i] != this._Valeurs[i]) return false;
                return true;
            }
        }

        private Dictionary<CleComposite, decimal> Dico = new Dictionary<CleComposite, decimal>();

        [TestInitialize]
        public void Initialisation()
        {
            CleComposite Cle = new CleComposite(new string[] { "2012", "A" });
            Dico.Add(Cle, 2501.56M);
        }

        [TestMethod]
        public void TestCleSimilaire()
        {
            CleComposite Cle = new CleComposite(new string[] { "2012", "A" });
            Assert.AreEqual(2501.56M, Dico[Cle]);
        }
    }
}

Surprise : ça ne marche toujours pas, et on a toujours la même erreur comme quoi la clé est absente du dictionnaire !

Après un peu de recherche (et en particulier, la lecture de cet excellent article de JaredPar), la solution est d’implémenter la surcharge Object.Equals ainsi bien sûr que celle de Object.GetHashCode. Dans cet exemple, je pointe simplement Object.Equals sur IEquatable<T>.Equals, et j’implémente Object.GetHashCode avec une méthode simplissime, à savoir prendre le hash de la concaténation de toutes les chaînes représentant les valeurs de la clé composite. Au final, le code suivant fonctionne :

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestDicoIEquatable
{
    [TestClass]
    public class TestCleIEquatable
    {
        public class CleComposite : IEquatable<CleComposite>
        {
            private string[] _Valeurs;

            public CleComposite(string[] valeurs)
            {
                this._Valeurs = valeurs;
            }

            public bool Equals(CleComposite other)
            {
                if (other._Valeurs.Length != this._Valeurs.Length) return false;
                for (int i = 0; i < other._Valeurs.Length; i++)
                    if (other._Valeurs[i] != this._Valeurs[i]) return false;
                return true;
            }

            public override bool Equals(object obj)
            {
                return this.Equals(obj as CleComposite);
            }

            public override int GetHashCode()
            {
                return string.Join(string.Empty, this._Valeurs).GetHashCode();
            }
        }

        private Dictionary<CleComposite, decimal> Dico = new Dictionary<CleComposite, decimal>();

        [TestInitialize]
        public void Initialisation()
        {
            CleComposite Cle = new CleComposite(new string[] { "2012", "A" });
            Dico.Add(Cle, 2501.56M);
        }

        [TestMethod]
        public void TestCleSimilaire()
        {
            CleComposite Cle = new CleComposite(new string[] { "2012", "A" });
            Assert.AreEqual(2501.56M, Dico[Cle]);
        }
    }
}

A noter que la documentation spécifie bien ce besoin d’implémentation, mais malheureusement pas dans la page de Dictionary<TKey, TValue>, ce qui est un peu gênant.

En espérant que ça serve à quelqu’un !

Posted in .NET, C# | Tagged | 3 Comments

Concours “Rien à coder”

Si vous avez pris de bonnes résolutions pour les vacances et décider d’apprendre quelques nouvelles technos Microsoft, voici quelques ressources pour commencer en Windows 8.

image

Et si vous voulez apprendre de manière un peu plus ludique (et essayer de gagner des lots), il y a le concours “J’en ai rien à coder” qui reprend.

image

Bref, tout ce qu’il faut pour commencer à programmer pour Windows 8, et être parmi les premiers à publier une application…

Posted in .NET | Leave a comment

Quelques remarques sur la gestion des données temporelles

Je viens de lire cet excellent article sur des hypothèses fausses (parfois de manière étonnante) que les programmeurs font sur le temps et sa gestion informatisée. Quelques-unes sont très simples, comme le fait que Février n’a pas toujours 28 jours, mais d’autres sont plus complexes.

Saviez-vous par exemple que tous les jours ne durent pas 24 heures ?

Cet article recense plein de problèmes possibles sur la gestion du temps, mais à sa lecture, je me disais qu’il ne proposait pas beaucoup de solution sur des exemples concrets. Du coup, je me disais que ça vaudrait le coup de lister les astuces et méthodes pour éviter ces problèmes. Je commence la liste :

Utiliser DateTime et TimeSpan

… et ce de manière correcte, bien entendu. On commence par un exemple bateau de la mauvaise façon de calculer le nombre de jours entre deux dates :

[TestMethod]
public void MauvaiseFacon()
{
    DateTime now = DateTime.Now;
    DateTime then = new DateTime(1974, 12, 25);

    int nbJours = 365 * (now.Year - then.Year)
        + 30 * (now.Month - then.Month)
        + (now.Day - then.Day);
    Assert.AreEqual(13692, nbJours);
}

Il faut bien entendu tenir compte des années bissextiles, mais saviez-vous par exemple que le mode de calcul de celle-ci est le suivant :

  1. L’année doit être un multiple de 4
  2. Elle ne doit pas être un multiple de 100
  3. Sauf dans le cas où elle est un multiple de 400

Afin de s’éviter toute cette complexité, le plus simple est donc bien sûr d’écrire plutôt le code comme ceci :

[TestMethod]
public void FaconCorrecte()
{
    DateTime now = DateTime.Now;
    DateTime then = new DateTime(1974, 12, 25);

    TimeSpan ecart = now - then;
    int nbJours = ecart.Days;
    Assert.AreEqual(13692, nbJours);
}

Le résultat est le suivant :

image

Ceci est l’occasion de faire la remarque que ces tests ne sont pas du tout indépendants du contexte, car ils seront faux dès demain, où le nombre de jours a augmenté. C’est pour montrer ce problème que j’ai pris une des deux dates de manière non fixe.

Comprendre la différence entre date locale et date UTC

Dans les API de .NET, il est également possible de gérer les dates et heures UTC. En gros, l’idée du temps universel est de dire qu’un même moment correspond à des heures différentes d’un fuseau horaire à l’autre.

Par exemple, à ce jour en France, nous sommes à l’heure d’été, et donc 2 heures après le temps universel :

[TestMethod]
public void TestUTC()
{
    DateTime now = DateTime.Now;
    DateTime nowUTC = DateTime.UtcNow;

    TimeSpan ecart = now - nowUTC;
    Assert.AreEqual(2, ecart.Hours);
}

Pour bien comprendre toute la richesse de la gestion du temps en .NET, il faut jeter un œil à la documentation de System.Globalization.DateTimeFormatInfo : 20 méthodes, 28 propriétés, ça peut paraitre beaucoup pour juste gérer les formats de date, mais on a accès à l’ère, au type de calendrier (Julien, Grégorien, etc.) avec des classes JulianCalendar et GregorianCalendar dédiée.

Même dans le calendrier Grégorien, on a la possibilité de choisir un type particulier :

image

Du coup, attention à bien choisir le type précis dont vous avez besoin, car l’API a une énorme richesse, et il y a une bonne raison pour cela : c’est que la gestion du temps est, contrairement à ce qu’on peut croire, un sujet extrêmement complexe. En tapant DateTimeOffset dans un moteur de recherche, vous trouverez plein de bonnes ressources, en particulier sur cet excellent site qu’est StackOverflow.

Synchroniser les horloges

Un autre cas appliqué où il est nécessaire de très bien gérer le temps est dans les Message Oriented Middleware. Lorsque des messages asynchrones sont échangés sur un SI, un timestamp est systématiquement appliqué pour régler les problèmes de durée de vie, etc. Or, ce timestamp est nécessairement appliqué sur la machine émettrice, et lue sur les machines destinataires.

Ne pas avoir la même heure sur les différentes machines composant un ESB est donc absolument essentiel, et ce de manière très précise : quelques minutes suffisent à perdre des messages. Heureusement, la plupart des OS permettent de synchroniser très facilement une horloge sur celle d’un autre ordinateur. Par exemple, en Windows :

net time \\machine /set

Se baser sur un serveur de temps

Pour aller encore plus loin, l’idéal est de synchroniser toutes les machines du domaine (ce qu’on peut faire avec net time), voire même synchroniser toutes les machines du monde, ce qui est la raison pour laquelle les horloges atomiques de référence existent.

Dans le cas de Windows, c’est visiblement le cas par défaut, en tout cas sur Windows 7 Ultimate 64 bits :

image

Conclusion

La gestion du temps est bien plus complexe que ce qu’on peut imaginer, et même dans les cas où on pense être sur un calcul simple, il faut bien utiliser les outils dédiés qui suivent les normes.

Posted in C# | 1 Comment

Et si on arrêtait de croire que la sécurité est une affaire d’experts ?

Juste un petit billet rapide en réaction à un article du Monde Informatique, sur lequel on retrouve un des multiples exemples de surestimation des difficultés d’une attaque informatique :

Marc Stevens, qui appartient au groupe de chercheurs en cryptologie du CWI, a analysé le certificat Microsoft corrompu utilisé par les auteurs de Flame. Celui-ci a découvert que les auteurs avaient mis au point un système d’attaque par collision MD5 différent de celui qu’il avait imaginé avec ses collègues en 2008. Selon lui, « seuls des crypto-analystes de haut niveau sont en mesure de concevoir une telle variante ».

Juste pour mettre les choses au point :

  1. La méthode de hash MD5 est considérée comme obsolète depuis longtemps. Même son remplaçant initial SHA1 est désormais déconseillé par le gouvernement américain.
  2. Les premiers travaux sur les générateurs de collisions de hash MD5 datent de 2005 (Chang & Yu, Université de Shandong)
  3. En me basant sur la description de Selinger, je fais créer une collision MD5 à mes étudiants de Master sur un TD de deux heures, et la grande majorité arrive au bout de cet exercice. Je précise qu’il s’agit en général de leur premier cours sur la sécurité informatique.

Bref, ce genre de déclaration (“seuls des crypto-analystes de haut niveau sont en mesure de concevoir une telle variante”) me parait abusive. Ou bien l’auteur considère qu’il faut être spécialiste pour appliquer une méthode connue depuis sept ans, et personnellement, je pense qu’il est alors dans l’erreur complète. Ou bien c’est effectivement la variante qui est particulièrement difficile, et dans ce cas, il faut expliquer quelle est cette variante, et ne pas simplement parler de collisions MD5.

C’est un peu le même phénomène qu’on constate lorsque les chercheurs en sécurité parlent des “Advanced Persistent Threats”. Pourquoi utiliser ce qualificatif “Advanced” alors que les failles décrites sont presque toujours une simple combinaison d’attaques connues, voire même d’un mélange de méthodes d’ingénierie sociale et de failles, si ce n’est par vanité ?

Tout ça donne l’impression qu’il faut épater l’usager, lui faire bien comprendre que la sécurité est quelque chose de très compliqué, etc. Or, ce n’est pas le cas, car la plupart des contre-mesures sont basées sur du pur sens commun et le respect de bonnes pratiques… Si tous les usagers lambda étaient conscients des règles de bases de l’ingénierie sociale (ne jamais donner ou modifier un mot de passe suite à un coup de téléphone) et de la prudence (par exemple, juste savoir que 66% des clés USB perdues contiennent des malwares), les pirates seraient obligés de mettre en place des méthodes beaucoup plus “Advanced”…

Or, quand on cherche à faire peur, c’est principalement pour faire vendre Sourire

Posted in Sécurité | Tagged | Leave a comment