Cercar en aquest blog

20/5/10

Diseny SOLID, el bon disseny

El dia 13 de Maig d'enguany (2010), es va celebrar a Barcelona el XXV 'Foro' d'arquitectura de Microsoft, promogut per Microsoft Ibérica. Bàsicament es va centrar en les novetats del Framework 4.0 i Visual Studio 2010, extenen-se en demostracions basades el l'aplicatiu desenvolupat com a 'demo' d'una arquitectura orientada al domini. Si entrem a la Web d'arquitectura http://msdn.microsoft.com/es-es/architecture/default.aspx podem accedir als continguts i el codi.

Tot el que és va dir va ser molt interesant, jo no hi vaig assitir però en tinc bons 'inputs' i m'he llegit les presentacions. De totes les lectures, i sense desmereixen cap, he trobat mol interesant la que parla de desacoplament entre capes de la arquitectura, presentada per Hadi Hariri, i els principis del disseny SOLID.

El dissney SOLID diu bàsicament cinc coses a tenir en compte, si volem un resultat en la implementació, sòlid, robust, escalable i flexible.

Single Responsibility Principle: Una classe ha de ser dissenyada i implementada per tenir una sola responsabilitat. Si una classe és la responsable d'autenticar usuaris, només té la responsabilitat d'autenticar usuaris.

Open Closed Principle: Una classe ha d'estar oberta a extensions, però mai a canvis. Per entendre-ho, a una classe li podem afegir nous metodes, podem especificar-la més (herència), però mai canviar la seva signatura pública, propietats, metodes i funcions, inicials.

Liskov Substitution Principle: Si tenim una classe subtipus d'una altre, en l¡ aplicació les classes pare han de poder ser substituides per les classes fill sense cap problema, i alteracions de l'aplicació.

Interface Segregation Principle: Mai s'ha d'obligar als clients a implementar interfaces que no requereixen.

Dependency Inversion Principle: Intentar aplicar desacoplament entre capes diferents de la arquitectura.


En el següent enllaç podeu trobar totes les presentacions en format pdf.


27/4/10

La capa de dades: Entity Framework

Per fer una petita introducció, recomano la lectura de les següents entrades de la llinreria MSDN.



Un exemple aclaridor de la potencia d'aquest framework la podem veure en una de les seccions a la que ens guia el segon enllaç, "Walkthrough: Serialize Self-Tracking Entities (Entity Framework)" o "Attaching and Detaching Objects (Entity Framework)" o "Working with Self-Tracking Entities (Entity Framework)".

I també estaria bé fer una lectura del que no es recomana a l'hora de fer aplicacions de n-capes, "Anti-Patterns To Avoid In N-Tier Applications", i el que si està bé "N-Tier Application Patterns"







Aprenem més...........!!!

22/4/10

Proveïdor d’Identitats: Un exemple per entendre-ho tot (VI)

(PART V)

Bé doncs, ara implementarem la segona part, entrar des del servei fronterer al servei final, i que aquest rebi les credencials de l’usuari que ha iniciat el procés en l’estació client.


Primer de tot, editarem el fitxer de configuració del servei fronterer i afegirem el següent:

 
Després agreguem una referència al servei final.
 
 
I, tornant al web.config del servei fronterer apliquem la següent modificació:
 
Tot seguit modifiquem la implementació d’ambdós serveis, per tal d’aplicar la funcionalitat que volem demostrar.





En el servei fronterer (Front_End_Service):

public string GetDataUserName()
        {
            //TODO: Change the code below to handle your claims usage.
            IClaimsPrincipal principal = (IClaimsPrincipal)Thread.CurrentPrincipal;
            IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;

            SecurityToken st = identity.BootstrapToken;
            if (st == null)
            {
                st = principal.Identities[0].BootstrapToken;
            }

            string _sconf = "WS2007FederationHttpBinding_IService";
            RequestSecurityTokenResponse _rsts = new RequestSecurityTokenResponse();

            STSRPClient c2 = new STSRPClient(st, _sconf);

            Back_End_Service.IServiceChannel cl2 = c2.ClientActAs;

            string res1 = cl2.GetDataUserName();

            cl2.Close();

            return string.Format("Front_End_Service: tu ets {0}{1}{2}" , identity.Name, "\r\n" ,res1);
        }


