UrlAuthorizationModule.CheckUrlAccessForPrincipal

Como já devem saber, utilizamos o elemento location no arquivo Web.Config para especificarmos, declarativamente, as restrinções de segurança (usuários ou papéis) para uma determinada páginas ASPX.

Caso precise verificar se o usuário atualmente autenticado tem ou não permissão para acessar uma determinada página se baseando as configurações do Web.Config, há um método estático chamado CheckUrlAccessForPrincipal que está definido dentro da classe UrlAuthorizationModule que permite isso. Esse método retorna um valor booleano indicando se o usuário informado tem ou não permissão de acesso. Eis um exemplo da sua utilização:

if(UrlAuthorizationModule.CheckUrlAccessForPrincipal(“~/ContaCorrente.aspx”, Context.User, “GET”)) { …. }

Estendendo a classe MembershipUser

Uma das grandes questões que encontro por ai é com relação a API do Membership do ASP.NET 2.0. Muitos desenvolvedores precisam incrementá-la, ou melhor, adicionar novas propriedades para que a mesma ganhe novas características, e atendam a necessidade da aplicação que está sendo construída. Essas propriedades são provenientes de tabelas diferentes das criadas pelo utilitário aspnet_regsql.exe ou ainda, em alguns casos, novas colunas que foram adicionadas dentro das tabelas aspnet_Users e aspnet_Membership.

Já no código, o que geralmente se costuma fazer, é criar uma classe que herde diretamente de MembershipUser:

public class Usuario : MembershipUser { … }

… e, dentro desta, adicionar as propriedades que estão sendo desejadas. Depois disso, será necessário customizar o provider que estamos utilizando que, na maioria das vezes é o SqlMembershipProvider, e sobrescrever alguns métodos para adicionar um trabalho extra, onde devemos buscar os dados adicionais. Eis abaixo um exemplo:

public class CustomSqlMembershipProvider : SqlMembershipProvider
{
    public override MembershipUser GetUser(string userName, bool userIsOnline)
    {
        MembershipUser user = base.GetUser(userName, userIsOnline);
        CustomUser custom = new CustomUser(user);
        custom.Telefone = “+55 (19) 5555-5555”;
        return custom;
    }
}

Mas as mudanças não param por ai. Como a interface da classe Membership opera somente com o tipo MembershipUser, voce será obrigado em todos os momentos que precisar chamar um determinado método (como o GetUser acima), efetuar conversões para o seu tipo específico. Algo mais ou menos como:

CustomUser custom = Membership.GetUser(“israelaece”, false) as CustomUser;
if(custom != null)
    Response.Write(custom.Telefone);

Ainda para piorar as coisas, voce perderá toda um dos principais benefícios do Provider Model que é a reutilização de código pois, se amanhã voce precisar rodar a aplicação sob uma base de dados Access ou Oracle, voce terá que sobrescrever cada um dos respectivos providers para dar o devido suporte.

Ao meu ver, eu acredito que a classe MembershipUser deve ser utilizada única e exclusivamente para gerenciar a autenticação/identificação do usuário dentro da aplicação. Se voce precisar adicionar características para os usuários, então a minha recomendação é utilizar o Profile (que não deixa ser uma extensão do usuário). Caso isso não seja possível, crie uma API baseando-se no modelo de Provider Model, se desejar suportar os benefícios que ele proporciona. Se mesmo assim, nenhuma dessas alternativas forem possíveis, então voce poderia criar uma classe que expõe as novas propriedades do usuário, populando-a da forma tradicional, operando de forma independente a classe MembershipUser.

Membership: Geração/Reset de Password

Se voce está utilizando as funcionalidades de autenticação do ASP.NET 2.0, então é provável que voce já se deparou com a necessidade de recuperar/redefinir (PasswordRecovery) a senha de uma determinado usuário. Há um detalhe importante a ser observado quando voce habilita esta funcionalidade.

