Sep 16

Autenticació PAM/OTP via PHP

Reading time: 2 – 3 minutes

Una bona forma de continuar aprofindint amb el tema OTP i també amb PAM, després de l’article: Shellinabox i OTP, és explicar-vos com m’ho he fet per afegir suport OTP al PHP, de forma que quan programem amb PHP es pugui delegar l’autenticació al sistema PAM del linux. Obviament això té certes restriccions perquè PHP corre amb els permisos de l’usuari de l’Apache, o bé, de l’usuari del FSGI, etc. L’important de fer notar és que no és habitual llençar codi PHP amb permisos de ‘root’. Així doncs, depèn de quina acció li fem fer al PAM aquest no la podrà dur a terme, per exemple, no podrà accedir al fitxer /etc/shadow per validar el password de sistema. De fet, com que el que jo vull és treballar amb OTP això no és rellevant.

El primer que s’ha de fer és instal·lar el paquet php5-auth-apm i reiniciar l’Apache:

apt-get install php5-auth-pam
/etc/init.d/apache2 restart

Ara anem al directori /etc/pam-script.d i fem:

cd /etc/pam-script.d
rm pam_script_acct
# creem fitxer pam_script_acct
echo '#!/bin/sh' > pam_script_acct
echo 'exit 0' >> pam_script_acct
chmod 755 pam_script_acct

Ara toca crear el fitxer /etc/pam.d/php, amb el següent contingut:

auth    sufficient      pam_script.so onerr=fail dir=/etc/pam-script.d/
account  required       pam_script.so onerr=fail dir=/etc/pam-script.d/

Amb això ja en tenim prou per anar a jugar amb el php, el primer és amb un phpinfo(); validar que apareix algo així:

i després podem fer un codi tan senzill com aquest:

<?php
    $result=pam_auth('user','password-otp',&$error);
    var_dump($result);
    var_dump($error);
?>

La comanda clau com podeu veure és pam_auth, passeu com a paràmetre el nom de l’usuari, el password que ús ha donat la vostre aplicació generadora de passwords OTP i la variable que voleu que reculli els errors la passeu per referencia. En cas d’error de l’autenticació aquesta comanda contindrà la descripció de l’error. Aquesta mateixa funció retorna un codi boleà amb el resultat de l’autenticació.

Sep 21

Symfony+Propel: usar múltiples schemas en un sol projecte

Reading time: 2 – 3 minutes

La idea que es persegueix al usar múltiples esquemes en un sol projecte és la de poder usar múltiples bases de dades en un sol projecte. La idea sembla molt senzilla, però a través de la documentació oficial del projecte Symfony la veritat és que no he sabut trobar la solució. En l’explicació per portar a terme aquesta funcionalitat hem referiré tota l’estona als fitxers .yml i no als .xml equivalents, però suposo que la idea és totalment exportable.

Es tracta de deixar d’usar el fitxer schemal.yml i passar a usar diferents fitxers anomentats nomarbitrari_schema.yml, si encomptes d’un nom voleu usar un número tampoc hi ha problemes per fer-ho, per exemple, 1_schema.yml i successius.

Pel que fa al contingut d’aquests fitxers és el de sempre, tot i que jo ús recomanaria usar petites ajudes com aquesta:

nombasededades:
  _attributes:
    package: lib.model.nombasededades

Aquest nombasededades l’usarem després per especificar les dades de connexió al fitxer databases.yml, però potser el més interessant és fixar-se en el paràmetre package que ens permet que tot el model de dades quedi guardat dins de lib/model/nombasedades. O sigui, que tot queda molt més ben organitzat i fàcil d’accedir.

Cal que vigileu si dues bases de dades tenen dues taules que s’anomenen igual, ja que si això passa llavors hi haurà un conflicte en els models de dades que es generen, perquè aquestes tindran el mateix nom. Això és fàcilment solucionable simplement a l’schema heu de dir-li que el nom de la classe que representa les dades d’aquella taula de la base de dades és un nom diferent al de la taula, o sigui, el nom escollit per defecte.

