sábado, 12 de fevereiro de 2011

Gerando imagem para validação de números.


Gerando imagem para validação de números
Hoje em dia está sendo muito usado a geração de imagens para confirmação de alguma ação. Um exemplo disse é o email da uol, onde você tem uma opção de solicitar a confirmação do envio.
Bom, segue uma função que faz isso:

Primeiro adicione no seu form os componentes TButton, TImage e TEdit.

unit Unit1;
  interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls;
type
  TForm1 = class(TForm)
  Image1: TImage;
  Button1: TButton;
  Edit1: TEdit;
  procedure Button1Click(Sender: TObject);
  procedure FormShow(Sender: TObject);
private
  { Private declarations }
public
 { Public declarations }
  function GeraImagem(Img: TImage): string;
end;
 
var
  Form1: TForm1;
  validapost : string;
implementation
{$R *.dfm}
{ TForm1 }
function TForm1.GeraImagem(Img: TImage): string;
  const
    f: array [0..4] of string =  ('Courier New', 'Impact', 'Times New Roman', 'Verdana', 'Arial');
    s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    C : ARRAY [0..14] OF tcOLOR = (clAqua, clBlack, clBlue, clFuchsia, clGray, clGreen, clLime, clMaroon, clNavy, clOlive, clPurple, clRed, clSilver, clTeal, clYellow);
 
var
  i, x, y : integer;
  r : string;
begin
  randomize;
  Img.Width := 160;
  Img.Height := 60;
  for i := 0 to 3 do
  r := r + s[Random(length(s)-1)+1];
  with Img.Picture.Bitmap do
  begin
    width := Img.Width;
    Height := Img.Height;
    Canvas.Brush.Color := $00EFEFEF;
    Canvas.FillRect(Img.ClientRect);
    for i := 0 to 3 do
    begin
      Canvas.Font.Size := random(20) + 20;
      Canvas.Font.Name := f[High(f)];
      Canvas.Font.Color := c[random(High(c))];
      Canvas.TextOut(i*40,0,r[i+1]);
    end;
    for i := 0 to 2 do
    begin
      Canvas.Pen.Color := c[random(High(c))];
      Canvas.Pen.Width := 2;
      canvas.MoveTo(random(Width),0);
      Canvas.LineTo(random(Width),Height);
      Canvas.Pen.Width := 1;
      x := random(Width-10);
      y := random(Height-10);
      Canvas.Rectangle(x,y,x+10,y+10);
    end;
  end;
  Result := r;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  if (Edit1.Text = validapost) then
    Application.MessageBox('Parabéns, muito bem','Sucesso',
  MB_OK + MB_ICONINFORMATION);
  FormShow(self);
end;
 
procedure TForm1.FormShow(Sender: TObject);
begin
  validapost := GeraImagem(Image1);
end;
 
end.

terça-feira, 8 de fevereiro de 2011

Chave Estrangeira (Foreign Key) com DBExpress e ClientDataSet


Com o uso de DBExpress não existe mais a hipótese de campos Lookup para se trazer um determinado campo de uma chave estrangeira, como fazíamos antigamente. Isso porque para criarmos um campo Lookup, precisaríamos deixar a tabela aberta por completo na memória para que os outros datasets pudessem buscar a informação nela através do campo Lookup, e isto sai completamente dos conceitos do ClientDataSet...
    Para resolver este problema, usa-se JOINs em instruções SQL, que "performaticamente" falando, são mais rápidos, porém, um pouco mais trabalhoso, no sentido em que, ao digitarmos os dados para um ClientDataSet, estes ficam na memória e o Join não é executado no BD, até que o dado seja gravado. Dessa maneira, ao informarmos o ID da chave estrangeira não veríamos o campo que desejamos até que este registro seja gravado. Porém, pode-se “forçar” a busca deste campo através de uma função que você pode mais abaixo.

    Um cenário interessante para podermos exemplificar é um cadastro de venda, onde na tabela de vendas existe uma chave estrangeira da tabela de clientes para se gravar o ID do cliente que esta efetuando aquela compra. Se estivermos fazendo uma consulta, automaticamente o JOIN faz o seu papel e retorna o NOME deste cliente, se a SQL for algo do tipo:
SELECT
  V.CODVENDA,
  V.DATA,
  V.CODCLIENTE,
  C.NOME AS NOME_CLIENTE
FROM
  VENDAS V
     INNER JOIN CLIENTES C ON V.CODCLIENTE = C.CODCLIENTE

    Porém, ao inserir um novo registro o JOIN não é executado como já comentamos, pois os dados não estão no banco ainda, somente em Cache. Resolvendo isso, a função:

function GetFieldByID(var SQLConn: TSQLConnection;
                                   Table, ResultField, IDField: String;
                                   ID: integer): string;
var qry: TSQLQuery;
      SQL: String;
begin
  Result := '';
  SQL:='SELECT ' + ResultField + ' FROM ' + Table + ' WHERE '+ 
  IDField + '=' + IntToStr(ID);
  qry := nil;
  try
    SQLConn.Execute(SQL, nil, @qry);
    if Assigned(qry) then
      Result := qry.FieldByName(ResultField).AsString;
  finally
    qry.Free;
  end;
end;

Entendendo a Função

    Passamos para a função qual o SQLConnection (SQLConn) responsável pela execução da qry e também informamos em qual Tabela (Table) ela deverá buscar, através de qual campo (IDField) e qual ela irá trazer (ResultField), além é claro do ID que estamos procurando.
    A função monta então a SQL e a executa no SQLConnection especificado. Este por sua vez, retorna um ResultSet que fica armazenado na variável qry (que é um TSQLQuery). Se o SQLConnection não retornar o ResultSet é porque a SQL não retornou nada caso contrario (if Assigned(qry)), retorna para o Result da função o valor do campo pesquisado com a SQL montada.

    Para usá-la, no evento OnValidate do campo CODCLIENTE basta fazer:

cdsVendas.FieldByName('NOME_CLIENTE').AsString := GetFieldByID(SQLConnection1,'CLIENTES','NOME','CODCLIENTE',Sender.AsInteger);

    Você também pode tratar o campo verificando se a função retornou algum valor, e se não, gerar uma exceção avisando que o código não foi encontrado, por exemplo:

Var str: string;
begin
  str:=GetFieldByID(SQLConnection1,'CLIENTES','NOME','CODCLIENTE',Sender.AsInteger);
  if str<>'' then
    cdsProdutor.FieldByName('RAZAOS').AsString := str
  else
  begin
    MessageDlg('Código Não Encontrado!', mtWarning, [mbOK], 0);
    Abort;
  end;
end;

    A vantagem de se usar esta função, é que muda-se pouco de seu uso para outros campos, por exemplo, se quiséssemos saber o nome do vendedor supondo que este também esteja previsto na SQL através de um JOIN, seria:

cdsVendas.FieldByName('NOME_VENDEDOR').AsString :=  GetFieldByID(SQLConnection1,'VENDEDORES','NOME','CODVENDEDOR',Sender.AsInteger);

domingo, 6 de fevereiro de 2011

Chega de Showmessage!!!

Se você pensa..."Que Mensagem para validação de campos é muito chato!". Vai encontrar uma maneira bem simples de fugir deste terror.
O que veremos neste artigo é ao invés de mandarmos uma mensagem para o usuário, que é muito chato, você poder fazer o usuário se interagir com o sistema, veja...
Para este Artigo vamos precisar criamor uma Procedure.
Dei o Nome desta Procedure de TrocaCor com a sintaxe abaixo

Procedure TrocaCor(Campo : TWinControl ; CorTrocada , CorMeio, CorFinal : TColor ; Tempo : Cardinal );

O intuito desta rotina é o Seguinte:

- no lugar de mandarmos uma mensagem, faremos com que o campo "pisque", e que receba o foco depois deste "pisca-pisca"

o Parâmetro Campo, é do Tipo WinControl , pois poderá ser um TEdit, TComboBox , TListBox e Etc...(que recebam foco).

Os Parâmetro de Cores:
-CorTrocada
-CorMeio
-CorFinal

São do Tipo TColor, pois são elas as responsáveis de fornecedor o efeito de "piscar".

E a última, Tempo, serve para executar em qual período de Tempo;

vamos Lá Entao:

Implemente esta procedure com CTRL + SHIFT + C, e vamos aos códigos:

if (Campo is TEdit) Then
Begin
  (Campo as TEdit ).Color := CorTrocada;
  (Campo as TEdit).Update;
  Sleep(Tempo);
  (Campo as TEdit).Color := CorMeio;
  (Campo as TEdit).Update;
  Sleep(Tempo);
  (Campo as TEdit).Color := CorTrocada;
  (Campo as TEdit).Update;
  Sleep(Tempo);
  (Campo as TEdit).Color := CorFinal;
  (Campo as TEdit).SetFocus;