O comportamento deste controle merece uma atenção especial para os dois cenários suportados:

  • enablePasswordRetrieval = true: quando esta opção está definida como true, obriga voce também a definir a propriedade passwordFormat para um valor diferente de Hashed. Se esta combinação de valores for atendida, a senha será recuperada da base de dados e enviada para o e-mail do respectivo usuário.
  • enablePasswordRetrieval = false: esta configuração é geralmente utilizada quando a propriedade passwordFormat é definida como Hashed, ou seja, não será possível recuperar a senha da base de dados, pois ela foi hasheada. Neste caso, quando o usuário utilizar o controle PasswordRecovery, então ele gerará uma nova senha e, é neste momento que começa toda a confusão.

O processo para a geração da nova senha invoca, indiretamente, o método GeneratePassword do provider selecionado que, na maioria dos casos, é o SqlMembershipProvider. Dentro deste método há uma chamada para o método estático GeneratePassword da classe Membership. A confusão ocorre aqui por causa de dois atributos que são configurados no arquivo Web.Config: minRequiredPasswordLength e minRequiredNonalphanumericCharacters. Esses valores servem apenas para validação de senhas informadas pelo usuário mas, para o processo de geração automática (reset), são desprezados.

O SqlMembershipProvider tem um comportamento padrão que é: se o valor informado no atributo minRequiredPasswordLength for menor que 14, ele assume 14; o valor do atributo minRequiredNonalphanumericCharacters é sempre informado mas, pela lógica implementada, não mudará em nada.

O que voce precisa fazer é sobrescrever o método GeneratePassword da classe SqlMembershipProvider e, colocar a regra necessária para mudar o comportamento padrão. Eis aqui uma possível implementação:

using System.Security.Cryptography;

public class CustomMembershipProvider : SqlMembershipProvider
{
    private static char[] chars = “a0bc1d2efgh3i4jkl5m6n7o8p9qrstuvuyzw”.ToCharArray();

    public override string GeneratePassword()
    {
        if (this.MinRequiredNonAlphanumericCharacters == 0)
        {
            byte[] randomBytes = new byte[this.MinRequiredPasswordLength];
            char[] result = new char[this.MinRequiredPasswordLength];
            new RNGCryptoServiceProvider().GetBytes(randomBytes);

            for (int i = 0; i < this.MinRequiredPasswordLength; i++)
            {
                int index = ((int)randomBytes[i]) % chars.Length;
                result[i] = chars[index];
            }

            return new string(result);
        }
        else
        {
            return base.GeneratePassword();
        }
    }
}

//Configuração (Web.Config):
<membership defaultProvider=”SqlMembershipProvider”>
    <providers>
        <clear/>
        <add
            name=”SqlMembershipProvider”
            type=”CustomMembershipProvider”
            minRequiredNonalphanumericCharacters=”0″
            minRequiredPasswordLength=”5″/>
    </providers>
</membership>

SSL no IIS 5/6

Caso voce esteja utilizando o IIS 5 sob Windows XP Professional e, por algum motivo, precisa de SSL para efetuar algum teste, voce pode baixar o IIS 6.0 Resource Kit Tools que, entre várias ferramentas, possui uma chamada SelfSSL 1.0, que faz os ajustes necessários dentro do IIS para que voce possa invocar seus Websites a partir de um endereço HTTPS.

Essa ferramenta nada mais é do que um utilitário de linha de comando que, basta digitar SelfSSL e, em seguida, confirmar teclando “Y”.

Propriedade ApplicationName

Quando instalamos a estrutura de Membership do ASP.NET 2.0, temos uma tabela que armazena o nome das aplicações (aspnet_Applications) que estão sendo acomodadas dentro daquele servidor SQL Server. Essa tabela possui uma coluna chamada ApplicationName e, se valor é exposto através de uma propriedade estática, também chamada ApplicationName, da classe Membership.

A propriedade ApplicationName da classe Membership trata-se de uma propriedade de escrita/leitura. Sendo assim, voce pode alterar o nome da aplicação em tempo de execução e, a partir dai, passar a executar todos os métodos baseados nesta aplicação. Eis um exemplo:

Response.Write(Membership.ValidateUser(“israel”, “passw0rd”).ToString());
Membership.ApplicationName = “NomeDaOutraAplicacao”;
Response.Write(Membership.ValidateUser(“israel”, “passw0rd”).ToString());

