miércoles, 3 de diciembre de 2014

Universal PredicateBuilder... tunned for Azure AD Graph API queries

Las consultas a la Azure AD Graph API no admiten le sean provistos valores nulos o vacíos, y las opciones de filtro soportadas al día de hoy se limitan a and, or, eq, ge, le, Startswith, y Any.

La Microsoft Azure Active Directory Graph Client Library facilita notablemente el manejo de estas consultas desde las aplicaciones .NET, pero aún es algo incomodo la construcción dinámica de predicados, debido a lo cual recurrí para esta labor al PredicateBuilder generosamente compartido por Pete Montgomery allá por febrero del 2011.

Afortunadamente todo el entorno de Microsoft Azure está evolucionando rápidamente así que es de esperar estas limitaciones no perduren por mucho tiempo, pero comparto esta pequeña solución por si algún otro desarrollador se encuentra ante la misma situación.

Los ajustes introducidos a la versión de Pete fueron mínimos, y solo para asegurar la compatibilidad con los operadores lógicos and y or (Pete usó AndAlso, y OrElse, no soportados por la Graph API).

Consulta de usuarios

Basada en AzureADSamples/ConsoleApp-GraphAPI-DotNet

    IPagedCollection<IUser> searchResults = null;

    var predicate = GraphAPIPredicateBuilder.Create<IUser>(u => u.AccountEnabled == true);
    if (!string.IsNullOrWhiteSpace(canonicalUser.Name))
        predicate = predicate.And(u => u.UserPrincipalName.StartsWith(canonicalUser.Name));
    if (!string.IsNullOrWhiteSpace(displayname))
        predicate = predicate.And(u => u.DisplayName.StartsWith(displayname));
    if (!string.IsNullOrWhiteSpace(description))
        predicate = predicate.And(u => u.GivenName.StartsWith(givenname));

    try
    {
        ActiveDirectoryClient activeDirectoryClient =
            AzureADAuthenticationHelper.GetActiveDirectoryClientAsApplication();
        IUserCollection userCollection = activeDirectoryClient.Users;
        searchResults = userCollection.Where(predicate).ExecuteAsync().Result;
    }
    catch (Exception e)
    {
        Debug.WriteLine(string.Format("Error searching users - {0} {1}", e.Message,
            e.InnerException != null ? e.InnerException.Message : ""));
    }


GraphAPIPredicateBuilder


    /// <summary>
    /// Enables the efficient, dynamic composition of query predicates... tunned for Azure AD Graph API
    /// 
    /// Based on the work of Pete Montgomery at February 10, 2011
    /// http://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/
    /// </summary>
    public static class GraphAPIPredicateBuilder
    {
        /// <summary>
        /// Creates a predicate that evaluates to true.
        /// </summary>
        public static Expression<Func<T, bool>> True<T>() { return param => true; }

        /// <summary>
        /// Creates a predicate that evaluates to false.
        /// </summary>
        public static Expression<Func<T, bool>> False<T>() { return param => false; }

        /// <summary>
        /// Creates a predicate expression from the specified lambda expression.
        /// </summary>
        public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }

        /// <summary>
        /// Combines the first predicate with the second using the logical "and".
        /// </summary>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.And);
        }

        /// <summary>
        /// Combines the first predicate with the second using the logical "andAlso".
        /// </summary>
        public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.AndAlso);
        }

        /// <summary>
        /// Combines the first predicate with the second using the logical "or".
        /// </summary>
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }

        /// <summary>
        /// Combines the first predicate with the second using the logical "orElse".
        /// </summary>
        public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.OrElse);
        }

        /// <summary>
        /// Negates the predicate.
        /// </summary>
        public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
        {
            var negated = Expression.Not(expression.Body);
            return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
        }

        /// <summary>
        /// Combines the first expression with the second using the specified merge function.
        /// </summary>
        static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // zip parameters (map from parameters of second to parameters of first)
            var map = first.Parameters
                .Select((f, i) => new { f, s = second.Parameters[i] })
                .ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with the parameters in the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // create a merged lambda expression with parameters from the first expression
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        class ParameterRebinder : ExpressionVisitor
        {
            readonly Dictionary<ParameterExpression, ParameterExpression> map;

            ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }

            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ParameterRebinder(map).Visit(exp);
            }

            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression replacement;

                if (map.TryGetValue(p, out replacement))
                {
                    p = replacement;
                }

                return base.VisitParameter(p);
            }
        }
    }


That's all folks!


jueves, 6 de noviembre de 2014

TFS - MSBuild scripts en el Custom Assembly Path?

Desde el TFS 2010 se introdujo un nuevo parámetro Custom Assembly Path (o Ruta de acceso de control de versiones a ensamblados personalizados en español... uf! que quedó largo), al Build Controller para poder especificar allí la ubicación de aquellas dll´s que queremos tener disponibles en todos nuestros Build Definitions, sin tener que estar bajándolas del TFS para cada compilación.

Custom Assembly Path en el Build Controller