End //TEdit
else if (Campo is TComboBox) Then
Begin
  (Campo as TComboBox ).Color := CorTrocada;
  (Campo as TComboBox).Update;
  Sleep(Tempo);
  (Campo as TComboBox).Color := CorMeio;
  (Campo as TComboBox).Update;
  Sleep(Tempo);
  (Campo as TComboBox).Color := CorTrocada;
  (Campo as TComboBox).Update;
  Sleep(Tempo);
  (Campo as TComboBox).Color := CorFinal;
  (Campo as TComboBox).SetFocus;
  End;//TComboBox
End;

Bom, no caso acima eu imaginei que os campos fossem somente de dois tipos, TEdit e TComboBox, mas você poderá ir além disso, ou mais além ainda. Criar uma variável onde armazene a classe e economizar nos códigos...;)

Vamos usá-la Agora:
Coloque um botão no Formulário, um Edit e um ComboBox

E no envento OnClick do Botão Digite:
if Edit1.Text = 'Edit1' Then
Begin
  TrocaCor(Edit1 , clRed, clBlue, clBlack , 50);
  Exit;
End;

Entendendo, o código acima testará se o conteúdo do Edit1 for = Edit1
se Sim ele Executa nossa procedure e sai da Rotina. num intervalo de 50 milisegundos

Lembrando que quanto menor o número mais rápido será a piscagem....

Questões de segurança em banco de dados


Uma das principais preocupações nos dias de hoje é com a segurança das informações que estão armazenadas nos bancos de dados dos sistemas dos nossos clientes.
Para tanto, os desenvolvedores gastam muito tempo com o desenvolvimento de mecanismos que protejam estas informações, como senhas de acesso, níveis de segurança nos programas e políticas de usuários nos bancos de dados.

O que muitos analistas ou desenvolvedores não sabem ou não levam em consideração, é a segurança, não do sistema, não do banco de dados como um servidor, mas dos ARQUIVOS que compões este banco de dados.

Em muitas vezes, enquanto enumerava os benefícios que meu sistema traria a empresa, caso ele substituísse o sistema em uso, eu mostrava esta fragilidade, disfarçada em fortes esquemas de proteção via software.

No início, eram as tabelas PARADOX. Elas, aparentemente, podiam proteger os dados utilizando uma senha "embutida" diretamente nas tabelas. O sistema, contendo entrincados meios de autenticar os usuários, "abriam" estas tabelas com a senha pré-definida.

A principal falha deste método reside no fato de que a senha está em algum ponto no executável, e com um editor de arquivos que mostre hexadecimal, poderá ser encontrada.

Os programadores, motivados pela perspicácia dos invasores, resolveram codificar a senha, mas a necessidade de se obter informações sempre é mais forte, e os invasores começaram a ver que:

"A corrente parte sempre em seu elo mais fraco"

Como os programas estavam cada vez mais protegidos, as atenções se  voltaram para os bancos de dados. Afinal, o objetivo é roubar a informação e não invadir o programa.

Recuperar dados de um sistema não é a coisa mais difícil do mundo quando o programador se apoia somente na segurança do sistema operacional e do servidor de banco de dados. No caso do PARADOX, eu lembro quando recebi um desafio de pegar os dados de uma pessoa em um sistema de cadastro que possuía vários métodos de segurança.
O sistema realmente era bem montado, com senha e políticas de segurança, e o banco continha senha.
Um pouco de tempo na internet e eu consegui acessar todos os dados, com um programa de quebra senhas de tabelas PARADOX, o que mostrou que este tipo de banco de dados não é muito seguro se não for bem estruturado.

Para efeito de didática, eu disponibilizo o programinha em meu site.
Clique aqui para baixar...

Por favor, não o use em bancos de dados de terceiros.

Na empresa em que trabalhei, os programadores (e analistas) tinham o péssimo hábito de trabalhar com o Interbase da pior forma possível:

Eles colocavam uma máquina para ser o servidor do Interbase, até aí tudo bem.
Depois, acreditem, compartilhavam a pasta onde estava o banco de dados GDB usando o compartilhamento de pastas do Windows. Em seguida, as estações que rodavam o programa, acessavam o banco de dados através do mapeamento de pastas da Microsoft, criando um drive para a pasta compartilhada do servidor e acessando o GDB usando o protocolo local.

Eu considero isto a maior heresia de todos os tempos em se tratando de Interbase...
Me perdoem os programadores que lerem este artigo e adotam esta doutrina.

