Page 2 of 14

ERROR 2006 (HY000) at line 27652: MySQL server has gone away

Enquanto estava usando um script para substituir o meu banco de dados local com o banco de dados de produção, eu me deparei com o seguinte erro:

ERROR 2006 (HY000) at line 27652: MySQL server has gone away

O primeiro passo é verificar o tamanho da variável max_allowed_packet

  
+--------------------+----------+
| Variable_name      | Value    |
+--------------------+----------+
| max_allowed_packet | 16777216 |
+--------------------+----------+

O valor da variável max_allowed_packet está definido como 16777216, isso quer dizer que em nosso /etc/mysql/my.cnf ela está definica com 16M (16777216÷1024÷1024).

Como resolver ERROR 2006 (HY000) at line 27652: MySQL server has gone away

Para resolvermos esse problema, vamos alterar o valor da variável max_allowed_packet, no arquivo /etc/mysql/my.cnf

  
$ sudo vim /etc/mysql/my.cnf

Vamos alterar para 128M

  
...
[mysqld]
...
max\_allowed\_packet    = 128M  
...
[mysqldump]
max\_allowed\_packet    = 128M  
...

Caso você também queira aumentar o tempo de timeout, adicione abaixo do [mysqld]

  
...
[mysqld]
...
wait_timeout = 6000  
...

Agora é só reiniciar o serviço do mysql

$ sudo service mysql restart


		
	

Como instalar o servidor FTP (vsftpd) no Ubuntu 14.04

Se você quiser configurar um servidor ftp seguro, eficiente e rápido, então você pode considerar instalar o vsftpd. É, provavelmente, o servidor FTP mais seguro e rápido para sistemas UNIX-like.

Por se pequeno para fins de velocidade e segurança, muitas configurações de FTP mais complicados são realizáveis com vsftpd. Por exemplo, é possível lidar com:

  • Configurações de IP virtuais
  • Os usuários virtuais
  • Operação independente ou inetd
  • Configuração poderosa por usuário
  • largura de banda
  • Per-source-IP configuração
  • Per-source-IP limites
  • IPv6
  • O suporte à criptografia SSL através da integração

Utilizando o passo a passo a seguir como guia para a sua configuração, você evita de se deparar com error como:

  • Error: The data connection could not be established: EHOSTUNREACH – No route to host
  • Error: Connection timed out
  • Error: Failed to retrieve directory listing

Instalando vsFTPd

  
sudo apt-get update  
sudo apt-get install vsftpd  

Configurando o vsFTPd

O servidor vai ser executado com privilégios mínimos de o usuário “nobody” e registro completo será ativado. Finalmente, vamos limitar clientes para os tempos de espera de ligação específicas e limitar os comandos que aceitará de todos os clientes FTP.

  
sudo vim /etc/vsftpd.conf  
  
#######################################################
###  vsftpd.conf   BEGIN
#######################################################
# manpage: http://vsftpd.beasts.org/vsftpd_conf.html
#
# Run in daemon mode
background=YES  
listen=YES  
#listen_address=111.111.111.111
#pasv_address=127.0.0.1
#
# The new highly restrictive seccomp filter sandbox
# If you see "OOPS: priv_sock_get_cmd" then disable seccomp
seccomp_sandbox=NO  
#
# User to run daemon as
#nopriv_user=_vsftpd
#ftp_username=_ftp
#
# Ftp ports
connect\_from\_port\_20=NO  
ftp\_data\_port=20  
listen_port=21  
pasv_enable=Yes  
pasv\_min\_port=7000  
pasv\_max\_port=7014  
pasv\_addr\_resolve=NO  
pasv_address=191.237.128.149  
pasv_promiscuous=NO  
port_enable=NO  
port_promiscuous=NO  
#
# SSL (force options for a SSL only server)
#ssl_enable=YES
#ssl_tlsv1=YES
#ssl_sslv2=NO
#ssl_sslv3=NO
#rsa_cert_file=/etc/ssl/private/vsftpd.pem
#allow_anon_ssl=YES
#force_anon_data_ssl=YES
#force_anon_logins_ssl=YES
#force_local_data_ssl=YES
#force_local_logins_ssl=YES
#
# Timeouts
connect_timeout=0  
data\_connection\_timeout=0  
idle\_session\_timeout=0  
#
# Information messages
#setproctitle_enable=YES
#banner_file=/etc/banner
dirmessage_enable=YES  
ftpd_banner=Teste ftp server  
#
# Access limits and controls
async\_abor\_enable=NO  
#cmds_allowed=ABOR,APPE,CWD,DELE,HELP,LIST,MDTM,MKD,NLST,PASS,PASV,PWD,QUIT,RETR,RMD,RNFR,RNTO,SIZE,STOR,TYPE,USER
#cmds_allowed=ABOR,CWD,DELE,LIST,MDTM,MKD,NLST,PASS,PASV,PWD,QUIT,RETR,RMD,RNFR,RNTO,SIZE,STOR,TYPE,USER,ACCT,APPE,CDUP,HELP,MODE,NOOP,REIN,STAT,STOU,STRU,SYST
delay\_successful\_login=1  
delete\_failed\_uploads=yes  
guest_enable=NO  
write_enable=YES  
max_clients=100  
max\_login\_fails=1  
max\_per\_ip=0  
pam\_service\_name=vsftpd  
allow\_writeable\_chroot=YES  
tcp_wrappers=NO  
hide_file={.*,*.mp3}  
deny_file={*.mp3}  
#
# Preferences
ascii\_upload\_enable=NO  
ascii\_download\_enable=NO  
hide_ids=YES  
ls\_recurse\_enable=NO  
use_localtime=NO  
#
# Allow anonymous FTP?
#anonymous_enable=YES
#anon_max_rate=0
#anon_mkdir_write_enable=NO
#anon_root=/disk01/ftp/
#anon_world_readable_only=YES
#anon_umask=0022
#anon_upload_enable=NO
#anon_other_write_enable=NO
#no_anon_password=NO
#
# Allow local user access?
local_enable=YES  
local\_max\_rate=0  
local_umask=0022  
chroot\_local\_user=YES  
check_shell=NO  
chmod_enable=NO  
secure\_chroot\_dir=/var/run/vsftpd/empty  
#userlist_enable=YES
userlist_deny=NO  
#userlist_file=/etc/vsftpd_users
#
# Logging
dual\_log\_enable=NO  
log\_ftp\_protocol=NO  
vsftpd\_log\_file=/var/log/vsftpd.log  
xferlog_enable=YES  
xferlog\_std\_format=NO  
xferlog_file=/var/log/xferlog  
#
# Passive