Pel que fa al fitxer databases.yml el seu contingut és el de sempre, simplement cal que penseu que cal descriure la connexió de cada una de les bases de dades que heu anat declarant als diferents fitxers d’schema.yml, un exemple:

all:
  nombasededades1:
    class:                sfPropelDatabase
    param:
      ...
  nombasededades2:
    class:                sfPropelDatabase
    param:
      ...

Un cop ben configurat tot això ja podem fer un típic symfony propel-build-model i llestos.

Aug 11

symfony: protegint els entorns de desenvolupament, integració i producció

Reading time: 7 – 12 minutes

A l’article symfony: entorns de desenvolupament, integració i producció es pot entendre el perquè de treballar amb aquests tres espais de noms (environment) així doncs el que es preten explicar és:

  • Impedir accés a les aplicacions en entorns de ‘dev’ i ‘int’ des de llocs no controlats. O sigui, que una IP d’internet no pugui executar myapp_dev.php o myapp_int.php però encanvi una IP de la xarxa del desenvolupador si que pugui.
  • Impedir accés a aplicacions de backend des de l’entorn de producció. Si hem desenvolupat alguna aplicació per tal de fer el manteniment de l’aplicació principal, una part d’administració o backend aquesta no sigui accessible des d’IPs no controlades.
  • Separar el fitxer de configuració config/cofnig.php per cada un dels entorns. Així podem definir constants o d’altres tasques de forma particular per cada un dels entorns.

Per tal d’aconseguir aquests objectius he creat el fitxer config/config.lib.php:

<?php
/**
 * Comproba si la IP amb la que s'accedeix a l'aplicació permet connectar en aquest environment
 *
 * @param string $env nom de l'environment
 * @param array $ips ips des de les que podem accedir a l'environment
 * @param string $clientip ip del client que vol accedir al recurs
 */
function envCorrecte ($env,$ips,$clientip)
	{
	if (SF_ENVIRONMENT==$env)
	{
		$acces=0;
			foreach($ips as $ip)
		{
			$ipclient=substr($clientip,0,strlen($ip));
				if ($ipclient==$ip)
			{
				$acces=1;
				}
		}
		if ($acces==0)
			{
			echo "You are not in '".$env."' environment! (".$ipclient."<>".$ip.")";
				exit;
		}
	}
}
/**
 * Comproba si l'aplicació amb la que s'accedeix al projecte és publica o no
 *
 * @param array $apps llista d'aplicacions no públiques
 * @param array $ips llista d'IP amb les que es pot accedir a les apps no públiques
 * @param string $clientip ip del client que vol accedir al recurs
 */
function appCorrecte ($apps,$ips,$clientip)
	{
	// solucionem problema de la clau '0'
    	$apps_aux[0]='';
		$apps = $apps_aux + $apps;
	//
	if (array_search(SF_APP,$apps)!=false)
		{
		$acces=0;
		foreach($ips as $ip)
			{
			$ADR=$HTTP_SERVER_VARS['REMOTE_ADDR'];
			$ipclient=substr($clientip,0,strlen($ip));
				if ($ipclient==$ip)
			{
				$acces=1;
				}
		}
		if ($acces==0)
		{
				echo "Keep away! ".SF_APP." is not a public application!";
			exit;
		}
		}
}
?>

Llavors des del fitxer config/config.php cal que cridem aquesta petita llibreria de funcions que he creat. La resta és més que lògica, aquí en teniu un exemple:

<?php
include("config.lib.php");
/**
 * Controlem accés als 'environments' de 'env' i 'int'
 */
