Introdução
No post anterior, criamos um programa para servir de “Menu” para a Agenda e outras funcionalidades a serem criadas pela aplicação. Porém, não foi colocada nenhuma proteção para a execução do programa — Controle de Acesso ou similar. Vamos ver como fazer isso de forma segura e elegante, e ver alguns parágrafos sobre Segurança da Informação.
Controle de Acesso
Quando pensamos em restringir o acesso a uma determinada informação, precisamos avaliar o quão importante é a informação em si, e o quão desastroso seria se outra pessoa — além de você — tivesse acesso a esta informação. Determinado este fator, e ele sendo considerado de alta importância, precisamos ver quais são as formas ou caminhos de acesso a informação que podem ser utilizadas, e não apenas como protegê-las, mas o custo disso.
Quando me refiro a custo, não necessariamente estou falando de dinheiro, mas em custo de implementação — em horas — e custo de acesso a informação — desempenho da aplicação ser diminuída em virtude das barreiras de segurança. Um outro fator também importante é “Quanto vai me custar se eu perder a informação ?!” — já este aspecto também é tratado na Segurança da Informação, pois mesmo que a informação não tenha valor para outra pessoa, para você ela deve ter.
E você está sujeito a perder seus dados de várias formas — desde um ataque intencional para prejudicar você ou a sua Empresa, até mesmo um incidente com um ou mais equipamentos … por exemplo, a sala dos servidores ficava no subsolo da empresa, e devido a uma enchente, a água alagou a sala até o teto … danificando permanentemente os computadores e os discos de dados. Então você lembra que têm um BACKUP (cópia) dos dados, feito no dia anterior — que infelizmente estava em outro servidor, NA MESMA SALA 😐 Mas esse aspecto de segurança contra desastres a gente aborda em outro tópico … risos …
Usuário e Senha
Pensando inicialmente como um usuário da Agenda, e eu quero que somente pessoas que eu autorize tenham acesso para ler e mexer na minha Agenda, exigir na entrada do programa o fornecimento de um nome ou identificador do operador/usuário e uma senha já é um bom começo.
Se, por outro lado, eu quero definir que, um determinado usuário não pode ver ou alterar determinados contatos, ou não pode excluir ninguém da agenda, mas outro usuário pode, podem ser criados mais controles — como direitos de acesso e operação — que podem ser definidos por usuário ou ainda por grupos de usuários, e por operação. Quando trabalhamos com muitos usuários e direitos, torna-se vantajoso criar grupos de usuários e dar os direitos comuns ao grupo, e depois adicionar ou relacionar o usuário a um ou mais grupos. Por definição, um usuário que pertence a um grupo herda os direitos do grupo. Esta abordagem é muito comum em sistemas operacionais (Windows, Linux, Unix, etc.)
Restrições e/ou Liberações
Normalmente partimos de duas abordagens básicas: Restringir ou Liberar. Podemos partir de um sistema de segurança aberto — onde tudo é permitido — e inserir restrições — usuário pode tudo, exceto operações X e Y — ou partir de um sistema de segurança fechado — onde nada é permitido — e as operações ou recursos devem ser explicitamente liberados para um operador ter acesso as operações.
Ainda podemos trabalhar de forma mista, onde por exemplo as operações de um determinado recurso — cadastro de usuários, por exemplo — por padrão é bloqueado para todos, exigindo liberação para acesso, e o acesso aos dados da agenda — liberado por padrão — mas que, mediante a inserção de restrições, podem ter algumas operações ou mesmo o próprio acesso a rotina bloqueados.
Via de regra, um controle de acesso visa tratar a exceção. Logo, se a maioria dos operadores deve ter acesso a agenda, é mais fácil e custa menos inserir restrições quando e onde necessário. Por outro lado, se ter acesso a um determinado recurso é a exceção — via de regra a maioria não deveria ter acesso — então optamos por trabalhar com uma liberação.
Acesso externo (por fora da aplicação)
Eu posso não ter acesso ao programa de Agenda, porém o programa usa um Banco de Dados para armazenar as informações. Se o operador da máquina estiver usando o meu terminal, e o meu Banco de Dados tiver uma relação de confiança com o meu usuário local, eu abro a ferramenta de administração do Banco de Dados, e posso fazer o que eu quiser com os dados, desde consultar até excluir. Uma alternativa é criar o banco com um usuário diferente, com senha forte, e com autenticação explícita por usuário e senha.
Se eu tenho acesso ao computador onde a Agenda está instalada, mesmo que eu não tenha acesso ao Banco de Dados por dentro de uma ferramenta, eu posso tentar parar o serviço do Banco, copiar os arquivos de dados e logs do SGBD, e montar o banco em outro computador. A alternativa para proteger um acesso aos dados mediante cópia é habilitar uma criptografia dos dados no próprio Banco de Dados. Assim, mesmo que alguém consiga copiar o Banco inteiro, sem a chave de segurança ninguém vai entender o que está gravado ali. Alternativas como essa exigem planejamento e controle, pois se você mesmo perder a chave, nem você mais acessa os dados.
Se o seu equipamento está conectado na Internet, e você não tomou medidas de segurança, desde o procedimento de não instalar qualquer coisa de qualquer lugar no equipamento, e não bloqueou alguns acessos e serviços normalmente visados e com brechas conhecidas de ataque, ou colocou uma senha fraca em um usuário com direitos administrativos no seu equipamento, um acesso remoto na máquina tem praticamente o mesmo efeito como se o intruso estivesse realmente na frente do equipamento — quando ele pode estar a 2 quadras ou 50 mil quilômetros de distância.
RANSOMWARE
Um adendo especial, sobre um novo tipo de ataque, que já fez algumas vítimas pelo mundo, é o RANSOMWARE — uma aplicação nociva, que não visa roubar dados do equipamento, mas “sequestrá-los”. O aplicativo age como um Vírus ou Trojan, mas uma vez infectado o equipamento, ele se mete no acesso aos dados do disco, e criptografa arquivos, pastas, o que ele acha que deve ou os dados para o qual ele foi programado para criptografar. Em um determinado momento, ele pode simplesmente restringir o acesso às pastas e arquivos, ou mesmo apagar os arquivos originais, e exigir um pagamento para o fornecimento da chave capaz de voltar os dados que ele criptografou na sua forma original, ou exige uma senha para o próprio programa malicioso desfazer e restaurar os dados dos arquivos originais — normalmente com o pagamento de alguma importância em dinheiro não rastreável, como cripto-moedas.
Estudo de caso – Agenda
Por hora a ideia de controle de acesso na Agenda, além de fins didáticos, é começar de forma simples, por exemplo, para tratar um cenário onde eu quero que apenas minha esposa e eu possamos abrir a Agenda, mas sem bloquear nenhuma operação ou contato da agenda. Nós dois podemos ver todas as informações de todos os contatos da Agenda, não importa quem cadastrou o contato, e podemos fazer todas as operações com os contatos — como alterar dados, alterar foto, excluir o contato, enfim.
Neste caso, precisamos inicialmente de uma nova tabela, com pelo menos dois campos, um para o nome ou identificação do usuário ou operador — pode ser um nome, ou um e-mail, e outro campo para a autenticação de acesso, como por exemplo uma senha. Antes de implementar isso, vamos avaliar nossa necessidade e as possibilidades de se implementar esta solução.
Usuário ou e-Mail ?
Por ser um sistema local, um nome único de usuário já resolveria a questão de identificação. Porém, pensando um pouco além do óbvio, eu bem que poderia usar um e-mail. Afinal, lembra do que acontece quando você “perde a chave”? Nem você entra mais na sua casa.
Vantagens do e-Mail
- Se o próprio operador esquece da senha, e a aplicação é capaz de enviar um e-mail ao usuário, ele pode usar um recurso como por exemplo “Password Reset”, onde a aplicação gera uma chave temporária de acesso para aquele usuário, e envia a chave no e-mail dele. Somente ele deve ter acesso ao e-mail, logo, uma vez que ele acesse o sistema com a chave que o sistema gerou, a aplicação tenta garantir que é de fato aquele usuário que está realizando o acesso, e permite a ele redefinir sua senha de acesso.
- Dificilmente um usuário se esquece do próprio endereço de e-mail.
- Sem o e-mail, o usuário precisaria entrar em contato com o Administrador do Sistema, de alguma forma provar sua identidade, e pedir para ele uma troca de senha. Quando falamos de um sistema local de Agenda, pra usar em casa, sem problemas. Mas, pensando em algo um pouco maior, algo como a Agenda disponível na Internet, é um desperdício criar um suporte para atender usuário que perdeu senha, se você pode dar a ele um procedimento seguro de provar sua identidade e restaurar sozinho e com segurança seu acesso ao sistema.
Autenticando por Senha
Antes de mais nada, a respeito de qualquer tipo de senha em um sistema informatizado: Nunca grave a senha em um sistema de controle de acesso. Sim, é isso mesmo: NÃO GRAVE A SENHA.
Certo, então como vêm a pergunta: Se eu não gravar a senha, como eu vou conseguir validar a senha que o operador digitou na entrada do sistema está certa ?! Bem, primeiro vamos aos riscos: Uma tabela de usuários e senhas vale muito para pessoas mal-intencionadas. Muitos usuários na internet usam senhas fortes para diversos recursos, mas acabam usando a mesma senha para outros recursos. Logo, mesmo que você grave a senha que o operador digitou usando algum tipo de criptografia, se alguém descobre o algoritmo de descriptografia, ele terá acesso a todas as senhas de todos os usuários.
Uma alternativa bastante segura é gerar um hash (ou dispersão criptográfica) unidirecional. Na prática, você usa um algoritmo que é capaz de gerar uma sequência de dados a partir de uma informação fornecida, mas o algoritmo não é capaz de restaurar o dado original a partir da sequência de dados (hash) gerado. Um bom exemplo disso é o MD5 — vide referências no final do post. A partir de uma informação fornecida, o algoritmo MD5 gera uma sequência hexadecimal de 128 BITS, mas a partir desta sequência ele não é capaz de desfazer a operação e informar qual foi a informação que gerou aquele hash.
Logo, quando o usuário informar a senha de acesso que ele gostaria de usar, a aplicação gera um MD5 desta senha, e grava o resultado no banco de dados, atrelado a este usuário. No momento que este usuário for entrar no sistema, e fornecer o e-Mail e Senha, você localiza ele no cadastro pelo e-Mail, e gera novamente o MD5 da senha que ele informou. Se ele informou a mesma senha, o resultado do MD5 será o mesmo. Assim, nem você sabe a senha original, nem quem conseguir roubar ou copiar esta tabela vai saber.
Existe a possibilidade de colisão, isto é, duas senhas diferentes gerarem o mesmo hash. Porém, como estamos falando de um hash de 128 bits, sabe quantas são as possíveis combinações? 2^128 (dois elevado a potência 128), algo em torno de 3.40e+38 combinações diferentes — imagine que 64 bits = 18.446.744.073.709.551.615 ( dezoito quintilhões, quatrocentos e quarenta e seis quatrilhões, setecentos e quarenta e quatro trilhões, setenta e três bilhões, setecentos e nove milhões, quinhentos e cinqüenta e um mil, seiscentas e quinze possibilidades), agora multiplica esse número por dois, sessenta e quatro vezes seguidas 😛
Vamos ao AdvPL
Primeiro, vamos criar a função responsável pela criação e abertura da tabela de usuários. Ela é praticamente uma cópia com alterações da função de criação da tabela de Agenda.
// --------------------------------------------------------------
// Abertura da Tabela de USUARIOS da Agenda
// Cria uma tabela chamda "USUARIOS" no Banco de dados atual
// configurado no Environment em uso pelo DBAccess
// Cria a tabela caso nao exista, cria os índices caso nao existam
// Abre e mantém a tabela aberta em modo compartilhado
// --------------------------------------------------------------
STATIC Function OpenUsers()
Local cFile := "USUARIOS"
Local aStru := {}
Local aDbStru := {}
Local nRet
While !GlbNmLock("USUARIOS_DB")
If !MsgYesNo("Existe outro processo abrindo a tabela USUARIOS. Deseja tentar novamente ?")
MSgStop("Abertura da tabela USUARIOS em uso -- tente novamente mais tarde.")
QUIT
Endif
Enddo
// Cria o array com os campos do arquivo
aadd(aStru,{"IDUSR" ,"C",06,0})
aadd(aStru,{"LOGIN" ,"C",50,0})
aadd(aStru,{"SENHA" ,"C",32,0})
If !TCCanOpen(cFile)
// Se o arquivo nao existe no banco, cria
DBCreate(cFile,aStru,"TOPCONN")
Else
// O Arquivo já existe, vamos comparar as estruturas
USE (cFile) ALIAS (cFile) SHARED NEW VIA "TOPCONN"
IF NetErr()
MsgSTop("Falha ao abrir a tabela USUARIOS em modo compartilhado. Tente novamente mais tarde.")
QUIT
Endif
aDbStru := DBStruct()
USE
If len(aDbStru) != len(aStru)
// Estao faltando campos no banco ?
// Vamos alterar a estrutura da tabela
// Informamos a estrutura atual, e a estrutura esperada
If !TCAlter(cFile,aDbStru,aStru)
MsgSTop(tcsqlerror(),"Falha ao alterar a estrutura da tabela USUARIOS")
QUIT
Endif
MsgInfo("Estrutura do arquivo USUARIOS atualizada.")
Endif
Endif
If !TCCanOpen(cFile,cFile+'_UNQ')
// Se o Indice único da tabela nao existe, cria
USE (cFile) ALIAS (cFile) EXCLUSIVE NEW VIA "TOPCONN"
IF NetErr()
MsgSTop("Falha ao abrir a tabela USUARIOS em modo EXCLUSIVO. Tente novamente mais tarde.")
QUIT
Endif
nRet := TCUnique(cFile,"LOGIN")
If nRet < 0
MsgSTop(tcsqlerror(),"Falha ao criar índice único")
QUIT
Endif
USE
EndIf
If !TCCanOpen(cFile,cFile+'1')
// Se o Indice por ID nao existe, cria
USE (cFile) ALIAS (cFile) EXCLUSIVE NEW VIA "TOPCONN"
IF NetErr()
MsgSTop("Falha ao abrir a tabela USUARIOS em modo EXCLUSIVO. Tente novamente mais tarde.")
QUIT
Endif
INDEX ON IDUSR TO (cFile+'1')
USE
EndIf
If !TCCanOpen(cFile,cFile+'2')
// Se o indice por LOGIN nao existe, cria
USE (cFile) ALIAS (cFile) EXCLUSIVE NEW VIA "TOPCONN"
IF NetErr()
MsgSTop("Falha ao abrir a tabela USUARIOS em modo EXCLUSIVO. Tente novamente mais tarde.")
QUIT
Endif
INDEX ON LOGIN TO (cFile+'2')
USE
EndIf
// Abra o arquivo de agenda em modo compartilhado
USE (cFile) ALIAS (cFile) SHARED NEW VIA "TOPCONN"
If NetErr()
MsgSTop("Falha ao abrir a tabela USUARIOS em modo compartilhado. Tente novamente mais tarde.")
QUIT
Endif
// Abre os indices, seleciona ordem por ID
// E Posiciona no primeiro registro
DbSetIndex(cFile+'1')
DbSetIndex(cFile+'2')
DbSetOrder(1)
DbGoTop()
// Solta o MUTEX
GlbNmUnlock("USUARIOS_DB")
Return .T.
Para quem ainda está usando um binário 7.00.131227, que não têm as funções GlbNmLock() e GlbNmUnlock(), pode temporariamente substituí-las por GlbLock() e GlbUnlock() — a diferença é que o Lock realizado é global, isto é, desconsidera o nome informado como parâmetro.
Agora, vamos fazer a função de LOGIN, porém vamos atentar a um detalhe: Se eu não quiser habilitar o controle de usuários na minha agenda, eu simplesmente deixo o cadastro de usuários vazio.
// ---------------------------------------------------
// Função responsável pelo controle de acesso - Login
// Somente exige autenticação se o cadastro de usuários tiver
// pelo menos um usuário
// ---------------------------------------------------
STATIC Function ChkUser(oDlg)
Local lOk := .T.
// Abre cadastro de usuarios
OpenUsers()
// Vai para o topo do arquivo
DbSelectarea("USUARIOS")
DBGoTOP()
If !EOF()
// Se existem usuarios na tabela de usuarios,
// o login foi habilitado .
lOk := DoLogin(oDlg)
Endif
// Fecha o cadastro de usuarios
DbSelectarea("USUARIOS")
USE
If !lOk
MsgStop("Usuário não autenticado.","Controle de Acesso")
QUIT
Endif
Return
// Definições para uso da função AdvPL MD5()
#define RAW_DIGEST 1
#define HEX_DIGEST 2
// Função responsável pelo diálogo e validação do Login
STATIC Function DoLogin(oDlg)
Local cTitle := 'Controle de Acesso'
Local oDlgLogin
Local oGetLogin
Local cLogin := space(50)
Local cPassW := space(16)
Local oBtnOk
Local lGo,lOk
While .T.
lGo := .F.
lOk := .F.
cLogin := space(50)
cPassW := space(16)
DEFINE DIALOG oDlgLogin TITLE (cTitle) ;
FROM 0,0 TO 90,450 PIXEL;
FONT oDlg:oFont ; // Usa a mesma fonte do diálogo anterior
OF oDlg ;
COLOR CLR_WHITE, CLR_RED
@ 05+3,05 SAY oSay1 PROMPT "Login" RIGHT SIZE 20,12 OF oDlgLogin PIXEL
@ 05,30 GET oGetLogin VAR cLogin PICTURE "@!" SIZE CALCSIZEGET(45) ,12 OF oDlgLogin PIXEL
@ 25+3,05 SAY oSay1 PROMPT "Senha" RIGHT SIZE 20,12 OF oDlgLogin PIXEL
@ 25,30 GET oGetPassw VAR cPassW SIZE CALCSIZEGET(16) ,12 OF oDlgLogin PIXEL
oGetPassw:LPASSWORD := .T.
@ 25,155 BUTTON oBtnOk PROMPT "Ok" SIZE 60,15 ;
ACTION (lGo := .T. , oDlgLogin:End()) OF oDlgLogin PIXEL
ACTIVATE DIALOG oDlgLogin CENTER
If !lGo
// SE a janela foi fechada, desiste
EXIT
Endif
DbSelectarea("USUARIOS")
DBSetOrder(2) // Indice por LOGIN
If DBSeek(cLogin)
// Encontrou o Login informado
If MD5(alltrim(cPassW),HEX_DIGEST) == USUARIOS->SENHA
// A senha informada "bate" com a senha original
// Seta que está OK, sai do Login
lOk := .T.
EXIT
Endif
Endif
// Chegou aqui, o login nao existe ou a senha nao confere
MsgStop("Login ou senha inválidos. "+;
"Confirme os dados e repita a operação.", ;
"Falha de Autenticação")
Enddo
Return lOk
Feito isso dessa forma, conseguimos implementar um controle de acesso simples e eficiente, e bastante seguro, pois a senha original nunca é armazenada. A tela, após implementado um usuário, deve ficar assim:
Conclusão
Por hora, sem a inclusão de um usuário, não há autenticação na Agenda. As partes de código publicadas aqui ainda exigem alguns ajustes em outros pontos, por exemplo inserir o Login na inicialização da Agenda. Para pegar o fonte completo, acesse o GITHUB!
Agradeço novamente a audiência, as curtidas e os comentários, e desejo novamente a todos TERABYTES DE SUCESSO 😀
Referências