I en el servei final (Back_End_Service):
public string GetDataUserName()
        {
            //TODO: Change the code below to handle your claims usage.
            IClaimsPrincipal principal = (IClaimsPrincipal)Thread.CurrentPrincipal;
            IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;

            return string.Format("Back_End_Service: tu ets {0} i l'actor és {1}", identity.Name, identity.Actor.Name);
        }


I no oblidem importar les llibreries IDP i STS al servei fronterer.


Ara només queda accedir a la consola del ADFS 2.0 i configurar el servei final per tal de que accepti delegació. Per fer-ho, haurem de donar autorització per delegar a l’usuari del pool de connexions on es trobi el servei fronterer dins el servidor d’aplicacions (IIS+WAS). Normalment el servei de xarxa, tot i que no és el més convenient, si volem fer crides des de servidors diferents. És millor utilitzar un usuari del domini, amb permisos d’accés (els necessaris) al AD.
Primer de tot, dins la consola, editem les reclamacions del servei final:

Tot seguit ens posicionem en la pestanya de regles d’autorització de delegació.




Afegim la regla que autoritzarà a l'usuari del pool d'aplicacions del servei fronterer a l' IIS a actuar com a l'usuari que hi ha encaixat en el token que s'ha generat des de la estació client per accedir al servei fronterer i que també ens servirà per accedir al servei final. Això és el que s'anomena "delegar el control l'accés".


L'usuari del servei de xarxa, és el que i ha configurat al pool del servei fronterer a l'IIS. Quan un servei o aplicació té configurat aquest usuari en el seu pool, i accedeix a un recurs de xarxa, l'usuari es converteix dins el domini en l'usuari [domini\nomdehost$], molt important saber-ho. Una altre cosa que hem de fer, es donar permisos d'accés a la clau privada del certificat que utilitzem per signar els tokens a aquest usuari.

Tot seguit, un cop afegida la regla, ens posicionem a les regles de transformació (primera pestanya). Esborrem tot el que hi hagi, i hi afegim dues regles que deixarem passar “reclamacions” del token d’entrada, el que utilitzem per accedir al servei de forma delegada.






Fem el mateix amb el Rol, i finalment tenim:




I ja està, ara només cal provar un altre cop tot el procés, provem i obtenim:



Finalment podem veure com fem traspassar de manera transparent, les credencials de l’usuari fins al servei final. Com que no podem fer un login de l’usuari en el servei fronterer (no podem, desconeixem la contrasenya de l’usuari) utilitzem autoritzacions de delegació per tal d’aconseguir-ho.



Amb aquest senzill exemple, es demostra una de les grans utilitats que pot oferir un gestor d’identitats.

Proveïdor d’Identitats: Un exemple per entendre-ho tot (V)


(PART IV)
Per implementar la negociacio amb el STS i el accés al servei, primer ens crearem un a llibreria en C# i anomenada IDP i li posarem aquesta classe dins: 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IdentityModel.Tokens;
using System.ServiceModel;
using Microsoft.IdentityModel.Protocols.WSTrust;
using System.ServiceModel.Security;

namespace IDP
{
    public static class EndPoints
    {
        public static string baseUri = "http://[idphost]/adfs/services/";
        public static string SSLbaseUri = "https://[idphost]/adfs/services/";
        