$ip_dev=array("10.138.0.","192.168.");
$ip_int=array("10.138.0.","192.168.");
envCorrecte('env',$ip_env,$HTTP_SERVER_VARS['REMOTE_ADDR']);
envCorrecte('int',$ip_int,$HTTP_SERVER_VARS['REMOTE_ADDR']);
/**
 * Controlem accés a les 'apps' d'ús privat
 */
$ip_apps=array("10.138.0.","192.168.","w.x.y.z");
$apps=array('myapp_1','myapp_2','myapp_n');
appCorrecte($apps,$ip_apps,$HTTP_SERVER_VARS['REMOTE_ADDR']);
/**
 * Carreguem el fitxer de configuració propi de l'"environment".
 */
include("config.".SF_ENVIRONMENT.".php");
/**
 * Part comú en els 3 entorns
 */
?>

Com podeu veure el codi és molt senzill i la potència i el dinamisme, excepcionals.

Aquest codi també l’he publicat com un snippet a Access control to environment and applications and custom global config file for an environment.

Aug 11

symfony: entorns de desenvolupament, integració i producció

Reading time: 3 – 4 minutes

Al desenvolupar un projecte de software m’agrada treballar en 3 entorns. Mentre s’esta desenvolupant el projecte esta bé tenir un espai de variables (o entorn de noms) a l’aplicació que correspongui als ajustaments del servidor de desenvolupament (normalment una màquina pròpia), per exemple, els path, urls, dades de la bbdd, etc. tot això ha d’estar el màxim separat possible del codi per tal de poder crear les configuracions pels entorns d’integració i producció.

L’entorn d’integració és una màquina en l’entorn de desenvolupament del client, amb les característiques el més semblants possibles a l’entorn de producció. O sigui, les dades de configuració de l’espai de noms han d’apuntar a serveis si pot ser de la mateixa versió que l’entorn de producció. Així podem provar l’aplicació i millores de l’aplicació abans de passar-la a producció. Finalment hem de tenir l’entorn de noms per producció amb les dades de treball.

Per tal de suportar tot això symfony suporta en moltíssims fitxers de configuració la possibilitat de definir l’environment, en el cas que plantejo, ‘dev’, ‘int’ i ‘prod’. En el front controller de cada aplicació podem definir aquest entorn de noms així després s’accedirà a la part dels fitxers de configuració que pertoca.

Per exemple, si mirem el fixer config/databases.yml on symfony defineix els sistemes de BBDD que usa la nostre aplicació en cada un dels environments podriem tenir algo així:

dev:
  nombbdd:
    class:           sfPropelDatabase
    param:
      phptype:       mysql
      hostspec:      localhost
      database:      nombbdd
      username:      userbbdd
      password:      passbbdd
int:
  nombbdd:
    class:          sfPropelDatabase
    param:
      phptype:      oracle
      username:     nombbdd
      password:     nombbdd
      hostspec:     hostint
      database:     NULL
prod:
  nombbdd:
    class:          sfPropelDatabase
    param:
      phptype:      oracle
      username:     nombbdd
      password:     nombbdd
      hostspec:     hostprod
      database:     NULL

Al cridar el front controller és quan symfony diu amb quin environment treballarem, per exemple, per una aplicació anomenada myapp podriem tenir aquests tres front controllers un per cada entorn:

Entorn de desenvolupament web/myapp_dev.php:

<?php
define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'myapp');
define('SF_ENVIRONMENT', 'dev');
define('SF_DEBUG',       true);
#
require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
#
sfContext::getInstance()->getController()->dispatch();
?>

Entorn de integració web/myapp_int.php:

<?php
define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'myapp');
define('SF_ENVIRONMENT', 'int');
define('SF_DEBUG',       true);
#
require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
#
sfContext::getInstance()->getController()->dispatch();
?>

Entorn de producció web/myapp.php:

<?php
define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'myapp');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);
#
require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
#
sfContext::getInstance()->getController()->dispatch();
?>

