Blog de l'Admin GX

Certains clients ont besoin d'analyser leurs portails web, le traffic, les pages visitées ; et parfois le besoin se pose sur un intranet / extranet professionnel, et donc à accès restreint ou confidentiel.

Alors attention à ce que vous faites, Google Analytics, qui est souvent la première idée qui vient à l'esprit (c'est même souvent le client qui le demande) n'est peut-être pas fait pour cela : la licence Google Analytics (ah, les licences Google...) implique :

  • l'ouverture de toutes les portes de vos contenus pour analyse par Google (ce qui devient délicat pour les données confidentielles du client),
  • d'autoriser Google à collecter, traiter et stocker ces contenus (même si ces données ne seront pas redistribuées à des tiers, Google peut en faire un usage interne).

Voir les paragraphes 4.2, 8.1, 8.2 et 8.3 de la licence http://www.google.com/intl/fr_ALL/analytics/tos_content.html

Lorsque l'intranet / extranet comporte des données confidentielles (ce qui est le plus souvent le cas), annoncer de telles implications au client fait toujours changer d'avis ce dernier.

Pour autant, il existe des alternatives sérieuses à Analytics :

  • Piwik, logiciel libre et gratuit à l'usage. Il s'installe en local, a l'avantage d'être composé en plugins ; facilement personnalisable.
  • FireStats, gratuit pour usage non commercial, payant sinon. Il s'installe aussi en local. Semble à un stable plus avancé dans son développement que Piwik.
  • Woopra, payant selon les paquetages choisi, a une interface de stats très riche, mais fonctionne en outil à distance (comme Analytics).

Tous vos retours sur ce post sont les bienvenus.

Aujourd'hui, j'ai eu la problématique de beaucoup de gens (à en croire les posts de forums laissés sans réponses depuis des années) :

"

Comment puis-je remplacer le code qui s'exécute derrière un opérateur de template, afin d'éviter de recoder tous mes templates ?

Comment puis-je déclencher cet échange d'opérateurs directement depuis un autre opérateur, en live dans le template ? (un peu comme un interrupteur on/off...)

Comment faire pour avoir des URLs absolues dans tous mes templates à l'appel de "ezurl", sans toucher au code de template ?!?

dixit le petit développeur de templates

"

J'ai enfin la réponse ! Une solution technique qui ne modifie rien dans le kernel, qui se fait proprement dans une petite extension toute autonome et qui ne casse RIEN tant qu'on n'a pas décidé d'activer l'échange d'opérateurs ! C'est propre, c'est rapide, un petit "on" au début du template englobant, un petit "off" à la fin pour faire propre, et tous les templates qui seront appelés entre temps utiliseront un opérateur personnalisé à la place du méchant opérateur qui vous ennuie !

Le principe est simple (au début) : il suffit de déclarer (registerOperators) un opérateur qui porte le même nom que celui qu'on veut remplacer... facile... mais comme une si simple solution ne marche pas toujours (cela dépend de l'ordre dans lequel sont chargées les extensions, et donc QUI aura le dernier mot sur la déclaration de l'opérateur...), il faut pouvoir faire une déclaration en live dans le template (mettre un interrupteur à "on" pour forcer la déclaration dynamique). Comble du luxe, il est aussi possible de faire le chemin inverse pour revenir à l'opérateur d'origine. Cela donne le code suivant :

AVANT : {'/user/login'|ezurl()} {* url normale... *}