        public  static SecurityToken GetTokenFrom_trust_13_usernamemixed(string username, string password, string appliesTo, out RequestSecurityTokenResponse rsts)
        {
            string adrecaSTS = "trust/13/usernamemixed";

            WS2007HttpBinding binding = new WS2007HttpBinding();

            binding.Security.Message.EstablishSecurityContext = false;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            binding.Security.Mode = SecurityMode.TransportWithMessageCredential; //https




            WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(binding, new EndpointAddress(SSLbaseUri + adrecaSTS));
            trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
            trustChannelFactory.Credentials.UserName.UserName = username;
            trustChannelFactory.Credentials.UserName.Password = password;
            trustChannelFactory.ConfigureChannelFactory();

            WSTrustChannel tokenClient = (WSTrustChannel)trustChannelFactory.CreateChannel();


            //create a token issuance issuance
            RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue);
            //Relying Party’s identifier
            rst.AppliesTo = new EndpointAddress(appliesTo);
            //call ADFS STS
            SecurityToken token = tokenClient.Issue(rst, out rsts);

            return token;
        }
        
        public static SecurityToken GetTokenFrom_trust_13_windows(string appliesTo, out RequestSecurityTokenResponse rsts)
        {
            string adrecaSTS = "trust/13/windows";

            WS2007HttpBinding binding = new WS2007HttpBinding();

            binding.Security.Message.EstablishSecurityContext = false;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
            binding.Security.Mode = SecurityMode.Message;
            binding.Security.Message.NegotiateServiceCredential = true;




            WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(binding, new EndpointAddress(baseUri + adrecaSTS));
            trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
            trustChannelFactory.ConfigureChannelFactory();

            WSTrustChannel tokenClient = (WSTrustChannel)trustChannelFactory.CreateChannel();


            //create a token issuance issuance
            RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue);
            //Relying Party’s identifier
            rst.AppliesTo = new EndpointAddress(appliesTo);
            //call ADFS STS
            SecurityToken token = tokenClient.Issue(rst, out rsts);

            return token;
        }

  
    }
}


Aquesta classe ens ajuda a obtenir tokens del STS per un servei en concret. El primer mètode, a partir d’unes credencials entrades per l’usuari, i el segon utilitzan les credencials del usuari loginat al SO (usuari Windows).


I ara, per tal de facilitar la creació de clients de Serveis (Agents de servei), que puguin actuar amb o sense delegació, utilitzarem una altre llibreria que ens facilitarà la feina. Aquesta la farem en VB.Net i li direm STS. Dins hi posarem la següent classe. Hem de fer les referències.

Imports System.IdentityModel.Tokens
Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports Microsoft.IdentityModel.Protocols.WSTrust
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Security
Imports System.ServiceModel.Security.Tokens
Imports System.Text

Public Class STSRPClient(Of T)
    Implements IDisposable
#Region "Members"
    Private _st As SecurityToken
    Private _factory As ChannelFactory(Of T)
#End Region
    ''' 
    ''' Contructor per generar Client a partir del fitxer de configuració
    ''' 
    ''' 
''' 
''' 
    Sub New(ByVal st As SecurityToken, ByVal bindingConfiguration As String)
        Create(st, bindingConfiguration)
    End Sub
  
    Private Sub Create(ByVal st As SecurityToken, ByVal bindingconfiguration As String)
        Me._st = st
        _factory = New ChannelFactory(Of T)(bindingconfiguration)
        _factory.ConfigureChannelFactory()
    End Sub

    Public Sub Close()
        _factory.Close()
    End Sub

    Public ReadOnly Property Client As T
        Get
            Return _factory.CreateChannelWithIssuedToken(_st)
        End Get
    End Property
    Public ReadOnly Property ClientActAs As T
        Get
            Return _factory.CreateChannelActingAs(_st)
        End Get
    End Property

#Region "IDisposable Support"
    Private disposedValue As Boolean ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: dispose managed state (managed objects).
            End If
            If Me._factory.State <> CommunicationState.Closed Then
                _factory.Close()
            End If
            _st = Nothing
            ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
            ' TODO: set large fields to null.
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
    'Protected Overrides Sub Finalize()
    '    ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class


Un cop compilades, farem les referències a aquestes en l’aplicació client.


  1. Obtenir token per l’usuari
  2. Crear client amb el token
  3. Invocar Servei
  4. Escriure el resultat

Imports System.IdentityModel.Tokens
Imports Microsoft.IdentityModel.Protocols.WSTrust