Com es pot veure, tan desenvolupament com integració ens mostraran informació de debug i producció amagarà els missatges d’error i reportarà sempre l’error “internal server error”. A més podem veure que tots tres entorns criden el mateix fitxer de configuració global config/config.php.

Si voleu més informació sobre aquest tema us recomano els següents enllaços de la documentació de symfony:

Aug 04

symfony: interficies amb tabs de forma molt senzilla

Reading time: 1 – 2 minutes

L’últim snippet que han pujat al repositori de symfony és un somni fet realitat. Sempre m’ha fet molta ràbia haver de montar-me un sistema de tabbing per una interficie web (UI web) i el que ens proposa aquest snippet és un helper que permet cridar una llibreria anomenada Tab Pane (llicència web 2.0) que a través de javascript modifica els objectes DOM de la pàgina web i ens permet treballar amb uns tabuladors molt ràpids. A més fins hi tot suporta diferents aspectes. Realment una forma molt còmode i senzilla de treballar amb interficies tabulades.

tabs.png

Doncs tornant a l’snippet de symfony podem cridar el helper i començar a usar el codi de forma ben senzilla, a la pròpia descripció en podeu veure un exemple. Doneu-hi un cop d’ull a Helper for Javascript Tabbed Panes. Com és obvi recordeu que aquest helper s’ha d’invocar des de la vista (template).

Jul 27

symfony: cridant funcions i18n des de les actions

Reading time: 2 – 4 minutes

Una eina molt potent de symfony és la d’internacionalització (i18n), concretament la funció __() es converteix en una de les més usades i indispensables en la capa de vista de les nostres aplicacions. Ja que permet després de forma molt senzilla traduir la nostre aplicació a l’idioma que sigui. El problema és quan des de la part del model em de carregar informació a objectes que després seràn directament mostrats per la vista. Si volem que aquesta informació sigui fàcilment traudible malgrat trobar-se en el model i no pas en la capa de vista la cosa es complica una miqueta. De fet, fins avui no sabia com es feia i ho he descobert gràcies a un snippet publicat a la web de symfony.

Concretament symfony tracta la funció __() com el que ell anomena un helper que ens permeten dinamitzar i donar moltíssima potència als nostres templates. Si no sabeu el que són els helpers us recomano que repasseu la documentació de symfony concretmanet View chapter. De fet, symfony té molts tipus de helpers: url, javascript, form, i18n, etc. fins hi tot podem programar els nostres propis helpers.

Així doncs el que comentaré serà com usar els helpers de tipus i18n des del model, però la resta de helpers també es poden invocar de la mateixa manera.

Primer cal pensar en incloure des del model el codi on es declaren les funcions del helper:

include_once('symfony/helper/I18NHelper.php');

Per invocar la funció __(), hem de fer:

sfConfig::get('sf_i18n_instance')->__($text, $args, 'messages');

El que no sé és si les eines de traducció que es dediquen a extreure els missatges de les funcions __() són capaces de recorre també els fitxers d’accions definits al model o només passen pels templates. Tot i que no deu ser molt difícil dir-los que també mirin els fitxers del model.

Aprofitant que parlo del tema de la internacionlització us recomano una eina per traduir els fitxers d’idioma (XLIFF) és una eina multiplataforma programada en java: open language tools XLIFF editor. Com podeu veure l’aspecte i la simplicitat és inmillorable. Ideal per persones no tècniques que ens poden fer les traduccions de les aplicacions.

Per generar els fitxers XLIFF des de symfony us recomano: HowToGenerateI18NFiles. És un petit manual que he trobat al wiki de symfony.

Jul 27

symfony: Problemes de javascript

Reading time: 1 – 2 minutes

prototype.png