AVEC SUBSTITUTION :
  {sethacks()}... {* j'active ma substitution *}
    {'/user/login'|ezurl()} {* l'opérateur utilisé a changé ! *}
  {unsethacks()}... {* je retire ma substitution *}

APRES : {'/user/login'|ezurl()} {* url normale ! *}

Il suffit qu'entre sethacks() et unsethacks(), je fasse exécuter toute la vue full de mon contenu, et l'opérateur de template (ezurl dans mon exemple) sera celui que j'aurai recodé dans mon extension !

Bon maintenant, comment on fait tout çà ?

Supposons que vous savez faire une extension avec des opérateurs de templates (si vous ne savez pas, appuyez-vous sur des exemples connus, comme ezwebin...).

Dans votre fichier eztemplateautoload.php, vous aurez ceci :

$eZTemplateOperatorArray = array();

$eZTemplateOperatorArray[] = array(
 'script' => 'extension/mon_extension/autoloads/hackedoperators.php',
 'class' => 'HackedOperators',
  'operator_names' => array(
  'sethacks' // ON NE MET RIEN D'AUTRE ICI !!!
 )
);

Vous aurez remarqué qu'ici, je ne déclarer qu'un seul opérateur, l'interrupteur qui permet de mettre en route la machine...

Dans le fichier des opérateurs (hackedoperators.php), vous aurez ceci :

class HackedOperators {

  function HackedOperators() {
    $this->Operators = array(
      'sethacks', 'unsethacks',
      'ezurl' // ICI, TOUT DECLARER !
    );
    $this->NamedOperators = array(
      'sethacks'=> array( ),
      'unsethacks'=> array( ),
      'ezurl'=> array(
        'quote' => array( 'type'=>'string',
              'required'=>false,'default'=>"double"),
        'type' => array( 'type'=>'string',
              'required'=>false,'default'=>"full"),
      )
    );
  }

  function modify($tpl, $operatorName, $operatorParameters,
        $rootNamespace, $currentNamespace, &$operatorValue,
        $namedParameters) {
    switch ( $operatorName ) {
      case 'sethacks':
        $tpl->registerOperators($this); // force set for all ops
      break;
      case 'unsethacks':
        $tpl->registerOperators(new eZURLOperator()); // reset ops
        // complete here with all reset instructions
      break;
      case 'ezurl':
        $this->ezurl($operatorValue, $namedParameters);
      break;
      default :
        $tpl->warning( $operatorName, "Unknown operator" );
      break;
    }
  }

  function ezurl(&$value, $namedParameters) {
    // ICI, ON MET SON NOUVEAU CODE !
  }

  function operatorList() {
    return $this->Operators;
  }
  function namedParameterPerOperator() {
    return true;
  }
  function namedParameterList() {
    return $this->NamedOperators;
  }
}

Le tour est joué !

Amusez-vous bien.

 

A chaque appel de module dans eZpublish (comme le module "content", ou de simples modules, comme des modules AJAX), et donc à chaque appel d'une page de contenu ou d'une fonction AJAX dans une extension, le kernel d'eZpublish (/index.php) met à jour une variable de session nommée LastAccessesURI.

Cette variable est utilisée lorsqu'on quitte le mode "édition", pour revenir sur la page sur laquelle nous étions avant l'édition, ou lorsqu'on se logue depuis une page à accès restreint, afin de revenir vers ladite page une fois logué...

Ceci est un problème dans un cas bien particulier, car chronologiquement, quand un utilisateur appelle une simple page dans le front office contenant au moins un sous appel AJAX vers un module d'extension, les étapes suivantes sont jouées :

  • URL de la page demandée par l'utilisateur -> l'URI est placée dans LastAccessesURI
  • URL du module AJAX appelé par la page (via Javascript) -> l'URI du module AJAX est placée dans LastAccessesURI

Vu que l'appel AJAX est effectué en dernier, ce n'est pas l'URL de la page qui reste dans LastAccessesURI, mais celle de l'appel AJAX : eZpublish ne fait pas cette différence. Lorsque notre utilisateur édite la page puis ferme l'édition, ou encore lorsque la page demandée est sous accès contrôlé (donc l'utilisateur passe par la page intermédiaire user/login), il est ensuite redirigé vers l'URL du module AJAX en pleine vue, qui n'est pa la page qui devrait être affichée à ce moment là.

Heureusement, eZpublish a prévu le coup (bien que ça ne soit pas documenté...) : il est possible de déclarer notre module AJAX d'une manière particulière pour éviter la mise à jour de la variable LastAccessesURI : dans index.php une condition entoure la mise à jour de la variable :

if ( ... && !in_array( $module->uiContextName(),
     array('edit','administration','browse','authentication')
     )) {
    $http->setSessionVariable("LastAccessesURI",
          $currentURI);
 }

Ainsi, on redéclare notre module AJAX comme étant un module d'admin, et c'est tout !

Dans notre extension/.../modules/.../module.php, on utilisera le code suivant :

$ViewList['get'] = array(
     'script' => 'get.php',
     'ui_context' => 'administration'
 );

Regarding to a recent post I put here about Kerberos and Apache, there is a way to replace Kerberos when the Active Directory cannot be properly configured to accept Kerberos connections. You can simulate the NTLM auth process with the browser by 6 steps in PHP :

function get_login() {

   /*
   step:  | type:
   -------|----------------|------------------------------------
   1      | C --> S        | GET ...
   -------|----------------|------------------------------------
   2      | C <-- S        | 401 Unauthorized
          |                | WWW-Authenticate: NTLM
   -------|----------------|------------------------------------
   3      | C --> S        | GET ...
          |                | Authorization: NTLM
          |                | <base64-encoded type-1-message>
   -------|----------------|------------------------------------
   4      | C <-- S        | 401 Unauthorized
          |                | WWW-Authenticate: NTLM
          |                | <base64-encoded type-2-message>
   -------|----------------|------------------------------------
   5      | C --> S        | GET ...
          |                | Authorization: NTLM
<base64-encoded type-3-message>
   -------|----------------|------------------------------------
   6      | C <-- S        | 200 Ok
   -------|----------------|------------------------------------
   */

   $headers = apache_request_headers();
   if($headers['Authorization'] == NULL) { // step 1
       header( "HTTP/1.1 401 Unauthorized" ); // step 2
       header( "WWW-Authenticate: NTLM" );
       exit;
   };
   if(isset($headers['Authorization'])
         && substr($headers['Authorization'],0,5) == 'NTLM ') {
            // step 3 to 6
       $chaine=$headers['Authorization'];
       $chaine=substr($chaine, 5); // type1 message
       $chained64=base64_decode($chaine);
       if(ord($chained64{8}) == 1) { // step 3
           // check NTLM flag "0xb2",
          // offset 13 in type-1-message :
           if (ord($chained64[13]) != 178) {
                echo "Please use NTLM compatible browser";
                   return null;
           }
           $retAuth = "NTLMSSP";
           $retAuth .= chr(0).chr(2).chr(0).chr(0);
          $retAuth .= chr(0).chr(0).chr(0).chr(0);
           $retAuth .= chr(0).chr(40).chr(0).chr(0);
          $retAuth .= chr(0).chr(1).chr(130).chr(0);
           $retAuth .= chr(0).chr(0).chr(2).chr(2);
          $retAuth .= chr(2).chr(0).chr(0).chr(0);
           $retAuth .= chr(0).chr(0).chr(0).chr(0);
          $retAuth .= chr(0).chr(0).chr(0).chr(0).chr(0);

           $retAuth64 =base64_encode($retAuth);
           $retAuth64 = trim($retAuth64);
           header( "HTTP/1.1 401 Unauthorized" ); // step 4
           header( "WWW-Authenticate: NTLM $retAuth64" );
           exit;
       }
       else if(ord($chained64{8}) == 3) { // step 5
           $lenght_domain = (ord($chained64[31])*256 + ord($chained64[30]));
           $offset_domain = (ord($chained64[33])*256 + ord($chained64[32]));
           $domain = substr($chained64, $offset_domain, $lenght_domain);
           $lenght_login = (ord($chained64[39])*256 + ord($chained64[38]));
           $offset_login = (ord($chained64[41])*256 + ord($chained64[40]));
           $login = substr($chained64, $offset_login, $lenght_login);
           $lenght_host = (ord($chained64[47])*256 + ord($chained64[46]));
           $offset_host = (ord($chained64[49])*256 + ord($chained64[48]));
           $host = substr($chained64, $offset_host, $lenght_host);
       }

   }
   $login = preg_replace("/(.)(.)/","$1",$login);
   $domain = preg_replace("/(.)(.)/","$1",$domain);
   $login = strtolower($login);
   $domain = strtoupper($domain);
   return array($login,$domain); // step 6 : accept
}

Warning : this code must be exectuted not only for the auth process when you want to login the user, but for each HTTP request to your application (so on each page).

It is very important to put HTTP 1.1 protocole in HTTP headers because HTTP 1.0 does not support Keep-alive connection and so NTLM auth. For some unknown reasons, even if some Apache versions change automatically the protocole version to 1.1 in your headers, other versions don't (for example 2.2.3 can do it and 2.2.9 can't).