Não fossem todos os problemas com o banco de dados que esta prática causa, ela afeta diretamente a segurança do banco, visto que o Interbase protege os dados a nível de servidor, ou seja, toda a segurança do interbase se baseia na pemissa de que o cliente não acessará o arquivo diretamente, mas sim através da engine do servidor,
que aplicará os métodos de segurança elaborados pelo DBA (users e roles).

Tudo isso significa que, o banco de dados, na forma de arquivo, não possui capacidade para se proteger. Caso o arquivo GDB seja copiado para outro servidor, o SYSDBA poderá acessar os dados da mesma forma (ou usuários com os mesmos nomes). Então, basta instalar um servidor Interbase com a senha padrão do SYSDBA e colocar lá o GDB que se quer invadir.

Obviamente, se o GDB está em uma pasta compartilhada, não será necessário colocar senhas nos programas nem no banco de dados, pois serão inúteis...

Uma outra prática muito usada pelos programadores, talvez por desconhecer o uso correto da conexão IB, é usar o formato \\\Pasta\Arquivo.GDB como DatabaseName do IBDataBase.
Este formato também força o cliente a conectar-se ao banco em modo LOCAL, visto que esta é uma convenção de nomes de rede para compartilhamento de pastas, o que indica que o servidor não estará sendo usado.
Neste caso, o formato correto seria: ::\Caminho\Arquivo.GDB
Sendo "DRIVE" e "caminho" fazendo referência ao sistema de arquivos do SERVIDOR.
Este método é muito eficiente e permite que o IB seja conectado até através da internet, pois dispensa o uso do NetBios.

A primeira vista, soluções simples poderão ser adotadas para proteger os dados e dar um pouco mais de trabalho para a pessoa que desejar roubar informações.

A principal premissa a se assumir é:
"Meu servidor sempre estará exposto de alguma forma"

No caso do PARADOX, eu desaconselho o uso. Nada contra o banco de dados, mas eu não acho que ele tenha sido criado para sistemas que estão em produção. Comparando com os servidores SQL atuais de baixo custo (e até grátis), ele não é a melhor solução.

No caso do Interbase, é obvio que o GDB jamais poderá cair em mãos erradas.
Para evitar, ou diminuir o risco, o ideal é, acima de tudo, utilizar o protocolo de rede para acessar o servidor.

Uma aproximação mais eficiente seria utilizar camadas de acesso ao servidor.
Este método só traria vantagens para o usuário e para o programador.
Para melhorar ainda mais a segurança, a rede em que está localizado o servidor poderia ser diferente da rede onde estão os usuários (não fisicamente, mas logicamente).

Um computador com duas placas de rede (e dois endereços IP) poderia conter a camada de acesso, que é visível aos usuários. Esta camada receberia os usuários por uma interface de rede e se comunicaria com o servidor pela outra. Esta abordagem tornaria o servidor simplesmente inacessível diretamente aos usuários, pois ele estaria em uma faixa de IP incompatível com a rede deles.

Caso o programador não tenha acesso aos métodos descritos, seria ideal utilizar a criptografia para proteger os dados. Escrevi alguns artigos na revista (foram matéria de capa das edições 14 e 20) e alguns artigos no site que descrevem como melhorar a segurança dos dados sem confiar esta tarefa ao servidor ou a máquina onde o servidor foi instalado.

Seguem os links destes artigos:

http://www.activedelphi.com.br/modules.php?op=modload&name=News&file=article&sid=236 
http://www.activedelphi.com.br/modules.php?op=modload&name=News&file=article&sid=145
http://www.activedelphi.com.br/modules.php?op=modload&name=News&file=article&sid=127
http://www.activedelphi.com.br/modules.php?op=modload&name=News&file=article&sid=123

Uma outra saída, e talvez a mais eficiente, seria manter o nível de paranóia acima do normal (indicado como seguro pelos médicos) e criptografar tudo o que ver pela frente.
Isto inclui todos os dados do banco nas tabelas e também o tráfego na rede!
Eu já ultrapassei a barreira da sanidade ao fazer isso, como um bom gestor de segurança da informação...

Caso o leitor queira chegar a estas fronteiras, faça o download do servidor de SQL que estou aprimorando (sim, é um derivado do FlashFiler da TurboPower). Este servidor de SQL tem todos os componentes para o Delphi e com a vantagem de criptografar tudo o que é armazenado nas tabelas e também criptografar o que é transmitido entre o servidor e os clientes.
A desvantagem é que será uma má notícia para o DBA quando ele descobrir que não lembra da senha do banco ao retornar de férias...