Symfony usa prototype com a framework de JavaScript. Malgrat la potència del projecte té un petit problema de disseny que ens dona problemes quan intentem integrar el nostre codi fet en prototype/symfony amb codi JavaScript de tercers. De fet, el problema de disseny és molt senzill prototype exten dues classes bàsiques de JavaScript: Object i Array ambdues són classes molt bàsiques i sovint usades en qualsevol script així doncs quan aquest codi intenta usar aquestes classes bàsiques al estar exteses no tenen el comportament habitual, així doncs el codi d’aquestes persones s’ha d’adaptar per suportar les modificacions de prototype. Així doncs, si això no és possible tindrem un problema d’incompatibilitat entre codis.

Si voleu més detalls tèncis sobre el problema l’Oriol m’ha passat un parell d’enllaços es raona el problema:

Jun 24

Validador de targetes de crèdit pel symfony

Reading time: 3 – 5 minutes

symfony.gifMalgrat els molts validadors de camps de formularis que inclou el famework symfony no n’inclou un de força important. El validador dels codis de les targetes de crèdit. Com segur que sabeu, els codi d’una targeta de crèdit és autoverificable. És a dir compleix un algoritme matemàtic entre els seus grups de 4 números. Doncs bé com que no tenia cap ganes d’implementar el que ja esta implementat moltes vegades, vaig trobar que a phpclasses.org un tal Harish Chauhan havia penjat una classe programada en PHP4 que implementava un validador de força tipus de targetes de crèdit (VISA, MASTERCARD, DISCOVER, AMEX, DINERS, JCB, Australian Bankcard, EnRoute i Switch Solo). Així doncs, vaig decidir integrar aquesta classe amb el sistema de validador de camps de formularis del symfony.

El primer que vaig fer va ser declarar els mètodes orginals com a privats i després extendre la classe sfValidator que és la usada pels validadors de symfony. Creant un parell de mètodes públics estàndards pels validadors, bàsicament serveixen per instanciar el mètode del validador real i per capturar els paràmetres rebuts des del fitxer yml corresponent.

Doncs bé si voleu consultar el codi de la classe i un exemple d’ús a part de poder-ho consultar en la part extesa d’aques article podeu mirar als snippets de symfony, concretament a Credit Card validator.

Codi del validador:

getParameterHolder()->get('num');
		$num = $this->getContext()->getRequest()->getParameter($num_param);
		$tipo_param = $this->getParameterHolder()->get('tipo');
		$tipo = $this->getContext()->getRequest()->getParameter($tipo_param);
		// Lanzamos la validación
		$validada=$this->_isVAlidCreditCard($num,$tipo,false);
		// Informamos de como ha ido la validación
		sfContext::getInstance()->getLogger()->info("CCVAL.class.php: Tipo: ".$tipo." Num: ".$num." Validada: ".$validada);
		if ($validada==false)
		{
			$error = $this->getParameterHolder()->get('error');
			return false;
		}
		return true;
	}
	public function initialize ($context, $parameters = null)
	{
		// initialize parent
		parent::initialize($context);
		$this->getParameterHolder()->add($parameters);
		return true;
	}
	/**
	 * Testing checksum
	 *
	 * @param integer $ccnum
	 * @return boolean
	 */
	private function _checkSum($ccnum)
	{
		$checksum = 0;
		for ($i=(2-(strlen($ccnum) % 2)); $i<=strlen($ccnum); $i+=2)
		{
			$checksum += (int)($ccnum{$i-1});
		}
		// Analyze odd digits in even length strings or even digits in odd length strings.
		for ($i=(strlen($ccnum)% 2) + 1; $i< 10)
			{ $checksum += $digit; }
			else
			{ $checksum += ($digit-9); }
		}
		if (($checksum % 10) == 0)
		return true;
		else
		return false;
	}
	/**
	 * Launch validation
	 *
	 * @param integer $ccnum
	 * @param string $type
	 * @param boolean $returnobj
	 * @return boolean
	 */
	private function _isVAlidCreditCard($ccnum,$type="",$returnobj=false)
	{
		$creditcard=array(  "visa"=>"/^4d{3}-?d{4}-?d{4}-?d{4}$/",
		"mastercard"=>"/^5[1-5]d{2}-?d{4}-?d{4}-?d{4}$/",
		"discover"=>"/^6011-?d{4}-?d{4}-?d{4}$/",
		"amex"=>"/^3[4,7]d{13}$/",
		"diners"=>"/^3[0,6,8]d{12}$/",
		"bankcard"=>"/^5610-?d{4}-?d{4}-?d{4}$/",
		"jcb"=>"/^[3088|3096|3112|3158|3337|3528]d{12}$/",
		"enroute"=>"/^[2014|2149]d{11}$/",
		"switch"=>"/^[4903|4911|4936|5641|6333|6759|6334|6767]d{12}$/");
		if(empty($type))
		{
			$match=false;
			foreach($creditcard as $type=>$pattern)
			if(preg_match($pattern,$ccnum)==1)
			{
				$match=true;
				break;
			}
			if(!$match)
			return false;
			else
			{
				if($returnobj)
				{
					$return=new stdclass;
					$return->valid=$this->_checkSum($ccnum);
					$return->ccnum=$ccnum;
					$return->type=$type;
					return $return;
				}
				else
				return $this->_checkSum($ccnum);
			}
		}
		else
		{
			if(@preg_match($creditcard[strtolower(trim($type))],$ccnum)==0)
			return false;
			else
			{
				if($returnobj)
				{
					$return=new stdclass;
					$return->valid=$this->_checkSum($ccnum);
					$return->ccnum=$ccnum;
					$return->type=$type;
					return $return;
				}
				else
				return $this->_checkSum($ccnum);
			}
		}
	}
}
?>