Finally, keep in mind that this code is not as secure as Kerberos Apache module, because this code will never check Active Directory permission to accept the user. This function is just to retrieve login and domain, you have to make the account check yourself.

Good luck !

 

After few months developing a little try under Android API, I wanted to make feedback on benefits of the Google OS. I want to achieve the first version of my project and then make a constructive criticism about Android regarding to other graphical or mobile APIs, like the iPhone.

Since the beginning of the year, I established a personal project to develop a small Android application, quite independent, using GUI at maximum of its capacities, a database (SQLite) and a few onboard Google Maps onboard. I'll give small screenshots in a way that says enough along on the usefulness of the program, because I do not know yet exactly what I will do with my application.
My first impressions over development were:

  • Firstly, the ownership of the GUI as a developer is quite long : you cannot learn to make a good GUI in a week, it takes more time (about 2 months). But when you play well with this API, you have fun. The GUI is certainly more easy to control than the Swing one, my only point of comparison in Java.
  • Despite this, the GUI is not as good as the iPhone one : technologically, it lacks important points (difficult to achieve, I know): the multi-touch is not supported at all and will probably never (or only partially, if we can hope?); the fluidity of the scrolls with the finger is not as impressive as on the iPhone and at the same time it lost ergonomism ; the sequence of screens and transitions between them are not automatic : you must integrate or develop it by yourself: it has its advantages but also disadvantages: each program will behave differently on transistions, resulting in a loss of landmarks for the user ; API adopted by Google is a little too oriented "1 screen means 1 Java class", which is sometimes a strong constraint to generate clear source code.
  • The screen resolution of Android is variable! Several resolutions will be possible: this advantage allows manufacturers of phones to have screens of different size depending on the targeted markets: it is good for them. For developers, that's another story! You must design applications with screens nor too rich (to fit on small screens), nor too poor (to avoid empty large screens), or (solution that I had adopted) set up screens for each resolution . This work is long, and if an application does not handle many resolutions (as I saw many applications as ADC Challenge winners...), small screen will not display everything, and therefore the application does not run properly on low resolutions, which are - I presume - being the first resolutions to emerge on the market, given the current technological possibilities.
  • Application side, we do what we want, as long as it is possible in Java, and if it is not too heavy to execute : we must not forget that Java is a language interpreted by a JVM, and it is basically heavy and power consumer. On a small phone, we have the computing power of a PC made five years ago. See how Windows Mobile is slow: it will be worse under Android, unless you have a very big phone tht cost a lot (see the dimensions of the HTC Dream / G1, the first android phone to be released, for about $300).
  • Synchronization side, between Android / Desktop, is for the moment inexisting: either I missed a big detail in the framework, or it is a huge mistake made by Google: I have not found any tool in the emulator to synchronize laptop with a PC... If every developer must do its own synchronization program, there will be problems quickly. I intend to look at this at the end of my project to analyze the feasibility and simplicity to develop a database sync.
  • Finally, as the framework version 0.9 was released recently, I was able to experience a transition of versions with my application. The changes in the API showed me about 550 errors in Eclipse. Classes have disappeared, others are displaced or have become private. Many methods have been renamed (without any logical intention), and Google Maps API has changed a lot. I had to fully rewrite the classes of my application using MAPS. For 550 errors, 30 hours were needed to bring my application up to version 0.9 (the gain is still significant in terms of fluidity, features ...).

 