#
#######################################################
###  vsftpd.conf  END
#######################################################

Ativando o modo Passivo

Para isso nós reservar um intervalo de portas para usar em conexões FTP passivas. No exemplo abaixo, nós iremos utilizar o intervalo da porta 7000 até 7014. Normalmente é comum ser utilizado um range de portas bem maior, porém no quando você utiliza azure, existe uma limitação de 150 endpoints, por isso, vamos utilizar um range menor. Para ativar o modo passivo, será preciso editar o arquivo de configuração vsftpd.conf novamente:

  
sudo vi /etc/vsftpd.conf  

Adicionando as linhas abaixo:

  
pasv_enable=YES  
pasv\_min\_port=7000  
pasv\_max\_port=7014  
port_enable=YES  

Configurando o Firewall

Você vai precisar para ativar dois módulos do kernel para o iptables. Estes irão ligar o NAT (Network Address Translation) para FTP e rastreamento de conexão FTP. Como iptables / Netfilter é parte do kernel, precisamos usar modprobe para adicioná-los para a sessão atual, afinal você não vai querer reiniciar um servidor só por isso, e também fazer alterações em /etc/modules para que, caso seja necessário reiniciar o seu servidor mais tarde, os módulos sejam carregados durante a inicialização do mesmo.

Primeiro, nós deveremos utilizar o modprobe para usar esses dois módulos agora:

  
sudo modprobe ip_nat_ftp  
sudo modprobe ip_conntrack_ftp  

Em seguida, vamos modificar o arquivo de configuração /etc/modules assim os módulos deverão ser carregados na próxima reinicialização:

  
sudo vim /etc/modules  

Adicione as linhas:

  
ip\_nat\_ftp  
ip\_conntrack\_ftp  
  
sudo iptables -A INPUT -p tcp --destination-port 7000:7014 -j ACCEPT  

Adicionando usuários com acesso a pastas específicas

Pode ser necessário criar usuários com acesso a pastas específicas do seu servidor de FTP, para isso crie o usuário e atribua o home do mesmo para a pasta desejada.
Como meu servidor web está rodando nessa pasta estou adicionando o usuário para o grupo www-data

  
useradd -m usuarioEspecifico -s /usr/sbin/nologin -d /home/ftp/www/pasta/especifica/ -g www-data  

Garantindo permissão de escrita

Para garantir que o grupo possa escrever na pasta

  
sudo chmod go+rwx /home/ftp/www/pasta/especifica/  

Liberando as portas

Amazon EC2

Isto pode ser feito através do AWS Management Console (painel de controle web EC2 da Amazon), ou no seu próprio console:

ec2-authorize default -p 20-21
ec2-authorize default -p 7000-7014

Azure

É necessário adicionar as seguintes portas abertas no
EndPoint:
TCP 20-21
TCP 7000-7014

Reinicie o serviço vsftpd

Finalmente, você deve ter tudo que você precisa para se conectar via FTP no seu servidor:

  
sudo service vsftpd restart  

Como definir o fuso horário no Ubuntu

Uma das primeiras coisas que você deve fazer quando instala um servidor linux, é definir o fuso horário, afinal, é muito chato quando você tem que ir para o arquivo de log e encontrar todas as timestamps estão em UTC. Mas podemos definir o fuso horário do servidor para que quando seu SGBD cron e etc fiquem no mesmo horário em que o seu computador, tornando-os mais realistas.

Você pode verificar o fuso horário atual, apenas executando o comando:

  
$ date

Thu Dec 18 17:06:17 MST 2014

Para trocar a o fuso horário, basta executar o comando:

  
$ sudo dpkg-reconfigure tzdata

E selecionar o continente e o fuso horário que preferir.

Também não se esqueça de reiniciar cron, afinal, como ele não vai detectar a mudança do fuso horário ele continuará executando os seus agendamentos em UTC.

Nginx error 413: Request entity too large

Através desse post rápido hoje abordaremos um erro bastante comum com Nginx: o infame 413 Request entity too large. Eu tive o problema agora pouco quando estava fazendo um mini-sistema de upload de arquivos na minha máquina de desenvolvimento, então eu pensei que eu iria fazer um post no blog sobre isso para me certificar de que não se esqueça disso na próxima vez.

Error 413: O que isso significa?

Esse erro aparece quando um visitante envia muitos dados na solicitação HTTP, no meu caso o tamanho do arquivo que eu estava fazendo upload era grande demais.
Isso normalmente é causada por:
• um arquivo muito grande sendo carregado pelo visitante.
• dados POST grandes demais sendo enviado pelo cliente.
A correção é tão simples como a criação de uma directiva na sua configuração Nginx, leia abaixo.

Como corrigí-lo? client_max_body_size

Para corrigir isso, você precisa aumentar o valor da diretiva client_max_body_size. Esta diretiva define a quantidade máxima de dados que o Nginx aceita em uma solicitação HTTP. Por padrão, esse valor é definido como 1 megabyte, ou seja, se você tentar carregar um arquivo maior do que 1 megabyte você estará recebendo um erro 413: Request entity too large. Você pode inserir esta diretiva em três níveis:

No bloco http: isto irá definir o valor diretiva para todos os servidores e os locais na sua configuration.
No bloco de server: isto irá definir o valor directiva para todos os locais de um determinado servidor .
No bloco location: isto irá definir o valor directiva para um local específico em um determinado servidor .

Neste exemplo, etaremos utilizando a diretiva no bloco http e vamos configurá-lo para 250 megabytes:

http {  
    client_max_body_size 250M; # permite o upload de arquivos de até 500 megabytes
    [...]
}

É necessário fazer mais alguma coisa?