Public Class Form1

    Private Sub BInvoke_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BInvoke.Click

        Dim adrecaservei As String = "http://localhost/Front_End_Service/Service.svc"

        ' App.config - Nom de la configuració del binding del client
        Dim conf As String = "WS2007FederationHttpBinding_IService"


        ' Primer hem d'obtenir un token de seguretat del IDP pel servei

        Dim rsts As New RequestSecurityTokenResponse
        Dim st As SecurityToken = IDP.EndPoints.GetTokenFrom_trust_13_usernamemixed(Me.TxtUser.Text, Me.TxtPwd.Text, adrecaservei, rsts)

        ' Un cop el tenim em de fer-lo servir per accedir-hi i invocar les seves operacions

        Dim clirp As New STS.STSRPClient(Of Front_End_Service.IServiceChannel)(st, conf)
        Dim client As Front_End_Service.IServiceChannel = clirp.Client

        Dim response As String = client.GetDataUserName()

        clirp.Close()
        clirp.Dispose()


        Me.TextBox1.AppendText(response + vbNewLine)

    End Sub
End Class



Diagrama de seqüències del event del botó.

Ara ja podem compilar i provar. De moment només invoquem el primer servei.



Invocació amb unes credencials incorrectes

 
Invocació amb credencials correctes.
 

Proveïdor d’Identitats: Un exemple per entendre-ho tot (IV)

(PART III)

Obrim l ‘administrador del IIS. Podem veure els serveis del ADFS i els nostres serveis.


Primer de tot, afegim un pool nou de connexions, que funcioni amb el .NET Framework 4.0. No és necessari però evitarem problemes (que no explicaré perquè depenen de la combinació de versions de SO + IIS). Aquest nou pool és el que utilitzaran els nostres nous serveis.


Bé ara ja podem tornar al Visual Studio, i picar una mica de codi.



Primer de tot podem mirar els web.config dels serveis i esbrinar que vol dir tot el que ha posat el procés de federació. És una bona pràctica.

Un cop assabentats d’aquesta nova tecnologia, modificarem els serveis.



Primer modificarem la Interfície d ‘ambdós, perquè quedi de la següent manera:
....

    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        string GetDataUserName();
    }

....


Tot seguit implementem la interfície en ambdós serveis.

Front_End_Service
using System.Threading;
using Microsoft.IdentityModel.Claims;
namespace Front_End_Service
{
    public class Service : IService
    {
         public string GetDataUserName()
        {
            IClaimsPrincipal principal = (IClaimsPrincipal)Thread.CurrentPrincipal;
            IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;
            return string.Format("Front_End_Service: tu ets {0}", identity.Name);
        }
    }
}



Back_End_Service

using System.Threading;
using Microsoft.IdentityModel.Claims;
namespace Back_End_Service
{
    public class Service : IService
    {
        public string GetDataUserName()
        {
            IClaimsPrincipal principal = (IClaimsPrincipal)Thread.CurrentPrincipal;
            IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;
            return string.Format("Back_End_Service: tu ets {0} i l'actor és {1}", identity.Name, identity.Actor.Name);
        }
    }
}



Després ens situarem a l’ aplicació client i crearem una referència al servei “Front_End_Service”.


Un cop fet això modificarem el fitxer “app.config”, per tal de posar el endpoint del STS que consumirà el token de seguretat.



Cercarem el “tag” següent :

 
 
I el substituirem per aquest, el trobarem comentat en el mateix app.config:
 
 
Podríem utilitzar-ne un altre, però aquest, és una bona opció. Una bona pràctica seria entendre els endpoints del STS.

Tot seguit crearem un formulari semblant a aquest, amb unes entrades de usuari i contrasenya , un botó per invocar el servei, i una consola per mostrar el resultat.

I, implementarem el codi que farà possible la invocació del servei. Sabem que necessitem un token de seguretat per accedir al servei, i el token ens l’ha de donar el STS a través del ADFS 2.0.
 
Freemarket.com Marketplace