Arquivar abril 2017

Integrando o Django com o AD Microsoft

Em um projeto novo da SIGMA, nos foi solicitado que utilizássemos
os usuários registrados no Active Directory do cliente, significando
que teríamos de integrar toda a funcionalidade de login do nosso projeto
Django com este camarada.

Existem diversas razões para esta integração, principalmente no nosso
caso, em que o cliente final é uma grande empresa, com toda uma infraestrutura
de Tecnologia da Informação bastante sólida e madura.

Entre estas razões, se encontram, em ordem de prioridade:

  1. Cliente não quer mais um usuário/senha para usar um sistema especialista;
  2. Equipe de TI não quer gerenciar os cadastros dos usuários em mais um sistema;
  3. Economia no desenvolvimento, de tempo e dinheiro;

Como funciona?

O Django, como um framework, já possui toda um maquinário importante para
criação e manutenção de permissões e grupos. Mas existe uma “pegadinha”:
o usuário em questão precisa existir no banco de dados que estamos usando.

Isto porque as permissões e grupos estão armazenadas em um banco de dados
relacional e existem relações com chaves estrangeiras entre usuários, permissões
e grupos.

O fluxo mais simples, portanto é:

  1. Usuário se autentica com a senha do Active Directory;
  2. Usuário está inativo no AD? Caso esteja, impeça o login e inative o usuário local;
  3. Usuário não está inativo, confira a senha;
  4. Sistema confere se o usuário já existe na base local;
  5. Caso ele já exista, ok, não faça mais nada;
  6. Caso ele não exista, copie os dados do usuário e crie um novo usuário na base local;
  7. Associe permissões/grupos;
  8. Vitória;

Seguindo este fluxo, todos os componentes do Django continuam a funcionar e não
precisamos criar permissões remotas no AD (ou LDAP) o que tornaria tudo
mais complicado de administrar (pense nas migrações do Django).

Outro ponto importante, delimitador deste fluxo, é a necessidade das sessões HTTP
no Django. Sem existir um usuário na base, não conseguiremos manter as sessões. Teríamos
de adaptar também o componente que registra e valida as sessões ativas.

Portanto esta arquitetura de cópia de usuários é a mais comum para integração
em bases de usuários “remotas” (este fluxo também é usado em pacotes que realizam
autenticação e integração com outros provedores de login, como OAuth2, por exemplo).

Como realizar a integração?

Felizmente, o Django é um projeto bastante modular. Neste caso em específico,
o Django aceita a configuração de settings de autenticação, permitindo
inclusive a autenticação em múltiplos backends, em cascata. Caso a autenticação
do usuário não funcione em um backend, ele tenta no próximo.

A arquitetura do Django facilitou bastante nossa vida neste sentido.

Quais são os passos:

Configurar o AUTHENTICATION_BACKENDS em seus settings.py

Desenvolver ou reutilizar um pacote open-source que já faz a conexão
e validação do usuário no Active Directory. No nosso caso, utilizamos o
django-python3-ldap.

Configurar as variáveis do django-python3-ldap:

# settings.py
LDAP_AUTH_USER_FIELDS = {
  "username": "uid",
  "first_name": "givenName",
  "last_name": "sn",
  "email": "mail",
}
LDAP_AUTH_USER_LOOKUP_FIELDS = ("username",)
LDAP_AUTH_CLEAN_USER_DATA = "django_python3_ldap.utils.clean_user_data"
LDAP_AUTH_SYNC_USER_RELATIONS = "jango_python3_ldap.utils.sync_user_relation"
"  LDAP_AUTH_FORMAT_SEARCH_FILTERS = "django_python3_ldap.utils.format_search_filters"
# Atenção neste ponto, pois ele é necessário para funcionar com o AD
LDAP_AUTH_FORMAT_USERNAME = "django_python3_ldap.utils.format_username_active_directory"```

Para a url indique conforme abaixo substituindo <ip_ou_dominio>
por seu ip ou dominio:

# settings.py
LDAP_AUTH_URL='ldap://<ip_ou_dominio>:389'

Utilize o comando dsquery user no ms-dos para obter a seguinte informação informação:

C:\Users\csantos>dsquery user
"CN=Claudio Santos,CN=Users,DC=server,DC=local"
"CN=Guest,CN=Users,DC=server,DC=local"

Utilize os três critérios de pesquisa, resultando em: LDAP_AUTH_SEARCH_BASE = 'CN=Users,DC=server,DC=local'

Comumente temos dois tipos de usuários: InetOrgPerson ou Users. Nós utilizamos o Users,
configurando LDAP_AUTH_OBJECT_CLASS = 'Users'

Configure este settings com uma variável que podemos pegar na
tela de criação de usuário no campo em destaque na imagem,
ela representa o Pre-Windows 2000 Domain Name do Active Directory:

attribute-editor-uid

# settings.py
LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN = 'SERVER'

Por fim vá em Active Directory Users and Computer, vá na pasta Users,
em seguida, clique com o botão direito, propriedades e por fim na aba Atributte Editor
informe o login do usuário no atributo uid, como mostrado na imagem abaixo;

attribute-editor-uid

Conclusões

Não vou discorrer aqui sobre os benefícios técnicos de usar open-source ou
coisa do tipo, mas sim dos benefícios gerados por uma integração deste nível.

As vantagens auferidas tem um profundo impacto psicológico sobre o usuário final
e sobre o retorno sobre investimento.

Em primeiro lugar, o sistema parece ter sido construído e fazer parte de fato
de toda a organização. O usuário não tem a impressão de sair de seu ambiente tecnológico,
confortável e seguro, para trabalhar em um sistema externo.

Como segundo ponto, o usuário tem uma barreira menor para utilizar o sistema.
A adoção de novos sistemas em organizações grandes sempre foi e sempre será
uma dificuldade, pelo próprio fator humano. Como a barreria de início é menor,
maior será a adoção dos usuários.

Em terceiro lugar, mas consequência do segundo ponto, quanto maior a adoção
dos usuários, maior o impacto que o novo sistema terá na organização, trazendo
um retorno maior para os stakeholders do projeto.

Como escrever bons relatórios de bugs

Escrever software é dificil. Manter um software atualizado e em funcionamento
sem defeitos, é ainda mais dificil.

Uma das ferramentas mais importantes para que isso ocorra, isto é, a criação
e manutenção de um bom software, com o mínimo de defeitos (nenhum software é perfeito), é sem dúvida, os relatórios de
defeitos, ou bug reports.

Eles podem ser criados por qualquer pessoa que use de fato o software, qualquer
que ele seja. Alguns relatórios de bugs (chamaremos apenas de bugs, daqui por)
diante serão escritos por desenvolvedores, que notarão comportamentos estranhos
ainda na etapa de desenvolvimento. Outros serão escritos por testadores profissionais,
que ganham o pão procurando a desgraça alheia. E outros bugs, serão encontrados
apenas quando o nosso software estiver em produção – pelo usuário final.

Não importando quem escreveu, todo e qualquer bug merece um relatório e uma
investigação formal. O quanto antes, melhor. Bugs tendem a ficar cada vez mais
caros, conforme o tempo passa. Estima-se que um bug descoberto em produção custa 10 vezes mais o valor do que se ele tivesse sido encontrado antes – durante o desenvolvimento.

A conta é simples: se o bug tivesse sido encontrado durante a etapa de desenvolvimento, o desenvolvedor gastaria 2 horas para resolvê-lo e adicionar
um teste de unidade, afim de garantir que o mesmo não retorne para assombrar ninguém,
anos depois.

Se o bug é encontrado em produção, serão gastos, aproximadamente, 20 horas para
resolvê-lo. Senão mais. Tudo em produção é mais difícil.

Certo, bugs são caros, mas e daí? Bem, se podemos resolver o bug em desenvolvimento
e uma das principais formas de se resolver um bug é criando um bug report,
bons relatórios de bugs facilitam a vida dos desenvolvedores, na hora de encontrar
o danado e resolvê-lo de uma vez por todas.

Maus relatórios de bugs deixarão os desenvolvedores e usuários irritados. Bugs
com títulos e descrições inadequadas serão muito mais difíceis de serem resolvidos,
pois são mais difíceis de serem encontrados, terem o comportamento exato reproduzido,
assim por diante.

Uma máxima importante é:

Um bom relatório de bug descreve o problema de forma que o desenvolvedor familiarizado com o projeto pode entender e resolvê-lo, sem falar com a pessoa que o escreveu.

Esta é uma tradução generalizada de uma [postagem do martiancraft][martincraft-bug-report]. Realmente, devo concordar com o título.

Pequena estória: uma certa vez, trabalhando com um cliente – sem expertise técnica, decidiu que gostaria de escrever seus próprios relatórios de bugs. Ótimo, pensei, uma coisa a menos que terei de fazer. Este cliente nunca havia utilizado ou mesmo visto um relatório de bug.

Quando ele começou a escrevê-los, ele colocava títulos estranhos, descrições que não mencionavam como reproduzir o mesmo e tentava explicar problemas de desenho da tela, utilizando palavras – sem um simples screenshot se quer. Tudo bem, pensei, ele apenas precisa aprender a escrever melhores relatórios de erros e nossa vida será muito mais fácil. Enviei alguns links para ele e a situação melhorou.

Os bugs eram corrigidos mais rapidamente, pois gastávamos menos tempo tentando entender o que ele quis dizer com aquilo. Facilitou a comunicação sobre outras funcionalidades que ele queria para seu produto. Entre diversas outras coisas.

Então, confiem em mim: um bom relatório de erros é essencial para a vida do desenvolvedor e do maior interessado, o cliente.

Então, o que todo bom relatório de bugs deve conter?

Apenas um problema por relatório

Apenas um problema por relatório de defeito. Apenas. Um. Problema. Por. Relatório.

Não existe forma de deixar isto mais claro. Quando você tem um ou mais defeitos em um mesmo relatório de bugs, isto confunde o desenvolvedor, dificulta a correção e dificulta principalmente o teste da correção.

Outra: os dois problemas andam juntos, são corrigidos juntos e aprovados juntos. Em uma equipe, estes problemas poderiam ter sido resolvidos em paralelo, fechados com timelines distintas (um bug era fácil, o outro difícil) e com maior qualidade.

Nunca reporte mais de um bug em um único relatório. Nunca. Mesmo se eles forem relacionados. Cada bug é importante o suficiente para merecer seu próprio relatório.

Título e Descrição

Um bom título é essencial para avaliação rápida do problema. Títulos que não esclarem a situação, ou mascaram o problema real, são as formas erradas de começar a escrever um relatório de erros.

Um bom título ajuda o desenvolvedor a realizar a triagem do bug, algo como: “ah é só um texto na tela”, “faltou uma validação aqui e ali” e “rapaz…tenho menor ideia do que diabos é isso”. Não só ajuda a identificar a criticidade do bug, mas também mais ou menos de onde vem o problema, facilitando a delegação da resolução para um ou outro desenvolvedor ou time.

Já as descrições são os lugares para se dar detalhes sobre o bug. Contexto, informações do ambiente, passos para reprodução do bug, dados para reprodução do bug, temperatura do ar, pressão atmosférica e humor do chefe no dia.

Sem essas informações cruciais, fica muito mais difícil reproduzir o bug, tornando o mais caro para a empresa e para o cliente.

Exemplos:

  • RUIM: “A aplicação não responde”;
  • BOM: “A aplicação não responde após a carga do arquivo XPTO na tela FOO”;

  • RUIM: “Texto fora do padrão”;

  • BOM: “Texto do título ‘Pesquisa’ está desformatado”;

  • RUIM: “Não consigo salvar imagens”;

  • BOM: “Dentro da tela de redimensionar imagens, ao tentar salvar a mesma após cortar 30% da largura da imagem, não consigo salvar as imagens”;

Acho que deu para entender.

Passos detalhados e comportamento esperado

Certo, temos nossos bugs, mas como recriar a situação? A primeira coisa que um desenvolvedor fará, é tentar recriar a situação para ver se o erro encontrado não é esporádico, se tem relação com as marés ou a lua, ou se é realmente um defeito encontrado consistentemente.

Defeitos esporádicos, como, “relatório não pode ser gerado durante os meses que possuem 31 dias” existem e são legítimos, mas o desenvolvedor primeiro irá procurar se ele não vem de uma má configuração ou algum passo errado que o usuário possa ter executado no momento de usar a aplicação.
Defeitos consistentes são rapidamente avaliados pois temos certeza de quais passos realizar para reproduzir o erro. Sem os passos, o desenvolvedor deverá contar com a sorte para reproduzir o danado – tarefa tediosa e que pode não resolver o problema.

Vamos dizer que um erro é encontrado em apenas 1/10 das vezes em que se executam uma tarefa, aleatoriamente. O desenvolvedor testou 9 vezes, mas o erro não se apresentou. O que ele irá pensar? Que não existe erro algum e talvez o usuário estivesse em uma versão antiga, ou o conjunto de dados mudou.

Mesmo que o responsável por reportar o defeito não saiba exatamente quais ações foram realizadas, em detalhes, para incluir no relatório, um conjunto mínimo de passos ajuda muito.

Exemplo:

  • RUIM: “Loguei no sistema e cliquei em imprimir no relatório.”;
  • BOM:
    • Logar no sistema com usuário que tenha permissão para gerar relatórios;
    • Clicar na seção de relatórios;
    • Escolher o relatório de faturamento;
    • Agrupar por mês e filial;
    • Clicar em gerar relatório;
    • Sistema apresenta uma tela azul;
    • Sistema deveria apresentar uma tela com um relatório em PDF, contendo o relatório de faturamento mensal, agrupado por filial;

A diferença entre os dois é gritante.

Quanto ao comportamento esperado, ele também é fundamental para quem for testar o bug e confirmar que o mesmo está resolvido. Além dos passos de testes, o comportamento esperado é o prego no caixão do defunto – ele tratá a certeza para o desenvolvedor de que o problema morreu.

Contexto

Informações de contexto são cada vez mais importantes, conforme os mesmos são distribuídos por uma gama muito maior de clientes – a web, por exemplo.

Um bom contexto contém:

  • Sistema operacional e versão;
  • Navegador utilizado e versão (em aplicações web);
  • Versão do software que está sendo utilizado;
  • Informações relevantes ao uso do sistema (dados, usuários, grupos e permissões, etc);
  • Condições especiais (com GPS do celular ligado, com pouca bateria no notebook, com a rede ligada, etc);

Em casos extremos, como aplicações gráficas ou desktops, qual placa de vídeo, qual resolução utilizada, etc. Quanto maior o número de detalhes, mais fácil.

Prioridade

Não confunda prioridade com criticidade. Prioridade é o quanto um bug impacta no dia a dia do usuário. Um bug crítico em uma funcionalidade usada apenas uma vez no ano, é muito importante, mas um bug de severidade média em uma ferramenta utilizada todos os dias tem prioridade muito maior de resolução.

Screenshots

Ah, os screenshots. Eles são muito úteis para provar que o usuário não está ~~louco~~ sonhando. Brincadeiras à parte, os screenshots colocam o desenvolvedor numa máquina do tempo e na máquina do usuário. Principalmente se o bug envolver qualquer tipo de tela ou interface de usuário.

Não é tão difícil, então, sempre que possível, tire um screenshot, desenhe em cima dele, aponte uma seta para o problema. O desenvolvedor será muito grato.

Quando fechar um bug

Existe ciclo de vida natural de um bug, que varia de empresa para empresa, mas o mais simples geralmente acontece assim:

  1. Defeito é encontrado;
  2. Bug é relatado;
  3. Desenvolvedor faz uma triagem do bug;
  4. Desenvolvedor resolve o bug;
  5. Desenvolvedor altera o status do bug para o relator conferir se o problema realmente foi resolvido;
  6. Relator fecha o bug (ou reabre, caso o conserto não tenha sido muito bom – neste caso, volte para item 3);

Este é o fluxo mínimo que temos de trabalho. Quando o bug resolvido chegar na “mesa” do relator, ele deve separar um tempo para fazer o teste da resolução, o quanto antes melhor e dar andamento nos processos.

Se outro defeito foi encontrado, crie outro bug. A maior parte das ferramentas atuais permitem que você referencie bugs e/ou tarefas, facilitando o desenvolvedor a acompanhar o histórico.

Pensamentos finais

Escrever relatórios de erros não é difícil. É um processo em que se colabora com a construção do software, então deve ser minimamente organizado e coerente. Desenvolvedores não tem bolas de cristal e não são hackers do tipo Matrix, onde vem tudo em código o tempo todo.

Um bom relatório de bugs é certamente apreciado e muito mais legal de ser resolvido do que um mau relatório. Seja bonzinho e nos ajude a te ajudar :D.

Links interessantes

TDD (Desenvolvimento Guiado por Testes)

A primeira pessoa a apresentar este método de desenvolvimento foi Kent Beck, autor do livro Extreme Programming, e relata em um capitulo de seu livro a necessidade de testar prematuramente, frequentemente e automaticamente, destacando que tal abordagem é necessária, pois quanto mais cedo encontrarmos erros mais barato será consertá-lo. Três anos mais tarde ele lança o livro Test Driven Development by example definindo uma estratégia de desenvolvimento que além de permitir detectar erros prematuramente, cria um ambiente de trabalho que favorece o desenho de uma boa arquitetura.

A principal característica do TDD é sua simplicidade, onde a principal regra a seguir é: criar um teste que falhe, escrever o código para o teste passar e em seguida refatorar seu código.

Perceba que não foi dito que uma linguagem é preferível que outras, nem que uma IDE é necessária para trabalhar desta forma. É claro que será necessário um framework de testes e o Kent Beck criou um que foi base para todos os outros que surgiram, o JUnit.

Certo, neste ponto começam a surgir as dúvidas, sobre como definir os métodos de testes, qual a granularidade do meu teste e por onde começar.

Um sistema sempre vai começar a partir dos requisitos ou estórias, uma boa documentação que defina quais serão os comportamentos do software.

Nós, na Sigma, descrevemos o comportamento de nosso sistema utilizando estórias. Então vamos escrever uma estória hipotética que define o comportamento de um sistema.

Eu como comprador

quero adicionar produtos comprados ao estoque

para manter o saldo em estoque atualizado;

Analisando a estória acima podemos extrair algumas entidades que o sistema de almoxarifado deve ter: Produto e Estoque. Além disso podemos visualizar os comportamentos esperados: dar entrada no estoque de um produto, consultar saldo de um produto em estoque;

Então podemos criar um teste que falhe para um dos comportamentos:

import unittest

class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):
        produto = Produto()

Este teste vai falhar, pois não existe uma classe Produto definida em nosso sistema.

import unittest


class Produto(object):
    def __init__(self):
        pass


class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):
        produto = Produto()

O código vai passar então podemos refatorar e criar corretamente um módulo e retirar o código da classe produto do module de teste;

Damos o segundo passo na implementação:

import unittest

class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):
        produto = Produto()
        estoque = Estoque()
        quantidade = 10
        estoque.entrada(produto, quantidade)

Perceba que avançamos alguns passos extras na implementação, pois temos experiência na criação de classes e podemos ser flexíveis e escrever mais trechos de código que temos fluência. Esta flexibilidade não pode ser usada em excesso, pois o principal objetivo ao escrever código que falhe é que esta falha seja fácil de ser encontrada, portanto se eu escrever muitas linhas de código eu posso gastar muito tempo consertando ele. Portanto aprecie com moderação.
O código acima está falhando, pois o método entrada não está implementado.

Portando vamos implementá-lo:

class Estoque(object):

    def __init__(self):
        self._estoque = []

    def entrada(self, produto, quantidade):
        self.estoque.append({
            'produto': produto,
            'quantidade': quantidade
        })

Novamente o teste vai passar, mas como definir que este teste é válido?

Asserções!

Asserção é um método para validar se uma condição de nosso teste é válida e caso ela não seja, minha asserção não será válida e vai lançar uma exceção.

import unittest

class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):

        # Definições
        produto = Produto()
        estoque = Estoque()
        quantidade = 10

        # Ações
        estoque.baixar(produto, quantidade)

        # Asserção
        saldo = estoque.saldo(produto)
        self.assertTrue(saldo == 10)

Perceba a estrutura de um testes, eu defino o estado inicial para meu teste, tomo uma ação e verifico se meu teste chegou ao estado esperado.

Este artigo serviu de base para o screencast sobre TDD, que foi gravado e está disponível neste link, com a apresentação também disponível neste link.

Geoprocessamento Online na Gestão Pública

Para a grande maioria dos municípios brasileiros o gerenciamento espacial de propriedades e lotes é um problema crônico que demanda solução urgente para governanças. A exigência por modelos de gerenciamento práticos e transparentes, oferencendo bases reais e confiáveis para tomadas de decisão, nunca foi tão pertinente..

Para entender a gravidade do tema é preciso observar, por exemplo, a execução e desenvolvimento de um plano diretor municipal, condizente com expectativas sociais e urbanas. A ação depende de soluções eficientes para administração espacial dos territórios. Tratam-se de ferramentas que já estão disponíveis no mercado mas que foram apropriadas por poucos governos municipais.

A atualização dos modelos tradicionais de geoprocessamento deve ser tratada como prioridade. A falta de investimento em tecnologias eficientes, melhor adaptadas à administração pública, pode custar caro às contas municipais e, consequentemente, para a população que vive nelas. Para entendermos a dimensão dos prejuízos basta considerar que praticamente todos os aspectos urbanos se processam em uma unidade geográfica organizada definindo as propriedades e prioridades de um município. O que isso quer dizer? Basicamente que, no âmbito da administração pública, quando o município não investe em soluções de geoprocessamento urbano, ele deixa de investir no planejamento da cidade e nas estruturas institucionais dela. Do transporte público à saúde, do zoneamento à educação, o uso de geoinformação e geotecnologias otimiza toda estrutura e serviços do município antes mesmo deles serem inaugurados.

Tecnologias de geoprocessamento eficientes, quando aplicadas à administração pública, garantem o acesso à informações integradas promovendo a gestão do território. A sociedade, dessa forma, usufrui, a longo termo, de serviços públicos mais eficientes e de melhor qualidade. Trata-se, portanto, de um investimento de baixo custo relativo que promove a sustentabilidade econômica e social do município a médio e longo prazos.

Sistemas web centralizados, com a representação cartográfica dos espaços municipais a partir de informações técnico-descritivas potencializam a gestão municipal. O uso destas ferramentas permite a disseminação de conteúdo entre todos os órgãos municipais e para o público, tornando as ações da prefeitura mais eficientes, transparentes e econômicas. A tecnologia para esse upgrade está disponível no mercado e registra grandes benefícios entre os municípios que implantaram sistemas de geoprocessamento digitais.