Apesar de funcionar, devemos nos atentar a questão do acesso em um ambiente multithreading, como é o caso do ASP.NET. Como a estrutura de Provider Model armazena e compartilha uma única instancia do provider ativo (padrão Singleton), a alteração em um local refletirá em todas as requisições e, conseqüentemente, podemos ter comportamentos inesperados. Geralmente isso só funcionaria em uma aplicação administrativa, que permitisse apenas um único usuário por vez acessando-a.

Credenciais de ASP.NET para ASP

Recentemente estive envolvido em um problema que muita gente acredito já ter passado. Trata-se de enviar dados de uma página ASP.NET para uma página ASP que encontra-se dentro da mesma aplicação (mesmo diretório virtual). Para ser mais preciso, precisava enviar dados as credenciais de acesso (login) para a página ASP.

Para enviar os parametros, poderia ser via querystrings ou cookies, mas lembrando que é preciso aplicar algum algoritmo de criptografia para garantir que nenhum espião consiga interceptar o processo e, consequentemente, visualizar o que está ali dentro. Para tornar as coisas um pouco mais complicadas, a solução mais interessante é mandar esses dados via headers, para ficar “mais seguro”. Como tudo está no mesmo servidor/aplicação, a solução para isso é criar um handler, herdando de DefaultHttpHandler que, por sua vez, fornece uma propriedade chamada ExecuteUrlHeaders que permite adicionar valores nos headers. O código abaixo ilustra essa classe:

public class TransferHandler : DefaultHttpHandler
{
    public TransferHandler() { }

    public override string OverrideExecuteUrlPath()
    {
        base.ExecuteUrlHeaders.Add(“AppUserName”,
            base.Context.User.Identity.Name);
        return null;
    }
}

O arquivo Web.Config fica:

<httpHandlers>
  <add path=”*.asp” type=”TransferHandler” verb=”GET,HEAD,POST” validate=”true”/>
</httpHandlers>

Com isso, todas as requisições para as página *.asp serão interceptadas por esse handler que fará o trabalho de capturar a informação de login, e enviá-la para a página *.asp. Com isso, nas páginas *.asp podemos fazer:

Response.Write Request.ServerVariables(“HTTP_APPUSERNAME”)

Lembre-se que o ideal é aplicar um algoritmo de criptografia/hash para “salgar” as coisas. O hash, ao meu ver, seria mais interessante, já que voce define uma chave privada (que somente voce conhece) e a utiliza para efetuar o hash do valor. Isole essa classe em um componente e exponha-o via COM, para que voce consiga acessá-lo na aplicação ASP. Neste caso, voce poderá enviar, via headers, o valor puro e também um novo item que é o resultado hash desse valor. Quando chegar na página de destino, voce pega ambos os dados, e valida através do mesmo componente (e mesmo algoritmo), para verificar se o valor não foi alterado durante a sua viagem.

Barrando acesso à elementos/atributos

Recentemente estive envolvido em um projeto simples mas um tanto quanto interessante. Estávamos fazendo a configuração de um servidor Web e todas as aplicações ASP.NET 2.0 que ali seriam hospedadas devem utilizar uma mesma base de dados para as funcionalidades de Membership, Roles e Profile.

A questão é que as aplicações podem ter um arquivo Web.Config e customizar essas configurações em cada uma delas. Só que isso não seria permitido. A solução foi até mais simples do que imaginava e, me forçou a olhar para alguns atributos que nunca prestei a devida atenção até então. Todos os elementos que são colocados nos arquivos de configuração herdam (diretamente ou indiretamente) da classe ConfigurationElement. Essa classe possui 5 principais propriedades (ou atributos):

  • lockAllAttributesExcept: Informa que todos os atributos de um elemento serão bloqueados, com exceção dos atributos que estão contidos nesta lista.
  • lockAllElementsExcept: Informa que todos os elementos de um elemento “pai” serão bloqueados, com exceção dos elementos que estão contidos nesta lista.
  • lockAttributes: Bloqueia apenas os atributos contidos nesta lista.
  • lockElements: Bloqueia apenas os elementos contidos nesta lista.
  • lockItem: Bloqueia o item como um tudo e, conseqüentemente, todos os seus “filhos”.