Lo interesante es que cualquier archivo que se guarde en ese path del TFS, lo mismo que las carpetas y sus contenidos, es bajado automáticamente a un directorio del Build Server inmediatamente luego de cada Check In. Así podemos dejar ahí scripts de MSBuild (o lo que se nos ocurra), para ejecutar rutinas en diferentes Build Process Templates usando la activity InvokeProcess.

La carpeta en la que queda el contenido del Custom Assembly Path es:

C:\Users\%username%\AppData\Local\Temp\BuildAgent\1\

Donde username corresponde al usuario bajo el cual corre el Team Foundation Build Service. y el ultimo numero al Build Agent.

Para acceder desde un Build Process Template a esta carpeta se debe agregar un activity del tipo GetBuildEnvironment al workflow, y luego asignar su atributo Result a una variable con un scope apropiado.


GetBuildEnvironment Activity como parte del workflow

Una vez creada la variable se puede acceder a todos los atributos que expone la clase BuildEnvironment, incluido el CustomAssemblyPath. en cualquier expresión VB que esté dentro del scope especificado.

Usando la variable buildEnvironment previamente creada

Todos los links de este post quedaron apuntando a la documentación del TFS 2010 porque ese fue la versión de servidor usada en el ejemplo, pero entiendo aplica totalmente a la siguientes versiones.

lunes, 27 de octubre de 2014

Encriptando una sección del web.config para Azure

Si bien el archivo web.config no es expuesto por el IIS, la presencia de información sensible en la sección appSettings de un proyecto Cloud Service de Microsoft Azure me llevó a buscar la posibilidad de encriptarlo local, y poder usarlo sin problemas en la nube.

Y lo que encontré fue esto:

Paso 1 - Necesito un certificado


Como mi proyecto ya estaba configurado para utilizar SSL lo que hice fue reutilizar el mismo certificado autofirmado.

Si no tenes un certificado hay que agregar uno al proyecto siguiendo los pasos descritos en Configuring SSL for an application in Azure (solo los pasos Get an SSL certStep 1: Get an SSL certificate, y la parte de upload certificate del paso Upload to AzureStep 3: Upload the deployment package and certificate).

Paso 2 - Agregar PKCS12ProtectedConfigurationProvider al WebRole desde Nuget



En el caso del Cloud Service solo basta con agregar este paquete al WebRole.

Paso 3 - Configurar el proveedor de ProtectedData en el web.config


Agregar la sección configProtectedData dentro de configuration del web.config, reemplazando el thumbprintValue por el del certificado usado.
<configuration>
  <configProtectedData>
    <providers>
      <add name="ProtectedConfigurationProvider" thumbprint="thumbprintValue" type="Pkcs12ProtectedConfigurationProvider.Pkcs12ProtectedConfigurationProvider, PKCS12ProtectedConfigurationProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=34da007ac91f901d"/>
    </providers>
  </configProtectedData>
  ...
</configuration>

Paso 4 - Encriptar la sección del web.config que se quiere proteger


En este caso solo buscaba encriptar la sección appSettings, pero se puede hacer lo mismo con cualquier otra sección, o varias.
aspnet_regiis -pef appSettings "C:\source\Azure\DevDay\WebRole1" -prov ProtectedConfigurationProvider
Como acertadamente anticipa Alp Arslan Eren en su blog, si ejecutando el aspnet_regiis te encontrás con un error could not load the PKCS12ProtectedConfigurationProvider assembly como me pasó a mí, siguiendo estas indicaciones se soluciona.

Y si, para armar el tutorial usé el proyecto DevDay 2014 que tan gentilmente cedió la gente de Microsoft para el concurso de AR-UY... ;-)

Paso 5 - Insertar el gacutil como parte de nuestro proyecto


Antes de poder usar la PKCS12ProtectedConfigurationProvider.dll en Azure vamos a necesitar registrarla en la GAC, y dado que los WebRoles de Azure no se están incluyendo las Microsoft SDKs tendremos que llevarnos una copia del gacutil.exe y config hacia la nube.

En mi caso levanté el Cloud Service sobre Windows Server 2012, así que me copie ambos archivos gacutil desde la carpeta C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools a una nueva carpeta Startup dentro de mi proyecto WebRole1.


Paso 6 - Nueva Startup Task para ejecutar gacutil


Al desplegar el WebRole en Azure necesitamos que se ejecute gacutil como uno de los primeros pasos. Para eso agregamos un archivo RegisterGAC.cmd (salvar este archivo de con codificación ANSI), a la carpeta Startup recientemente creada, conteniendo el comando:
Startup\gacutil.exe /nologo /i PKCS12ProtectedConfigurationProvider.dll
..y un nuevo Startup Task dentro del WebRole al archivo ServiceDefinition.csdef de la solución:
<WebRole name="WebRole1" vmsize="Small">
  <Startup>
    <Task commandLine="Startup\RegisterGAC.cmd" executionContext="elevated" taskType="simple"/>
  </Startup>
  ...
</WebRole>
Los tres archivos agregados en la carpeta Startup deben quedar marcados como Copiar en el directorio de salida: Copiar siempre.