Depois de alterar a configuração do servidor, não se esqueça de recarregar Nginx para que a configuração seja aplicada. Há também algumas definições de configuração que você deverá mudar, caso você esteja usando PHP. Caso não esteja utilizando PHP, apenas ignore o resto deste post. Você vai precisar para abrir seu arquivo de configuração php.ini e procurar dois valores:

  • upload_max_filesize: Tamanho máximo permitido para arquivos carregados (padrão: 2 megabytes). Você precisa aumentar esse valor se você espera por arquivos com mais de 2 megabytes de tamanho.
  • post_max_size: tamanho máximo dos dados POST que o PHP irá aceitar (padrão: 8 megabytes). Os arquivos são enviados através de dados POST, então você precisa aumentar esse valor se você está esperando por arquivos com mais de 8 megabytes.

Após fazer as alterações, não se esqueça de recarregar PHP-FPM, para que as alterações sejam aplicadas corretamente. Isso é tudo! Você não deve estar mais recebendo erros de upload depois disso.

Como Instalar a Plataforma de Blog Ghost no Ubuntu

Depois de mudar meu blog da kinghost para a DigitalOcean, eu resolvi mudar também a plataforma que eu utilizava para as postagens. Então, resolvi mudar do wordpress para o Ghost.

Primeiro, vamos verificar a configuração do mysql.

Verificando MySql

Antes de criarmos os bancos, é importante verficarmos se nossa instalação do MySql está utilizando utf8. No arquivo /etc/mysql/my.cnf verifique se a ele possui as linhas character_set_server e collation_server.

[mysqld]
[....]
character_set_server = utf8  
collation_server = utf8_general_ci  
[....]

Feito isso, podemos prosseguir para a instalação propriamente dita, e para tal vamos criar os bancos de dados e um usuário para o ghost.

Criando usuário e bancos de dados

$ mysql -u root -p

No prompt de comando mysql> vamos utilizar os seguintes comandos. Certifique-se de trocar “SUA_SENHA” com sua própria
senha, afinal, essa vai ser a senha do seu banco de dados que utilizaremos para o Ghost.
Vamos criar dois bancos de dados, um ghost e um ghostdev, um para produção e outro para desenvolvimento, respectivamente.
Criaremos um usuário ghost que terá acesso aos bancos, lembre-se de substituir @’localhost’ por @’%’, caso você deseje ter acesso remoto à esses bancos.
Atribuiremos privilégios para que os usuários possam acessar os bancos.

create database ghostdev;  
create database ghost;  
create user 'ghost'@'localhost' identified by 'SUA_SENHA';  
grant all privileges on ghost.* to 'ghost'@'localhost';  
grant all privileges on ghostdev.* to 'ghost'@'localhost';  
flush privileges;  
quit  

Instalando o Nodejs

$ sudo apt-get install npm nodejs

Instalando o Ghost

Primeiro, vamos entrar na pasta, criar a pasta blog

$ mkdir /var/www/blog/
$ cd /var/www/blog/

Agora, vamos baixar o ghost utilizando o curl.

$ curl -L https://ghost.org/zip/ghost-latest.zip -o ghost.zip

Vamos descompactar o arquivo.

$ unzip -uo ghost.zip -d .
$ cd ghost

Instalaremos o Node.js no Ghost. Você também vai querer o módulo para MySQL, para que a nossa instalação do Ghost utilize o banco de dados que criamos anteriormente.

$ npm install --production
$ npm install mysql

Modifique os config.js em seu Ghost. Altere a url nas sessões de desenvolvimento e produção para o endereço do seu blog.

