Autenticação e Autorização – um caso interessante…


Há umas 2 semanas tive um desafio interessante para resolver, por isso resolvi postar aqui o “causo”, pois ele é mais comum do que se imagina.

Um cliente em início de projeto possui os usuários no Active Directory (mais de 10.000), porém o AD não tem nenhuma configuração de roles. As roles ficam armazenadas em uma tabela de banco de dados e são gerenciadas por outro sistema. Ou seja, a  autenticação é feita em um lugar e a  autorização  é feita em outro.

Aparentemente esta configuração é mais comum do que se imagina: o AD é usado apenas para usuários e senhas, e outro sistema é usado para a parte de autorização…

O UCM pode usar o AD como diretório de usuários. Isto é feito através de um componente fornecido pela Oracle, e o mapeamento é feito na interface visual:


Image 1

Um Provider é um elemento externo que vai ser usado pelo repositório: um banco de dados, um diretório LDAP, uma ferramenta de conversão, etc. Neste caso, cadastramos o AD como um provider que será usado para autenticação. Você pode ter mais de um LDAP configurado, em ambientes de múltiplos domínios ou para contingência.

O problema aqui é que o provider pede o mapeamento das roles (campo  Role Prefix). E, como o AD não tem role configurada, os usuários entrariam no sistema como  guest (role padrão para usuários não-autenticados).

Para resolver este problema, temos 3 soluções, da mais para a menos indicada:

  1. Usar um produto da Oracle chamado Virtual Directory. Este produto pode consolidar os dados do AD e do Banco em uma visão consolidada, e expor uma interface LDAP. Desta forma, precisaríamos apenas registrar o Virtual Directory no Content Server como um provider, que não precisaria lidar com a complexidade de duas origens de autenticação.
  2. Customizar o provider para autenticar os usuários no AD, mas ir ao banco de dados para pegar as roles.
  3. Separar as tarefas: Usar o Web Server (Apache, neste caso) para forçar a autenticação dos usuários (usando um plugin chamado mod_ntlm), e mapear o provider no Content Server apenas para trazer as roles.

    Neste caso optamos pela opção 2. Na figura acima, repare no campo  Provider Class. Este campo se refere à classe Java que irá fazer a comunicação com o AD. É esta classe que precisamos modificar: ldap.ActiveDirectoryLdapProvider


    *** DISCLAIMER *** Antes de fazer este tipo de alteração no seu ambiente, entre em contato com o suporte técnico Oracle. O procedimento deste artigo não é oficial e não foi testado apropriadamente. As instruções abaixo tem caráter meramente informativo. Siga estas instruções por sua conta e risco. A Oracle Corporation e o autor deste artigo se isentam de qualquer responsabilidade por problemas causados por esta configuração.

    Se você tem um cenário parecido, (e um espírito aventureiro :-)  vamos fazer as modificações no Provider. O ideal é criar um novo componente com base no componente atual (ActiveDirectoryLdapComponent), desta forma preservando o componente original. Para não deixar este post muito longo, vamos pular esta parte e ir direto ao que interessa:

Editando a classe

O ideal é você usar uma IDE de desenvolvimento para editar a classe. No meu caso, o JDeveloper foi a ferramenta escolhida:


Image 2

Para compilar esta classe, você precisa colocar 2 jars no classpath do projeto. Ambos estão na pasta %UCM_HOME%\shared\classes. Os arquivos são: server.zip e classes111.zip.

O método que nos interessa para este caso é o  protected Vector retrieveGroups(String dn). Ele recebe como entrada a string  dn, que representa o Distinguished Name no AD. Vejamos abaixo as características de um usuário do AD:


Image 3


Para o usuário Denis Abrantes, o Distinguished Name é:


CN=Denis Abrantes,CN=Users,DC=oracle-ecm,DC=br,DC=oracle,DC=com

O método original usa os seguintes comandos:

protected Vector retrieveGroups(String dn)
  throws
ServiceException
{
   LdapConnectionInterface con = getLdapConnection();
   Hashtable results = con.read(dn);
   Vector attributes = (Vector)results.get("memberOf");
  
if(attributes != null)
        return attributes;
  
else
        return new Vector();
}

Basicamente o que ele faz é conectar ao AD e buscar o atributo memberOf. Este atributo é o que contém as roles. O problema é que os nossos usuários do AD não tem roles, logo o atributo memberOf volta em branco. O que precisamos fazer é mudar a lógica, mas neste caso o método retorna um Vetor com a informação no formato do atributo memberOf. Por isso a nossa nova lógica deverá manter o mesmo padrão de string do atributo memberOf. Na imagem abaixo podemos ver as características de um usuário com roles no AD:


Image 4


Podemos ver que cada role está em um atributo memberOf diferente. Este é o motivo pelo qual o método retorna um Vetor, não uma String. A nossa busca precisa montar, para cada role, uma string do seguinte formato:


CN=<<Role>>,OU=Roles,OU=UCM,OU=Oracle,DC=oracle-ecm,DC=br,DC=oracle,DC=com

O nosso código, portanto, ficaria da seguinte forma:

protected Vector retrieveGroups(String dn)
  
throws ServiceException
{
  
Vector attributes = new Vector();
String usuariodn = dn.substring(3,dn.indexOf(","));

   String userrole = "";
     
try {
   Connection conn;
      String username = "stellent";
      String password = "*****";
      String thinConn = "jdbc:oracle:thin:@localhost:1521:xe";
      DriverManager.registerDriver(new OracleDriver());
      conn = DriverManager.getConnection(thinConn,username,password);
     
String comando = "select role from empregadoecm where 
      nm_empregado='"+usuariodn+"';

      Statement s = conn.createStatement();
      ResultSet rs= s.executeQuery(comando);
      rs.next();
     
while (!rs.isAfterLast())
        {
        
userrole = "CN="+rs.getString("role")+",OU=Roles,OU=UCM,OU=Oracle,
         DC=oracle-cm,DC=br,DC=oracle,DC=com"; attributes.addElement(userrole);
         
rs.next();
        }
       rs.close();
       }
      
catch ( SQLException ex ) {  }
     
if(attributes != null)
          
return attributes;
      else
          
return new Vector();
}


Observe as linhas em vermelho. Pegamos o nome do usuário na DN e usamos para buscar as roles deste usuário na tabela empregadoecm. Em seguida, incluimos esta role em uma string e adicionamos ao Vetor que será retornado.

Após compilar esta classe, só precisamos substituir a classe original, na pasta %UCM_HOME%\classes\ldap pela classe modificada e reiniciar o Content Server. Naturalmente o componente de integração com o AD já deverá estar instalado e mapeado no servidor.

Testando a Nova Classe

O usuário denis.abrantes não existe no Content Server, como podemos conferir pela imagem abaixo:

Image 5


No Active Directory ele não possui nenhuma role, e no banco de dados ele possui a role de Gerente. Quando fizermos o login no UCM com este usuário, ele será autenticado no AD e suas roles serão consultadas no banco de dados:

Image 6

A partir do login, o usuário passa a existir no Content Server, com os atributos herdados do AD e as roles, do banco:


Image 7

Outros diversos metadados podem ser importados do LDAP e definidos como atributos do usuário: cargo, departamento, email, ramal, etc.

Este exemplo mostra o poder de customização que o Content Server tem: com apenas algumas linhas de código, podemos mudar radicalmente a forma como um login de usuário acontece. Usando recursos como serviços, filtros e triggers, podemos mudar muitos outros comportamentos do servidor, como as rotinas de check-in, busca, workflow, etc.