This week I had to make an eZpublish web site with an SSO authentication under eZpublish (NTLM). An SSO login handler is required. This login handler is executed by PHP and so is preceded by Apache authentication, with Kerberos Apache module.

The first problem comes when you want to let Kerberos authenticate the user (to give user data to PHP) or bypass authentication module if Kerberos cannot identify the user, to let eZpublish authenticate the user itself, with a classical form or another login handler : this parameter is not possible under Apache Auth modules, with a classical configuration like this:

<Directory /var/www/ezpublish>
    AuthType Kerberos
    KrbAuthRealms WASCOU.ORG
    KrbServiceName HTTP
    Krb5Keytab /root/wascou.keytab
    KrbMethodNegotiate on
    KrbMethodK5Passwd off
    Require valid-user
    Options All
</Directory>

The "Require valid-user" line will disallow site access until the user is not authenticated under Apache Kerberos module. This is the problem for users that need to be logged in with the classical form on eZpublish. Unfortunately there is no instruction to tell Kerberos to let a bypass in failure case (like with Basic or Digest modules, the same): Apache will give a HTTP 401 error, which is quite logical.

I suppose you know that eZpublish can call the user/login module from any URL that brings to a protected content: according to the user rights, a login form could be shown, and before this, a SSO login can be called. So there no way to indicate clearly to apache when eZpublish needs to login the user (to activate Kerberos authentication): telling "/user/login" URL is the only login URL is a mistake.

So, the solution is to play with well built Apache and PHP redirections, that the user cannot see, to call Kerberos module only when eZpublish needs it. Firstly, we will replace the above configuration with the following one, contained in a Location section, much more appropriated in our case :

<Location /ntlm/auth>
    AuthType Kerberos
    KrbAuthRealms WASCOU.ORG
    KrbServiceName HTTP
    Krb5Keytab /root/wascou.keytab
    KrbMethodNegotiate on
    KrbMethodK5Passwd off
    Require valid-user
    Options All
    ErrorDocument 401 /user/login
</Location>

Notice that the "/ntlm/auth" URL could bring us to an eZpublish module: this module must exist (you have to create it), but the PHP script behind this will never been executed and could remain empty (Apache and eZpublish will make redirections before this execution, see next step). Also notice the "ErrorDocument 401 /user/login" line, that will redirect the user if Kerberos cannot authenticate the user (and only for the "/ntlm/auth" URL!).