Para que fosse possível isso, editamos o arquivo Machine.Config e lá colocamos todas as configurações necessárias para que as aplicações pudessem herdar as configurações estipuladas para Membership, Roles e Profile e não ter direito de sobrescreve-las. Com isso, o arquivo Machine.Config fica definido como:

<?xml version=”1.0″>
<configuration>
  <connectionStrings>
    <add
        name=”LocalSqlServer”
        connectionString=”CONN_STRING”
        providerName=”System.Data.SqlClient”
        lockItem=”true” />
  </connectionStrings>
  <system.web>
    <membership defaultProvider=”AspNetSqlMembershipProvider” lockAttributes=”defaultProvider”>
      <providers lockElements=”clear”>
        <add
            name=”AspNetSqlMembershipProvider”
            type=”System.Web.Security.SqlMembershipProvider, System.Web”
            connectionStringName=”LocalSqlServer”
            enablePasswordRetrieval=”false”
            enablePasswordReset=”true”
            requiresQuestionAndAnswer=”true”
            applicationName=”/”
            requiresUniqueEmail=”false”
            passwordFormat=”Hashed”
            maxInvalidPasswordAttempts=”5″
            minRequiredPasswordLength=”7″
            minRequiredNonalphanumericCharacters=”1″
            passwordAttemptWindow=”10″
            passwordStrengthRegularExpression=””
            lockAttributes=”connectionStringName;enablePasswordRetrieval;passwordFormat” />
      </providers>
    </membership>
  </system.web>
</configuration>

Obviamente que algumas aplicações devem customizar algum desses atributos e, justamente por isso, que nem todos os elementos/atributos foram bloqueados. Por exemplo, se desejar customizar o nome da aplicação no arquivo Web.Config de cada uma das aplicações, então podemos fazer:

<?xml version=”1.0″?>
<configuration>
    <system.web>
      <membership userIsOnlineTimeWindow=”10″>
        <providers>
          <remove name=”AspNetSqlMembershipProvider”/>
          <add
            name=”AspNetSqlMembershipProvider”
            type=”System.Web.Security.SqlMembershipProvider, System.Web”
            applicationName=”Teste” />
        </providers>
      </membership>
    </system.web>
</configuration>

Nada impede do desenvolvedor poder adicionar novos providers de memberships, roles e profiles mas, ele não poderá habilitá-lo, já que o atributo defaultProvider está bloqueado a nível superior. Um outro detalhe importante é podemos remover o provider especificado no arquivo Machine.Config através do elemento remove mas, sendo assim, ele não poderá utilizar as funcionalidades de Membership definidas para o servidor.

Propagando o contexto de segurança nas chamadas assíncronas

Nas versões anteriores do ASP.NET quando precisamos efetuar um processamento assíncrono através da Interface IHttpAsyncHandler, o método a ser disparado iria correr sob as credenciais do worker process, o que poderia causar problemas de segurança, já que o contexto de segurança da aplicação não era propagado para o processamento assíncrono.

O ASP.NET 2.0 resolve isso com as PageAsyncTasks onde, automaticamente, o contexto é propagado sem que o desenvolvedor precise se preocupar em escrever código para gerenciar isso.

Navegando pelo diretório do .NET Framework 2.0 em %windir%Microsoft.NETFrameworkv2.0 eu vi um arquivo que não conhecia: Aspnet.config. Este arquivo possui um elemento chamado runtime e, dentro dele, dois sub-elementos, a saber: legacyImpersonationPolicy e alwaysFlowImpersonationPolicy.

<?xml version=”1.0″ encoding=”UTF-8″ ?>
<configuration>
    <runtime>
        ……
        <legacyImpersonationPolicy enabled=”true”/>
        <alwaysFlowImpersonationPolicy enabled=”false”/>

    </runtime>
</configuration>

Para que o comportamento seja semelhante as versões anteriores, o elemento legacyImpersonationPolicy é definido como false e, sendo assim, o contexto não será propagado. Caso você não precise manter compartibilidade com aplicações em outras versões, você pode inverter os valores do atributo enabled de ambos elementos e, consequentemente, todo o contexto de segurança será propagado entre as chamadas assíncronas da aplicação, inclusive se utilizando a Interface IHttpAsyncHandler.