development: {  
    // The url to use when providing links to the site, E.g. in RSS and email.
    url: 'http://blog.adlerdias.eti.br',

e

production: {  
    url: 'http://blog.adlerdias.eti.br',

Em seguida, colocaremos as configurações do banco de dados de produção e desenvolvimento, substituindo as versões SQLite padrão, tanto em desenvolvimento e produção:

database: {  
    client: 'mysql',
    connection: {
        host: 'localhost',
        user: 'ghost',
        password: 'SUA_SENHA',
        database: 'ghostdev',
        charset: 'utf8'
    }
},

e, para Produção:

database: {  
    client: 'mysql',
    connection: {
        host: 'localhost',
        user: 'ghost',
        password: 'SUA_SENHA',
        database: 'ghost',
        charset: 'utf8'
    }
},

Para finalizar, vamos instalar e iniciar o Ghost.

$ npm install --production
$ npm start

Em um navegador, navegue até a url 127.0.0.1:2368 para ver o seu Blog.

Gzip Requisições HTTP com NGINX

Se você utiliza já utiliza o Nginx, provavelmente você se importa com desempenho, e uma das maneiras mais fáceis de ganhar desempenho é compactar em Gzip as requisições HTTP. O Gzip pode não só aumentar o desempenho do seu site, como também economizar a banda.

Como o Gzip funciona

De uma forma bem simplificada, a compressão em gzip funciona, encontrando strings semelhantes dentro de um arquivo de texto, e substituindo aquelas strings temporariamente para fazer o tamanho total do arquivo menor. Esta forma de compressão é particularmente adequada para a web porque o HTML e CSS geralmente contém muitas strings repetidas, como espaços em branco, tags e definições de css.

Se você utiliza o Nginx no Linux, você, provavelmente já possui o gzip instalado.

Você pode adicionar essas diretivas no http, server ou ainda na location do seu arquivo de configuração do nginx, dependendo apenas das suas necessidades.

gzip on;  
gzip_proxied any;  
gzip_types text/plain text/css text/javascript text/xml application/xml application/xhtml+xml application/xml+rss;  
gzip_disable "MSIE [1-6].";  

Explicando as diretivas

gzip on; – Habilitamos a compressão Gzip.
gzipproxied any; – Aplica a compressão Gzip em todas as requisções. Você pode ver a lista com todos os parâmetros aqui – gzipproxied.
gziptypes text/plain text/css text/javascript text/xml application/xml application/xhtml+xml application/xml+rss; – Tipos de conteúdo que serão comprimidos com Gzip. Perceba que text/html já é habilitado por padrão, e irá gerar um aviso se você adicioná-lo aqui.
gzip*disable “MSIE [1-6].” – Serve para desabilitarmos a compressão com Gzip para as versões muito antigas do Internet Explorer.

Outras Configurações Gzip

Existem algumas outras configurações que podem ser utilizadas que não foram listadas acima. Ou seja, você pode personalizá-lo ainda mais. Eu não vejo qualquer razão para mudar estes dos valores padrão, mas acredito que valha a pena citá-los.

gzipcomplevel

Você pode aumentar o nível de compressão, pagando o preço do trabalho do servidor. O nível padrão é 1, que é o que a maioria das pessoas utilizam. É geralmente aceito que o aumento gzipcomplevel é um anti-pattern de desempenho, uma vez que leva mais tempo para comprimir do que a economia que será gerada por essa compressão extra.

gzipminlength

Com essa diretiva, você pode definir o tamanho mínimo do arquivo em bytes para que o mesmo seja comprimido utilizando Gzip. O valor padrão é de 20 bytes.

Se você quiser ler mais sobre Gzipping com o servidor nginx, recomendo a documentação oficial.

Redirecionamento 301 NGINX

Nginx é um grande servidor web, é também o servidor utilizado por este blog. Eu já utilizava o Nginx no meu ambiente de desenvolvimento e agora que eu resolvi mudar de vez da kinghost para a DigitalOcean, foi a chance que eu precisava para utilizá-lo também por aqui, para tal, eu tive que criar uma série de redirecionamentos tanto para melhorar a experiência do usuário evitando que os usuários fossem direcionados para páginas 404 quanto para indexação do motor de busca. Este post vai mostrar como criar alguns redirecionamentos 301 permanentes em seu arquivo de configuração Nginx.

Sempre que possível evite redirecionar páginas, mantendo as mesmas url’s quando você refaz um site. Mas se ainda assim você precisar redirecionar domínios, diretŕoios ou páginas em um servidor Nginx esse post vai te ajudar.

NGINX – Redirecionando Domínios

Para os sites novos, ter o www antes de seu domínio não é realmente necessário. Aqui está como redirecionar a versão www do seu site para a versão limpa, ou seja, a versão sem www.

Redirecionamento WWW para sem WWW


server   {
   server_name www.seudominio.eti.br;
   rewrite  ^/(.*)$  http://seudominio.eti.br/$1 permanent;
}

Redirecionamento sem WWW para WWW

Da mesma maneira, se o seu site já está publicado há bastante tempo, e utilizando a versão www, você pode ter certeza que os usuários serão redirecionados para a versão www.


server   {
   server_name seudominio.eti.br;
   rewrite  ^/(.*)$  http://www.seudominio.eti.br/$1 permanent;
}

NGINX Redirecionamento de Diretório

Pode ser necessário redirecionar todo um diretório de páginas para outro se você renomeá-lo. Caso você tenha que alterar a estrutura do site, possivelmente, você terá um conjunto desses em seu arquivo de configuração.

Essa diretiva vai dentro do bloco do servidor principal de seus sites de configuração NGINX.


if ( $request_filename ~ diretorio-antigo/.+ ) {
       rewrite ^(.*) http://seudominio.com/diretorio-novo/$1 permanent;
   }

if ( $request_filename ~ diretorio-antigo/.+ ) {
       rewrite ^(.*) http://seudominio.com/diretorio-novo/$1 permanent;
   }

Caso seja necessário, você pode simplesmente remover o diretório, dessa maneira:


if ( $request_filename ~ diretorio-antigo/.+ ) {
       rewrite ^(.*) http://seudominio.com/$1 permanent;
   }

Você pode reescrever a mesma regra acima, utilizando apenas uma linha, dessa forma:


location / {
    rewrite ^/diretorio-antigo/(.*) /$1 permanent;
}

NGINX Redirecionamento de Página

Quando você reconstruir um site, é comum para remover as páginas, ou renomeá-los. Se a página não tem links de outros sites, você não quer que os usuários que clicarem no link para terra em uma página de erro 404. A melhor coisa a fazer neste caso é enviar o usuário para a página seguinte mais útil.

Utilize essa diretriz no bloco de servidor para redireccionar páginas únicas.


if ( $request_filename ~ pagina-antiga/ ) {
      rewrite ^ http://seudominio.com/proxima-pagina/? permanent;
}

Você também pode utilizar o redirecionamento de página, para disponibilizar o arquivo robots.txt e o sitemap.xml.


location ~ ^/(sitemap.xml) {
    root /var/www/blog.adlerdias.eti.br/public;
}

location ~ ^/(robots.txt) {
    root /var/www/blog.adlerdias.eti.br/public;
}

abraço,
até a próxima

File(/usr/bin/php) is not within the allowed path(s)

open_basedir – O que é?

No PHP existe uma diretiva chamada openbasedir, que é uma medida de proteção que previne que usuários abram arquivos ou scripts que estão localizados fora do diretório home com PHP, a não ser que a pasta esteja especificada como excluída. A diretiva openbasedir se ativada, irá garantir que todas as operações em arquivos sejam limitadas à arquivos que estão dentro de uma certa hierarquia de pastas, e assim, evitar que scripts consigam acessar arquivos em uma pasta que não esteja autorizada. Quando um script tenta abrir um arquivo com, por exemplo, fopen() or gzopen(), a localização do arquivo é verificada. Quando o arquivo está fora da árvore de diretórios permitida, o php irá recusar abrir esse arquivo e algum dos erros abaixos poderá acontecer:

[adlersd@insomniac:~/www]
% composer  
PHP Warning:  Phar::mapPhar(): open_basedir restriction in effect. File(/usr/local/bin/composer) is not within the allowed path(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/:/home/adlersd/www/) in /usr/local/bin/composer on line 13

Warning: Phar::mapPhar(): open_basedir restriction in effect. File(/usr/local/bin/composer) is not within the allowed path(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/:/home/adlersd/www/) in /usr/local/bin/composer on line 13  

ou

[ErrorException]
  is_file(): open_basedir restriction in effect. File(/usr/bin/php) is not within the allowed p  
  ath(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/)

ou ainda

[adlersd@insomniac:~/www/sf2]
% composer                                                                255 ↵
PHP Warning:  Phar::mapPhar(): open_basedir restriction in effect. File(/usr/local/bin/composer) is not within the allowed path(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/) in /usr/local/bin/composer on line 13

Warning: Phar::mapPhar(): open_basedir restriction in effect. File(/usr/local/bin/composer) is not within the allowed path(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/) in /usr/local/bin/composer on line 13  
PHP Warning:  require(phar://composer.phar/bin/composer): failed to open stream: phar error: invalid url or non-existent phar "phar://composer.phar/bin/composer" in /usr/local/bin/composer on line 15

Warning: require(phar://composer.phar/bin/composer): failed to open stream: phar error: invalid url or non-existent phar "phar://composer.phar/bin/composer" in /usr/local/bin/composer on line 15  
PHP Fatal error:  require(): Failed opening required 'phar://composer.phar/bin/composer' (include_path='.:/usr/share/pear') in /usr/local/bin/composer on line 15

Fatal error: require(): Failed opening required 'phar://composer.phar/bin/composer' (include_path='.:/usr/share/pear') in /usr/local/bin/composer on line 15  

Como eu utilizo open_basedir para resolver o meu problema?

Precisamos editar o php.ini, no meu caso, /etc/php/php.ini

[adlersd@insomniac:~/]
% sudo vim /etc/php/php.ini

e modificar a linha abaixo adicionando os paths que estavam nos alertas/erros acima:

open_basedir = /srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/:/usr/bin/:/usr/local/bin/  

Como criar um sistema de autenticação (login) de usuário – parte 1

Nesse post, você vai aprender como criar o seu próprio sistema de autenticação de usuário com o banco de dados, essa é uma dúvida muito comum quando se está começando a lidar com o framework.

Existem uma série de extensões, módulos e sistemas RBAC disponíveis nas extensões Yii e, claro, o sistema RBAC do próprio framework. Porém, muitas vezes eles são destinados a projetos mais complexos, ou ainda, são muito complicados de integrá-los a sua solução quando você ainda está começando a caminhar e entender como o framework funciona.

Esse post é uma sugestão do Luciano.

Há muito tempo atrás eu criei um “projeto” para explicar alguns conceitos no yii. Abaixo seguem os links para você que chegou aqui, agora, não ficar perdido.

  1. Yii – Entendendo os relacionamentos (relations)
  2. Yii – Como criar um dropdown
  3. Yii – Como criar um filtro de um relacionamento
  4. Yii – Como fazer o botão delete do CGridView/TbGridView funcionar usando Ajax

Vamos utilizar essa mesma estrutura, e dar continuidade a esse “projeto”.

O que nós vamos construir

Quando você cria o seu projeto ou aplicação, no yii, automaticamente já está criado um sistema de autenticação de usuário muito simples que pode ser facilmente estendido para fornecer uma autenticação de usuário através do banco de dados. A partir de agora nós vamos construir um sistema de autenticação ou login, com nível de usuário, onde esse mesmo usuário pode ser usuário ou administrador e nós vamos ser capazes de usar esses níveis para limitar o acesso a funções dentro de nossas aplicações. As senhas de usuário serão tratadas de forma diferente, por enquanto basta você saber que não salvaremos as mesmas em texto puro, mas eu falarei sobre ela no seu devido tempo. Os usuários serão obrigados a alterar suas senhas depois de um intervalo de tempo definido.

Entendendo as diferenças entre WebUser e Autenticação

Esta primeira parte será a de entender a diferença entre a autenticação do usuário e do WebUser(identidade do usuário). Informações do usuário são armazenados em uma instância da classe CWebUser e este é criado na inicialização do aplicativo (ou seja: quando o usuário conecta pela primeira vez com o site), independentemente de o usuário estar autenticado (logado) ou não. Por padrão, o usuário é definido como Guest (visitante).
A autenticação é gerenciada por uma classe chamada CUserIdentity e essa classe verifica se o usuário é conhecido e se é um usuário válido. Como esta validação ocorre vai depender de sua aplicação, talvez aconteça no seu banco de dados – que será o nosso caso – ou entrar com facebook/google/github, ou contra um servidor ldap, etc…
O código gerado pelo Gii define a ação de login e modelo LoginForm que gerencia esse processo para nós e une essas duas classes juntas. No login, o sistema cria uma classe UserIdentity, passando os detalhes de login. Estes são validados, no nosso caso, como já citado, em nosso banco de dados.

Então o modelo de login passa o objeto UserIdentity ao objeto CWebUser, que, em seguida, armazena essa informação. Por padrão, a classe CWebUser armazena suas informações em dados da sessão e, portanto, não deve conter informações confidenciais, como senhas, por exemplo.

Nosso model de Usuário

CREATE TABLE `tbl_usuarios` (  
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ds_username` varchar(128) NOT NULL,
  `ds_password` varchar(128) NOT NULL,
  `ds_email` varchar(128) NOT NULL,
  `ds_nome` varchar(128) DEFAULT NULL,
  `nu_paginacao` tinyint(3) NOT NULL DEFAULT '25',
  `cd_empresa` int(9) DEFAULT NULL,
  `cd_filial` int(9) DEFAULT NULL,
  `role` int(1) NOT NULL DEFAULT '0',
  `dt_criacao` datetime DEFAULT NULL,
  `dt_alteracao` datetime DEFAULT NULL,
  `dt_ultima_visita` datetime DEFAULT NULL,
  `status` int(1) NOT NULL DEFAULT '0',
  `dt_expiracao_senha` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ds_username` (`ds_username`),
  UNIQUE KEY `ds_password` (`ds_password`),
  KEY `status` (`status`),
  KEY `superuser` (`role`),
  KEY `fk_usuario_empresa` (`cd_empresa`),
  KEY `fk_usuario_filial` (`cd_filial`),
  CONSTRAINT `fk_usuario_filial` FOREIGN KEY (`cd_filial`) REFERENCES `tbl_filial` (`cd_filial`),
  CONSTRAINT `fk_usuario_empresa` FOREIGN KEY (`cd_empresa`) REFERENCES `tbl_emp` (`cd_emp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Agora você deve utilizar o Gii para gerar o CRUD.

Personalizando o model do Usuário

Agora precisamos atualizar o modelo do usuário da seguinte forma:
1. Incluir constantes para representar nossos papéis.
2. Criptografar a senha
3. Use beforeSave para verificar senhas e atualizar a senha data de validade
4. Adicione quaisquer relações relevantes para a sua aplicação

/**
 * This is the model class for table "tbl_usuarios".
 *
 * The followings are the available columns in table 'tbl_usuarios':
 * @property integer $id
 * @property string $ds_username
 * @property string $ds_password
 * @property string $ds_email
 * @property string $ds_nome
 * @property integer $nu_paginacao
 * @property integer $cd_empresa
 * @property integer $cd_filial
 * @property integer $role
 * @property string $dt_criacao
 * @property string $dt_alteracao
 * @property string $dt_ultima_visita
 * @property integer $status
 * @property string $dt_expiracao_senha
 *
 * The followings are the available model relations:
 * @property TblFilial $cdFilial
 * @property TblEmp $cdEmpresa
 */
class Usuario extends CActiveRecord  
{
    const ROLE_USUARIO      = 3;
    const ROLE_ADMIN        = 1;
    const PASSWORD_EXPIRY   = 90;
    public $passwordSave;
    public $repeatPassword;
    /**
     * @return string the associated database table name
     */
    public function tableName()
    {
        return 'tbl_usuarios';
    }

    /**
     * @return array validation rules for model attributes.
     */
    public function rules()
    {
        // NOTE: you should only define rules for those attributes that
        // will receive user inputs.
        return array(
            //array('ds_password', 'compare', 'compareAttribute'=>'repeatPassword', 'message'=>'Por favor digite as senhas iguais.'),
            array('ds_username, ds_password, ds_email', 'required'),
            array('nu_paginacao, cd_empresa, cd_filial, role, status', 'numerical', 'integerOnly'=>true),
            array('ds_username, ds_password, ds_email, ds_nome', 'length', 'max'=>128),
            array('dt_criacao, dt_alteracao, dt_expiracao_senha', 'safe'),
            // The following rule is used by search().
            // @todo Please remove those attributes that should not be searched.
            array('id, ds_username, ds_password, ds_email, ds_nome, nu_paginacao, cd_empresa, cd_filial, role, dt_criacao, dt_alteracao, dt_ultima_visita, status, dt_expiracao_senha', 'safe', 'on'=>'search'),
        );
    }

    public function beforeSave() {
        parent::beforeSave();
        // Criptografamos a senha se for um novo registro
        if ($this->isNewRecord) {
            $this->ds_password = md5($this->ds_password);
            $this->dt_criacao=new CDbExpression("NOW()");
            $this->dt_expiracao_senha=new CDbExpression("DATE_ADD(NOW(), INTERVAL ".self::PASSWORD_EXPIRY." DAY) ");
        }
        else if (!empty($this->ds_password)&&!empty($this->repeatPassword)&&($this->ds_password===$this->repeatPassword))
        // Se não for um registro novo, salva a senha apenas se as duas senhas casarem.
        {
            $this->ds_password = md5($this->ds_password);
            $this->dt_expiracao_senha=new CDbExpression("DATE_ADD(NOW(), INTERVAL ".self::PASSWORD_EXPIRY." DAY) ");
        }
        return true;
    }

    /**
    * Compare Expiry date and today's date
    * @return type - positive number equals valid user
    */
    public function checkExpiryDate() {
        $expDate=DateTime::createFromFormat('Y-m-d H:i:s',$this->password_expiry_date);
        $today=new DateTime("now");
        fb($today->diff($expDate)->format('%a'),"PASSWORD EXPIRY");
        return ($today->diff($expDate)->format('%a'));
    }

    /**
     * @return array relational rules.
     */
    public function relations()
    {
        // NOTE: you may need to adjust the relation name and the related
        // class name for the relations automatically generated below.
        return array(
            'relationFilial' => array(self::BELONGS_TO, 'Filial', 'cd_filial'),
            'relationEmpresa' => array(self::BELONGS_TO, 'Empresa', 'cd_empresa'),
        );
    }

    /**
     * @return array customized attribute labels (name=>label)
     */
    public function attributeLabels()
    {
        return array(
            'id' => 'id',
            'ds_username' => 'Usuário',
            'ds_password' => 'Senha',
            'repeatPassword' => 'Repita a Senha', 
            'ds_email' => 'Email',
            'ds_nome' => 'Nome',
            'nu_paginacao' => 'Paginacao',
            'cd_empresa' => 'Empresa',
            'cd_filial' => 'Filial',
            'role' => 'Role',
            'dt_criacao' => 'Dt Criacao',
            'dt_alteracao' => 'Dt Alteracao',
            'dt_ultima_visita' => 'Dt Ultima Visita',
            'status' => 'Status',
            'dt_expiracao_senha' => 'Dt Expiracao Senha',
        );
    }

    /**
     * Retrieves a list of models based on the current search/filter conditions.
     *
     * Typical usecase:
     * - Initialize the model fields with values from filter form.
     * - Execute this method to get CActiveDataProvider instance which will filter
     * models according to data in model fields.
     * - Pass data provider to CGridView, CListView or any similar widget.
     *
     * @return CActiveDataProvider the data provider that can return the models
     * based on the search/filter conditions.
     */
    public function search()
    {
        // @todo Please modify the following code to remove attributes that should not be searched.

        $criteria=new CDbCriteria;

        $criteria->compare('id',$this->id);
        $criteria->compare('ds_username',$this->ds_username,true);
        $criteria->compare('ds_password',$this->ds_password,true);
        $criteria->compare('ds_email',$this->ds_email,true);
        $criteria->compare('ds_nome',$this->ds_nome,true);
        $criteria->compare('nu_paginacao',$this->nu_paginacao);
        $criteria->compare('cd_empresa',$this->cd_empresa);
        $criteria->compare('cd_filial',$this->cd_filial);
        $criteria->compare('role',$this->role);
        $criteria->compare('dt_criacao',$this->dt_criacao,true);
        $criteria->compare('dt_alteracao',$this->dt_alteracao,true);
        $criteria->compare('dt_ultima_visita',$this->dt_ultima_visita,true);
        $criteria->compare('status',$this->status);
        $criteria->compare('dt_expiracao_senha',$this->dt_expiracao_senha,true);

        return new CActiveDataProvider($this, array(
            'criteria'=>$criteria,
        ));
    }

    /**
     * Returns the static model of the specified AR class.
     * Please note that you should have this exact method in all your CActiveRecord descendants!
     * @param string $className active record class name.
     * @return Usuarios the static model class
     */
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
}

A classe UserIdentity

Em seguida, vamos alterar a classe UserIdentity original gerado Gii para autenticar o usuário contra a nossa nova tabela do usuário. Isto é encontrado em protected/components/UserIdentity.php e será parecido com este:

/**
 * UserIdentity represents the data needed to identity a user.
 * It contains the authentication method that checks if the provided
 * data can identity the user.
 */
class UserIdentity extends CUserIdentity  
{
    private $_id;
    private $_username;

    public function getName()
    {
        return $this->_username;
    }

    public function getId()
    {
        return $this->_id;
    }

    public function authenticate()
    {
        $user = Usuario::model()->find('LOWER(ds_username)=?', array(strtolower($this->username)));
        if($user === null)
        {
            $this->errorCode= self::ERROR_UNKNOWN_IDENTITY;
        }
        elseif($user->ds_password !== md5($this->password))
        {
            $this->errorCode= self::ERROR_PASSWORD_INVALID;
        }
        else
        {
            $this->_id = $user->id;
            $this->_username = $user->ds_email;
            $user->dt_ultima_visita=new CDbExpression("NOW()");
            $user->save();
            $this->errorCode= self::ERROR_NONE;
        }
        return !$this->errorCode;
    }
}

Personalizando a Classe WebUser

Uma vez que o usuário foi validado, no nosso caso, no banco de dados, podemos carregar os detalhes do usuário para a classe webuser que Yii detém a sessão do usuário. Posto isso, você deve tomar cuidado com as informações que você carrega e armazena na classe webuser, afinal, a sessão pode ser facilmente acessada pelo usuário final. Então, vamos criar um novo componente chamado WebUser em protected/components/

class WebUser extends CWebUser {  
    // Store model to not repeat query.
    private $_model;
    // Return first name.
    // access it by Yii::app()->user->first_name
    function getNome(){
        $user = $this->loadUser(Yii::app()->user->id);
        return $user->ds_nome;
    }
    function getRole(){
        $user = $this->loadUser(Yii::app()->user->id);
        return $user->role;
    }
    function getPaginacao(){
        $user = $this->loadUser(Yii::app()->user->id);
        return $user->nu_paginacao;
    }

    function getDtExpiracao(){
        $user = $this->loadUser(Yii::app()->user->id);
        return $user->checkExpiryDate();
    }

    // verificamos o valor do atributo role com a constante
    // ROLE_ADMIN para sabermos se o usuário é administrador
    function isAdmin(){
        $user = $this->loadUser(Yii::app()->user->id);
        if ($user!==null)
            return intval($user->role) == Users::ROLE_ADMIN;
        else return false;
    }

    // verificamos o valor do atributo role com a constante
    // ROLE_USUARIO para sabermos se o usuário é administrador
    function isUser(){
        $user = $this->loadUser(Yii::app()->user->id);
        if ($user!==null)
            return intval($user->role) == Users::ROLE_USUARIO;
        else return false;
    }

    // carrega o model do usuário
    protected function loadUser($id=null) {
        if($this->_model===null)
        {
            if($id!==null)
                $this->_model=Users::model()->findByPk($id);
        }
        return $this->_model;
    }
}

E então, no principal arquivo de configuração protected/config/main.php) você terá que especificar a classe do componente do usuário, por exemplo:

    [ ... ]
    'components'=>array(
        'user'=>array(
            // enable cookie-based authentication
            'allowAutoLogin'=>true,
            'loginUrl' => array('/site/login'),
            'class'=>'WebUser',
        ),
    [ ... ]

Nossa classe WebUser estende a classe CWebUser que é a classe acessado pelo componente Usuário da nossa aplicação. Assim, quando você referência Yii::app()->user agora irá referenciar a nossa nova classe WebUser. Portanto, tudo o que nós definimos aqui podem ser acessados usando a sintaxe
Yii::app()->user->propriedade ou Yii::app()->user->funcao()
Exemplo: Yii::app()->user->isAdmin()

Definimos um campo de perfil de usuário chamado nu_paginacao, que será utilizado para permitir que os usuários definam quantos itens são exibidos em um GridView. Por motivos de desempenho, então pode ser uma boa idéia definir um limite máximo para não acabar com o desempenho da aplicação.

Personalizando nosso CRUD

Vamos arrumar o formulário protected/views/usuario/_form.php da seguinte forma:

<div class="form">

<?php $form=$this->beginWidget('CActiveForm', array(  
    'id'=>'usuario-form',
    // Please note: When you enable ajax validation, make sure the corresponding
    // controller action is handling ajax validation correctly.
    // There is a call to performAjaxValidation() commented in generated controller code.
    // See class documentation of CActiveForm for details on this.
    'enableAjaxValidation'=>false,
)); ?>

    <p class="note">Fields with <span class="required">*</span> are required.</p>

    <?php echo $form->errorSummary($model); ?>

    <div class="row-fluid">
        <?php echo $form->labelEx($model,'ds_username'); ?>
        <?php echo $form->textField($model,'ds_username',array('size'=>60,'maxlength'=>128)); ?>
        <?php echo $form->error($model,'ds_username'); ?>
    </div>

    <div class="row-fluid">
        <div class="span6">
            <?php echo $form->labelEx($model,'ds_password'); ?>
            <?php echo $form->passwordField($model,'ds_password',array('size'=>60,'maxlength'=>128)); ?>
            <?php echo $form->error($model,'ds_password'); ?>
        </div>
        <div class="span6">
            <?php echo $form->labelEx($model,'repeatPassword'); ?>
            <?php echo $form->passwordField($model,'repeatPassword',array('size'=>60,'maxlength'=>128)); ?>
            <?php echo $form->error($model,'repeatPassword'); ?>
        </div>
    </div>

    <div class="row-fluid">
        <div class="span6">
            <?php echo $form->labelEx($model,'ds_email'); ?>
            <?php echo $form->textField($model,'ds_email',array('size'=>60,'maxlength'=>128)); ?>
            <?php echo $form->error($model,'ds_email'); ?>
        </div>
        <div class="span6">
            <?php echo $form->labelEx($model,'ds_nome'); ?>
            <?php echo $form->textField($model,'ds_nome',array('size'=>60,'maxlength'=>128)); ?>
            <?php echo $form->error($model,'ds_nome'); ?>
        </div>
    </div>

    <div class="row-fluid">
        <?php echo $form->labelEx($model,'nu_paginacao'); ?>
        <?php echo $form->textField($model,'nu_paginacao'); ?>
        <?php echo $form->error($model,'nu_paginacao'); ?>
    </div>

    <div class="row-fluid">
        <div class="span6">
            <?php echo $form->labelEx($model,'cd_empresa'); ?>
            <?php echo $form->textField($model,'cd_empresa'); ?>
            <?php echo $form->error($model,'cd_empresa'); ?>
        </div>
        <div class="span6">
            <?php echo $form->labelEx($model,'cd_filial'); ?>
            <?php echo $form->textField($model,'cd_filial'); ?>
            <?php echo $form->error($model,'cd_filial'); ?>
        </div>
    </div>

    <div class="row-fluid">
        <?php echo $form->labelEx($model,'role'); ?>
        <?php echo $form->textField($model,'role'); ?>
        <?php echo $form->error($model,'role'); ?>
    </div>

    <div class="row buttons">
        <?php echo CHtml::submitButton($model->isNewRecord ? 'Criar' : 'Salvar'); ?>
    </div>

<?php $this->endWidget(); ?>

</div><!-- form -->  

Para logar utilize admin/admin.

Por enquanto é só, vamos continuar com mais detalhes amanhã! Para baixar o projeto como está, clique aqui.

Como utilizar namespaces em php – parte 1

Namespaces já existem em outras liguagens de programação há muito tempo, essa é uma das mudanças mais significativas no PHP 5.3. Desenvolvedores Java e C#, além de outras linguagens, já estão familiarizados com isso, e agora com a utilização de Namapaces a estrutura de aplicações PHP mudará para melhor. Então, o que é um namespace no PHP? Em suma, é um recipiente abstrato que nos permite reutilizar a mesma função, classe e nomes de constantes, mas se aplicam diferentes significados com base em que contexto eles estão inseridos.

Não dá pra tornar a explicação mais fácil?

Imagine um namespace como uma gaveta na qual você pode colocar todos os tipos de coisas: um caderno, uma caneta, um lápis, uma borracha… Estes são os seus pertences. Diretamente debaixo de sua gaveta é de outra pessoa, e ele coloca as mesmas coisas nele. Para evitar o uso de itens de cada um, você decide colocar uma etiqueta nas gavetas para deixar claro o que pertence a quem.
Anteriormente, os desenvolvedores tiveram que usar underscore em suas classes, funções e constantes para separar bases de código. Isso é equivalente à nossa etiqueta.
Essencialmente, um namespace não é nada mais do que um bloco de código hierarquicamente rotulado que contém código PHP normal.

Por que precisamos de Namespaces?

Conforme a sua biblioteca de código vai crescendo, há um aumento do risco de acidentalmente, você acabar redefinindo uma função ou nome de classe que já foi utilizada anteriormente. O problema é agravado quando você tenta adicionar componentes ou plugins de terceiros, por exemplo: o que aconteceria se dois ou mais componentes utilizassem a classe Usuário?
Até agora, a única solução foi alongar os nomes de classe/função de comprimento. Por exemplo, o wordpress utiliza o prefixo ‘wp_'(underscore).
Problemas de colisão de nomes pode ser resolvido com namespaces. Constantes, classes e funções podem ser agrupadas em namespaces diferentes.

Como namespaces são definidos?

Por padrão, todoas as constante, classes e nomes de função são colocados no espaço global – como eram antes do PHP suportar namespace.
O namespace é definido apenas pela palavra-chave namespaceno topo do seu arquivo PHP. Ele deve ser o primeiro comando (com a exceção do declare) e nenhum código não-PHP, HTML, ou de espaço em branco pode preceder essa palavra-chave.

Vamos dar uma olhada em um pequeno trecho de código, e então vamos quebrar e expandir o que está acontecendo em pequenas partes, para que fique mais fácil de entender.

namespace Foo;

function Bar()  
{
    echo __NAMESPACE__;
}

namespace FooFoo;

function Bar()  
{
    echo "Bar";
}

echo \Foo\Bar();  

Saída:

Foo

Existem alguns pontos interessantes aqui:
A palavra-chave namespace
A constante NAMESPACE
A redefinição da função Bar()
A estranha nova sintaxe no comando echo no final do arquivo: a resolução de nomes

A palavra-chave namespace

Namespaces são nomeados arbitrariamente e têm duas formas de sintaxe. Em tempo, em qualquer uma das sintaxes, namespaces não podem ser aninhados, embora, sub-namespaces possam ser definidos, como será explicado mais adiante.

Sintaxe 1

namespace Foo;

// bloco de código

Sintaxe 2

namespace Foo  
{
    // bloco de código
}

A utilização de Namespaces torna mais fácil evitar definições conflitantes e introduzir mais flexibilidade e organização dos seus códigos.
Perceba também que que as chaves em torno do bloco de código são completamente opcionais. Na verdade, aderindo à regra de utilizar um namespace por arquivo e omitindo as chaves você torna seu código muito mais limpo.

A constante __NAMESPACE__

A constante NAMESPACE contém uma seqüência que exibe o namespace atual. Quando chamado a partir do contexto global, uma string vazia é exibida.

<?php  
namespace Blog;  
echo __NAMESPACE__;  
?>

Saída:

Blog

A utilização de namespaces tem benefícios bastante óbvios durante a depuração. Também pode ser usada para gerar dinamicamente um nome de classe completamente qualificado. Por exemplo:

<?php  
namespace Blog;

class MinhaClasse {  
    public function WhoAmI() {
        return __METHOD__;
    }
}

$c = __NAMESPACE__ . '\MinhaClasse';
$m = new $c;
echo $m->WhoAmI();  
?>

Saída:

Blog\MinhaClasse::WhoAmI

© 2017 Adler Dias

Theme by Anders NorénUp ↑