The big tip is here: if Kerberos cannot authenticate the user, it must redirect to an eZpublish page. The "/user/login" is an arbitrary choice, because the SSO login handler will make redirections before the execution of the user/login script (see next step).

Now, you have to make your SSO login handler, playing with all needed redirections, to manage correctly all possible bounds.

The following SSO login handler is a complete example:

function handleSSOLogin() {
  $ip = $_SERVER["REMOTE_ADDR"];
  $net = $ini->variable( 'NTLMSettings', 'net' );
  $mask = $ini->variable( 'NTLMSettings', 'mask' );

  // tip: (net & mask) == (ip & mask) : ok!
  if ((ip2long($net)&ip2long($mask))==(ip2long($ip)&ip2long($mask))) {

    // 2nd case : /ntlm/auth redirected to first URL, to auth under PHP.
    if ($_SESSION['ntlm_success']=="success") {
      if ( array_key_exists( 'REMOTE_USER', $_SESSION )
             && array_key_exists( 'AUTH_TYPE', $_SESSION ) ) {
        $remoteUser = $_SESSION['REMOTE_USER'];
        $authType = $_SESSION['AUTH_TYPE'];

        eZDebug::writeDebug('#25# user:'.$remoteUser,'');

        $loginParts = explode( '@', $remoteUser );
        $loginName = $loginParts[0];

        // main call of YOUR User handler in NTLM mode
        $user = LOGINCLASS::loginUser($loginName);

        if ( is_object( $user ) ) {
          return $user;
        } else {
          eZDebug::writeDebug('#36# Unable to fetch user','');
          unset($_SESSION['REMOTE_USER']);
          unset($_SESSION['AUTH_TYPE']);
        }
      } else {
        eZDebug::writeDebug('#39# No sso auth performed','');
        unset($_SESSION['REMOTE_USER']);
        unset($_SESSION['AUTH_TYPE']);
      }
      $_SESSION['ntlm_success'] = "failed";
      return false;
    }

    // first case : sso_handler redirection to /ntlm/auth.
    if ($_SERVER['SCRIPT_URL'] == '/ntlm/auth') {
      eZDebug::writeDebug('#47# IP on domain, Kerberos OK.','');
      if (!$_SESSION['ntlm_url']) {
        echo 'Cookies or/and Sessions are not activated.<br/>';
        eZExecution::cleanExit();
      }
      $ntlm_url = $_SESSION['ntlm_url'];
      unset($_SESSION['ntlm_url']);
      $_SESSION['ntlm_success'] = "success";
      $_SESSION['REMOTE_USER'] = $_SERVER['REMOTE_USER'];
      $_SESSION['AUTH_TYPE'] = $_SERVER['AUTH_TYPE'];
      eZHTTPTool::redirect($ntlm_url);
      eZExecution::cleanExit();
    } else if ($_SESSION['ntlm_success'] != "failed") {
      eZDebug::writeDebug('#59# IP on domain, checking NTLM.','');
      $_SESSION['ntlm_url']=$_SERVER['SCRIPT_URL'];
      eZHTTPTool::redirect('/ntlm/auth');
      eZExecution::cleanExit();
    } else {
      eZDebug::writeDebug('#64# IP on domain, Kerberos failed.','');
    }

  } else eZDebug::writeDebug('#67# IP not on domain, Stop.','');

  return false;
}

This script will process like this:

  • First, a test is executed to ensure we are on the right domain (by mask and IP)
  • Then a redirection is done to /ntlm/auth ; we store the original URL typed in the SESSION
  • When /ntlm/auth is called, apache Kerberos module will try to authenticate user
  • if failed, an Apache redirection (by "ErrorDocument 401" param) is done to /user/login
  • in this case, our SSO login handler will redirect to the original URL typed by the user, with a failed state for authentication (stored in the session)
  • the next login handler will try an authentication with a form...
  • if succeeded, our SSO login handler will store user login (given by Kerberos under $_SESSION['REMOTE_USER']) and will redirect to the original URL typed by the user,
  • on this new URL, our SSO login handler will authenticate the given user in the database (you have to implement "LOGINCLASS::loginUser($loginName);" line by yourself)
  • in case of success, login process is terminated, the $user is returned.
  • in case of failure, false is returned to let next login handler try an authentication with a form...

If you have any question, comment this post!

Good luck!

Ceci est mon blog personnel, sur eZpublish, Android, ou autres sujets...

Suivez-moi avec ce flux !
(un flux RSS uniquement Français est aussi disponible ici.)
 
 
 
Lun Mar Merc Jeu Ven Sam Dim
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31