Paso 7 - Desplegar la solucion en Azure


...y observar como la aplicación fluye gloriosa!


Fuentes:

jueves, 16 de octubre de 2014

IIS Express, HTTPS... y una URL externa

Usar el IIS Express con un sitio web creado en VS 2013 va como piña mientras te mantengas en localhost, el problema se me presentó cuando quise accederlo desde otra maquina de la red.

  1. Para empezar tuve que abrir el VS 2013 como Administrador, porque sino no me permitía cambiar la URL del proyecto.

    A continuación  cambie la URL entrando en Propiedades del proyecto, luego en Web, y ahí en el grupo de parámetros Servidores, sustituyendo localhost por el nombre publico de la maquina donde se aloja el sitio.

  2. Después tuve que crear un nuevo certificado para esa dirección usando Makecert:
    Makecert -r -pe -n CN="nombrePublico" -b 10/15/2014 -e 10/15/2017 -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localmachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12
    El secreto es que el certificado quede en localmachine porque ahí lo buscará el siguiente comando.

  3. Ahora falta asignar el certificado a la ip y puerto mediante netsh, ejecutando:
    netsh http add sslcert ipport=0.0.0.0:44302 certhash=certThumbprint appid={anyValidGuid}
    Podes averiguar el thumbprint, o huella digital, del certificado generado usando MMC, en el menú Archivos seleccionar la opción Agregar o quitar complementos, Certificados, y ahí Cuenta de equipo.

  4. El último paso es que nuestro equipo cliente confíe en el certificado que generamos. En mi caso usando Chrome lo que hice fue intentar acceder al sitio web alojado en el IIS Express, cuando me apareció el mensaje de el certificado no es de confianza, baje el certificado lo guarde en un archivo en mi maquina, y luego desde la configuración del Chrome lo importe a la carpeta de Entidades de certificación raíz de confianza (en este sitio se explica mucho mas detallado).

miércoles, 24 de septiembre de 2014

ACS90014 error using Active Directory Authentication Library V2

Intentando autenticar una aplicación de consola usando ADAL v2, al invocar al método AcquireToken pasando el clientId (sin clientSecret), y PromptBehavior.Always, luego del ingreso de las credenciales me devolvía el error:
ACS90014: The request body must contain the following parameter: 'client_secret or client_assertion'
Luego de buscar un rato pude encontrar que la causa era que el Application que habia creado en el Azure Active Directory era del tipo WEB APPLICATION AND/OR WEB AP cuando lo correcto, para el caso que me interesaba, era crearla del tipo NATIVE CLIENT APPLICATION.

Parte del código utilizado:

    string clientId =       ConfigurationManager.AppSettings["ClientId"];
    string clientSecret =   ConfigurationManager.AppSettings["ClientSecret"];
    string resource =       ConfigurationManager.AppSettings["ServiceRealm"];
    string authenticationUri =
                            ConfigurationManager.AppSettings["AuthBaseUri"] +
                            ConfigurationManager.AppSettings["Tenant"];

    AuthenticationContext authenticationContext =
                            new AuthenticationContext(authenticationUri, false);

    string token;
    var redirectUri = new Uri(ConfigurationManager.AppSettings["RedirectUri"]);
    try
    {
        AuthenticationResult authenticationResult = 
            authenticationContext.AcquireToken(resource, clientId, redirectUri, PromptBehavior.Always);
        token = authenticationResult.AccessToken;
        Console.WriteLine("\n Welcome " + authenticationResult.UserInfo.GivenName + " " + authenticationResult.UserInfo.FamilyName);
    }
    catch (AuthenticationException ex)
    {
        string message = ex.Message;
        if (ex.InnerException != null)
            message += "InnerException : " + ex.InnerException.Message;
        Console.WriteLine(message);
        Console.ReadKey();
        return;
    }

Basado en la publicación de Vittorio BertocciActive Directory Authentication Library (ADAL) v2 for .NET/Windows Store/Windows Phone–General Availability!

lunes, 11 de agosto de 2014

Invalid class [0x80041010]

Después de desinstalar el SQL Server 2005 en un Windows 7 32 bits ya no pude entrar al SQL Server Configuration Manager del SQL Server 2008... aparecía el error: "Cannot connect to WMI provider. You do not have permission or the server is unreachable".


Según explica la gente de Microsoft "This problem occurs because the WMI provider is removed when you uninstall an instance of SQL Server." (http://support.microsoft.com/kb/956013), y tal parece que las diferentes versiones de SQL Server comparten el mismo archivo de configuración WMI.

La solución pasa por volver a compilar las clases necesarias en el repositorio WMI (archivo sqlmgmproviderxpsp2up.mof), con el comando mpfcomp, para la versión del SQL Server que se desea reparar.

En mi caso (Windows 7 32 bits + SQL Server 2008), el comando fue:

mofcomp "C:\Program Files\Microsoft SQL Server\100\Shared\sqlmgmproviderxpsp2up.mof"

..y santo remedio.