Un exemple de com es crida el validador des dels fitxers yml de configuració al directori validate:

methods:
  post: [ntarjeta]
names:
  ntarjeta:
    required:     Yes
    required_msg: Credit Card number is required
    validators:   validarCC
validarCC:
    class:       CCVAL
    param:
      num:       ntarjeta
      tipo:      tipoCC
      error:     Your credit card number is invalid
Jun 20

Un parell d’enllaços molt bons sobre UTF-8 i PHP

Reading time: 1 – 2 minutes

Primer una cosa realment útil, un detector de quin format té un text per poder-lo transformar a UTF8 si cal. Sembla una tonteria però el no tenir-ho sempre et fa anar de corcoll: Detectando UTF8 en PHP (local).

L’altre enllaç és d’un wiki, on ens parlen del comportament de diverses funcions de PHP amb UTF8 i com jugar amb elles sense morir en l’intent i alguna coseta més:

May 16

I love symfony

Reading time: 2 – 2 minutes

symfony.gif

Porto dos dies enganxat al Zend Studio programant en PHP5, concretament amb el framework anomenat symfony. Doncs bé no tinc cap ganes de posar-me a escriure un article tècnic sobre aquests dos aplicatius i menys a entrar en detall en la potencia d’ambdues eines juntes. Però si que volia deixar constància que això no té res que veure amb la programació de PHP que feia fa uns anys amb el PHP3. Això si que és un plaer, classes, object factories, ADO amb Abstract Factory Design, debugger, SOAP… i un llarg etcetera que no s’acaba mai.

Realment si quan heu de programar us agrada tenir eines com deu mana al vostre abast, jo diria que aquest és el duo perfecte. Si a més podeu tenir un servidor amb apache2 i el Zend Studio Server instal·lat, un control de versions amb subversion, una gestió de tasques amb taskspro i un sistema de tiqueting i roadmap amb trac jo diria que ja no es pot demanar gran cosa més.

L’últim comentari al tema abans de plegar per avui, la curva d’aprenentage del symfony és força llarga, a més interioritzar el model MVC i la implmentació basada en mojavi que en fa symfony costa lo seu. Però després passes a una nova dimenció, m’ha costat arribari però ara em declaro un enamorat del symfony.