locked
Authentification dans wf4 avec appfabric RRS feed

  • Question

  • Ca fait un moment que je bosse sur wf et wcf, mais là je sèche.

    J'utilise WF4 et un service XAMLX avec une activité getuser() pour récupérer le user afin de gérer les droits et aussi d'historiser les appels

    Je veux récupérer le User qui s'est connecté au workflow pour lancer un etape.
    Authentification : forms, membership, role provider: aspnetroleprovider
    Ca marche nickel avec le protocole wsHttpBinding et wsHttpContextBinding.

    Jusqu'à ce que je remette la persistance !!!!! Là, plus d'authentification visible depuis le workflow (l'authentification fonctionne au niveau de la connexion)

    J'ai besoin d'aide. Je pense que les runtime prends la main quand on active la persistance et qu'il oublie les parametres d'authentification sur la route ...

    Patrick

    web.config serveur

      <services>
          <service behaviorConfiguration="serviceBehavior" name="TradeService">
            <endpoint address="" binding="wsHttpContextBinding" contract="ITradeService" bindingConfiguration="WSHttpContextBinding_ITradeService" />
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
          </service>
        </services>
        <bindings>
          <wsHttpContextBinding>
            <binding name="WSHttpContextBinding_ITradeService"
              <security mode="Message">
                <transport clientCredentialType="Windows" proxyCredentialType="None"
                  realm="" />
                <message clientCredentialType="UserName" negotiateServiceCredential="true"
             algorithmSuite="Default" />
              </security>
            </binding>
          </wsHttpContextBinding>
        </bindings>
        <behaviors>
          <serviceBehaviors>
            <behavior name="serviceBehavior">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="True" />
              <serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="AspNetSqlRoleProvider" />
              <serviceCredentials>
                <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="AspNetSqlMembershipProvider" />
                <serviceCertificate findValue="ServicesCert" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
              </serviceCredentials>
              <etwTracking profileName="Suivi des commandes" />
             
              <sqlWorkflowInstanceStore instanceCompletionAction="DeleteAll"
                                        instanceEncodingOption="GZip"
                                        instanceLockedExceptionAction="BasicRetry"
                                        connectionStringName="ApplicationServerWorkflowInstanceStoreConnectionString"
                                        hostLockRenewalPeriod="00:00:10"
                                        runnableInstancesDetectionPeriod="00:00:05"  />
                                       
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
        <diagnostics etwProviderId="213cad86-d983-4104-aa42-4e250cdf487e">
          <endToEndTracing propagateActivity="false" messageFlowTracing="false" />
          <messageLogging logEntireMessage="false" logMalformedMessages="false" logMessagesAtServiceLevel="false" logMessagesAtTransportLevel="false" maxMessagesToLog="3000" />
        </diagnostics>

    Si j'enlève la ligne  suivante ça marche

            <sqlWorkflowInstanceStore instanceCompletionAction="DeleteAll"
                                        instanceEncodingOption="GZip"
                                        instanceLockedExceptionAction="BasicRetry"
                                        connectionStringName="ApplicationServerWorkflowInstanceStoreConnectionString"
                                        hostLockRenewalPeriod="00:00:10"
                                        runnableInstancesDetectionPeriod="00:00:05"  />
       

    L'activité :

     // la ligne suivante est requise en wsHttpBinding
           // [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
            protected override void Execute(CodeActivityContext context)
            {
                try
                {
                    // la ligne suivante fonctionne en wsHttpBinding
                    //IIdentity identity = Thread.CurrentPrincipal.Identity;

                    IIdentity currentUser = ServiceSecurityContext.Current.PrimaryIdentity;
                    bool Admin = false;
                    if (Roles.IsUserInRole(currentUser.Name, "Admin"))
                    {
                        context.SetValue(IsAdmin, true);
                    }
                   
                    if (currentUser != null && currentUser.IsAuthenticated)
                    {
                        using (DbAdminConfigDataContext store = new DbAdminConfigDataContext())
                        {
                            vw_Admin_Users u = store.vw_Admin_Users.FirstOrDefault(q => q.UserName == currentUser.Name);
                            context.SetValue(UserName, currentUser.Name);
                            context.SetValue(UserId, u.UserId );
                        }
                    }
                }
                catch (Exception e)
                {
                    CDTrace.WriteLine(CDTrace.tracelevel.error, 0, "GetIdentity " + e.Message, "", e.StackTrace);
                  
                }
               
            }

    Le client :

     svc = new TradeServiceProxy.TradeServiceClient("WSHttpContextBinding_ITradeService");
                    svc.ClientCredentials.UserName.UserName = "ptournay";
                    svc.ClientCredentials.UserName.Password = "1";
                    svc.InnerChannel.Faulted += new EventHandler(InnerChannel_Faulted);

     


    ptournay
    dimanche 8 août 2010 09:26

Réponses

  • Ca marche

    Un grand merci à Jeremy.

    La solution consiste donc a encapsuler le block receive/send dans un OperationContextScope provenant du ctp1 du pack security.

    NE PAS FAIRE DE PERSIST Dans ce bloc, ni dans aucune activite sous-jacente.
    Penser à utilier le mode asynchrone le plus souvent possible pour sortir du bloc OperationContextScope  et effectuer un persist à la fin

    Je n'ai pas trouvé comment desactiver temporairement la persistance moi-même, dommage.

    Patrick


    ptournay
    mardi 10 août 2010 10:38

Toutes les réponses

  • Bonjour Patrick,

    je vais jetter un coup d'oeil à ce scénatrio,... ça m'intrigue.


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    lundi 9 août 2010 09:31
    Modérateur
  • Bonjour Patrick,

    Petite question tout de même :

    L'erreur est présente :

    • Dans AppFabric?
    • Dans IIS Seule (sans fabric)?
    • Dans Visual Studio?

    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    lundi 9 août 2010 09:38
    Modérateur
  • Merci de regarder

    Pour que tout celà fonctionne, on est quand meme obligé d'herberger dans IIS, surtout qauand l'appli client est silverlight et qu'il a de multiples projets.
    Et quand appfabic est installé, il s'installe partout dans IIS
    En plus pour la persistance, on est un peu dedans.

    Donc je n'ai pas testé dasn un environnement de type VS2010 pur.

    Ceci dit, je n'utilise aucun code specifique à appfabric.
    Il faut quand meme savoir que l'appli principale est un WCF Workflow (xamlx) et que je n'ai pas la main sur tout le code.

    Patrick


    ptournay
    lundi 9 août 2010 09:52
  • Pour la persitance, pas de soucis, c'est du WF4 et ça marche dans IIS et hors. Heureusement si non ça ne servirait pas à grand chose ;)

    Pour le Service Xamlx, il ne faut pas se faire de soucis, ça marche presque tout seule ;)

    Je me refais une version perso du code et je test si j'ai le même soucis


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    lundi 9 août 2010 09:56
    Modérateur
  • Jeremy,

    J'ai relu tes blogs, alors quelques precisions :

    Le GetUser est appelé dans un Request (j'utilise la meme facon que toi de passerr les arguments).

    J'ai essayé un "impersonate" sur le service sans trop y croire, masi ca ne veut plus du tout fonctionner. Même sans la persistance.

     


    ptournay
    lundi 9 août 2010 10:09
  • Je reviens vers toi,

    J'ai testé sans passer par IIS, idem.

    Mais il faut avouer que le serveur web integre de VS utilise quand meme les parametres systems de IIS ...

     


    ptournay
    lundi 9 août 2010 10:19
  • Re ;)

    Attention à ne pas tout mélanger. Il faut peut être qu'on revoit ce que tu cherches à faire.

    "Impersonate" sert à ce que le compte qui execute le workflow puisse executer ses activités avec les droits de l'appelant (le client proxy).

    C'est ce que tu veux faire?

    Ou tu souate juste identifier l'appleant du service?


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    lundi 9 août 2010 12:24
    Modérateur
  • Ce que je cherche c'est à authentifier l'appelant pour lui renvoyer uniquement la liste d'actions auquel il a droit ainsi que tracer son parcours.

    Par contre la tentative d'utilisation de impersonate était destinée à "propager" l'appelant original puisqu'il semble avoir été perdu dans le "double hop" de la persistance. Je connais son utilisation, et effectivement ça uarait pu fonctionner, avec les inconvénents sur les droits inhérents à cette fonction.
    Donc j'en suis toujours au même point.

    As-tu pu tester ?

    Patrick

     


    ptournay
    lundi 9 août 2010 12:28
  • je suis sur plusieurs petits tests similaire à ton services et tout fonctionne à peu près (il faut encore que je passe sur un provider pour les roles)

    Mais en attendant, as tu regardé le context http avec HttpContext.Current.User.Identity?


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    lundi 9 août 2010 12:52
    Modérateur
  • Bonjour Patrick,

    Après pas mal de tests je confirme un comportement étrange!!!!!

    J'ai réalisé mes test à partir d'un service xamlx simple : j'ai mis deux receive avec leurs réponse.

    Entre chaque receive j'ai ajouté une activité me donnant le nom de l'utilisateur :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Activities;
    using System.ServiceModel;
    
    namespace Demo.WF4.WcfAuthentication
    {
      /// <summary>
      /// Activity based on CodeActivity
      /// </summary>
      public sealed class CodeActivity1 : CodeActivity<String>
      {
        /// <summary>
        /// Execute
        /// </summary>
        /// <param name="context">WF context</param>
        protected override String Execute(CodeActivityContext context)
        {
          if (ServiceSecurityContext.Current == null)
          {
            return "ServiceSecurityContext.Current null";
          }
          else if (ServiceSecurityContext.Current.PrimaryIdentity == null)
          {
            return "ServiceSecurityContext.Current.PrimaryIdentity = null";
          }
          else
          {
            return ServiceSecurityContext.Current.PrimaryIdentity.Name;
          }
        }
    
      }
    }
    
    

     

    La réponse du premier Recieve doit me donnée le nom de l'utilisateur

    La réponse du second Receive doit me donner le nom du premier puis du second utilisateur du service.

    J'ai activé la correlation sur le service.

    Le tout avec un wsHttpBinding sur utilisant mes comptes windows.

    Voici mon servicemodel :

    <system.serviceModel>
      <bindings>
       <wsHttpBinding>
        <binding>
         <security mode="Message">
          <!--<transport clientCredentialType="Windows" proxyCredentialType="None"/>-->
          <message clientCredentialType="Windows" />
         </security>
        </binding>
       </wsHttpBinding>
      </bindings>
      <services>
       <service name="Service1">
        <endpoint binding="wsHttpBinding" contract="IService"/>
       </service>
      </services>
      <behaviors>
       <serviceBehaviors>
        <behavior>
         <!-- Pour éviter la divulgation des informations sur les métadonnées, définissez la valeur ci-dessous sur False et supprimez le point de terminaison des métadonnées ci-dessus avant le déploiement -->
         <serviceMetadata httpGetEnabled="true"/>
         <!-- Pour recevoir les détails d'exception des erreurs à des fins de débogage, définissez la valeur ci-dessous sur True. Définissez-la sur False avant le déploiement pour éviter la divulgation des informations d'exception -->
         <serviceDebug includeExceptionDetailInFaults="true"/>
    
         <sqlWorkflowInstanceStore instanceCompletionAction="DeleteAll"
                      instanceEncodingOption="GZip"
                      instanceLockedExceptionAction="BasicRetry"
                      connectionStringName="WF4"
                      hostLockRenewalPeriod="00:00:10"
                      runnableInstancesDetectionPeriod="00:00:05" />
        </behavior>
       </serviceBehaviors>
      </behaviors>
      <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
     </system.serviceModel>
     
    

    j'a fait mes premiers tests sans persitance (donc sqlWorkflowInstanceStore  en commentaires). Tout marche normalement.

    Si j'active la persitance, c'est la cata...

    Premier appel : ServiceSecurityContext.Current == null :(

    Second appel : j'ai bien le nom de l'utilisateur :)

    J'ai fait mes tests avec une authentification sql et un compte windows... et je cherche encore de ce côté car en authentification windows sur le sql ça cloche de temps en temsp... je dois m'en assurer avant de l'affirmer.

    voila l'état de mon avancée... Je crois que tu as touché un bug dans la configuration par défaut du trio persistance+WCF+WF... je crois qu'on a un impersonnate qui n'est pas à sa place... à confirmer


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    lundi 9 août 2010 14:29
    Modérateur
  • Merci beaucoup Jeremy,

    D'où mon idée de forcer un impersonate... Mais sans y parevenir, le Receive semble incompatible avec ce forçage.

    Si tu confirmes le bug , je ne pense qu'on aura une correction rapidement.

    Il faut néanmoins le remonter aux équipes MS afin qu'ils le corrigent dans un prochain pack.
    Comment fait-on dans ce cas pour ne pas griller les quelques indidents que j'ai alors qu'il s'agit d'un vrai bug  produit ?

    Patrick


    ptournay
    lundi 9 août 2010 14:52
  • Re,

    J'ai trouvé, après pas mal de tracking ;)

    en fait c'est le Thread du worklfow qui pose problèmes... à chaque invocation on se retrouve avec un context différent :(

    il y a une solution : intercepter les appel des clients WCF!

    ... Et Microsoft a publié un pack d'activités pour cela :

    http://blogs.msdn.com/b/endpoint/archive/2010/06/30/introducing-the-wf-security-pack-ctp-1-on-wf-codeplex-com.aspx

    je ne suis pas super fan de l'idée d'utiliser des pack suplémentaires d'activités, mais il semble y avoir un scope OperationContextScope bien pratique pour utiliser facilement l'operationContexte et donc le Servicecontext ;)

    Voici donc une solution au problème... (même si je pense que la programmation d'une extension WF pourrait être une solution plus soft... à voir, je n'ai pas encore envisager comment la coder)


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    lundi 9 août 2010 15:01
    Modérateur
  • Merci

    J'essaie le ctp ce matin.

    Je te tien au courant.

    Patrick


    ptournay
    mardi 10 août 2010 08:29
  • Bonjour Patrick,

    Ron Jacobs viens de publier une vidéo sur le sujet :

    http://blogs.msdn.com/b/endpoint/archive/2010/08/09/endpoint-tv-workflow-services-security-pack.aspx

    Voici la liste des points abordés :

    Use PrincipalPermission based authorization
    Work with Username and SAML tokens
    Impersonate the caller
    Create client credentials for calling services
    And much more...


    Comme par hazard, il est sujet de PrincipalPermission ;)


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    mardi 10 août 2010 08:38
    Modérateur
  • Bonjour Jeremy,

    Dans le genre, je contourne le problème :

    Voilà le message obtenu quand j'execute mon workflow avec la persitance en encapsulant mon Receive dans un OperationContextScope

    Le user est bien identifié, caz ca marache

    mais

    (1) Exception rencontrée :
    Les activités Persist ne peuvent pas être contenues au sein de blocs sans persistance.

    Je pense donc qu'ils ont rencontré le même problème que nous mais qu'ils l'ont contourné en desctivant la persistance dans cette opération.

    Ce que je vais faire c'est supprimer le "Persist before send"  dans le send et sauvegarder le user dans une variable du workflow.

    J'y retourne

    Patrick


    ptournay
    mardi 10 août 2010 10:08
  • Oui patrick, on contourne le problème :(

    J'ai regardé les codes que j'avais déjà fais sur le même genre de sujet et j'ai réalisé que je n'avais pas rencontré le souci car je n'utilisait le wsHttpBinding que pour sécurisé l'accès au service.

    A chauque fois je passe mes donnée métier au service, dont l'Id (Guid dans mon cas) de mes utilisateur dans ma base de donnée. Donc forcément j'ai toujours les informations sur l'utilisateur de mon service et je ne suis pas impacté par la manière dont WF charge ou décharge mes workflows.

    Sans spécialement vouloir contourner le problème, je suis arrivé à une solution plus simple et n'induisant pas le moindre changement en cas de persistance ;)


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    mardi 10 août 2010 10:15
    Modérateur
  • Ca marche

    Un grand merci à Jeremy.

    La solution consiste donc a encapsuler le block receive/send dans un OperationContextScope provenant du ctp1 du pack security.

    NE PAS FAIRE DE PERSIST Dans ce bloc, ni dans aucune activite sous-jacente.
    Penser à utilier le mode asynchrone le plus souvent possible pour sortir du bloc OperationContextScope  et effectuer un persist à la fin

    Je n'ai pas trouvé comment desactiver temporairement la persistance moi-même, dommage.

    Patrick


    ptournay
    mardi 10 août 2010 10:38
  • Bonjour Patrick,

    Pour désactiver la persistance, c'est très simple:

    1. Dans l'activité : Il faut ajouter une Variable<NoPersistHandle> dans ton activité qui doit programmer l'exeuction d'une activité sans persistance (Exemple d'une activité avec une  propriété Body de type Activity).
    2. Dans la méthode CacheMetadata : Il faut enregistrer ce handle comme une ImplementationVariable dans la metadata.
    3. Dans le Execute : Avant de faire le schedule sur le Body, il faut utiliser la méthode Enter de la varaible NoPersistHandle et lui passer le context.

    Et c'est tout ;)

     

     


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    mardi 10 août 2010 10:52
    Modérateur
  • Pour information, je viens de publié un article expliquant la réalisation d'un scope désactivant le persitance :

    http://blogs.codes-sources.com/jeremyjeanson/archive/2010/08/10/wf4-coder-une-activit-qui-d-sactive-temporairement-la-persistance.aspx


    Jérémy Jeanson MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    mardi 10 août 2010 11:45
    Modérateur