<?php
/**
*
*
* @author Benjamin Gillissen <hide@address.com>
*
* **************************************************************
Copyright (C) 2009 Benjamin Gillissen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details at:
http://www.gnu.org/copyleft/gpl.html
* **************************************************************
*/
class realm extends realm_ticket {
private static $neededconf = Array('cookname', 'chals', 'accs');
private static $REALM;
private static $AUTH;
private $accounts, $challenges, $acl;
public function __construct($realm){
if ( FALSE === @constant('CORE_AUTH') ){ return; }
parent::__construct($realm);
if ( isset(self::$REALM[$realm]) ){ return; }
if ( FALSE === configs::get('realm', 'realm') ){ errors::raise("Realm $realm : is unknown !", CORE_LOG_ALERT, 'REALM'); }
//checking some realm configs,
foreach(self::$neededconf as &$var){
if ( FALSE === configs::get('realm', 'realm', Array($realm, $var)) ){
errors::raise("Realm $realm : missing '$var' option", CORE_LOG_ALERT, 'REALM');
}
}
//cookies handler init, the first realm loaded set it, we do not overwrite.
//a better solution would be to loop realms and take the biggest lifetime,
//hmm sess use it too
$COOKRef = configs::get('realm', 'realm', Array($realm, 'cookname'));
if ( FALSE === configs::get('cookies', $COOKRef) ){
errors::raise("Realm $realm : is using cookies channel $COOKRef", CORE_LOG_DEBUG, 'REALM');
$lifetime = configs::get('realm', 'realm', Array($realm, 'cooklifetime'));
if ( FALSE === $lifetime ){
errors::raise("Realm $realm : missing 'cooklifetime' option, using 30days as default", CORE_LOG_WARNING, 'REALM');
configs::set('realm', 'realm', 60*60*24*30, Array($realm, 'cooklifetime'));
$lifetime = 60*60*24*30;
}
$conf = Array('cipher'=>FALSE, 'expire'=>$lifetime);
configs::set('cookies', $COOKRef, $conf);
}
$this->cleanup_tickets();
$this->cleanup_client_tickets();
self::$REALM[$realm] = Array();
}
//-------------------------------------------------------------------Object load
private function load_accounts(){
if ( isset($this->accounts) ){ return; }
if ( ! isset(self::$REALM[$this->realm]['accs']) ){
$accs = configs::get('realm', 'realm', Array($this->realm, 'accs'));
errors::raise("Loading ".count($accs)." Account engine for realm '$this->realm'", CORE_LOG_DEBUG, 'REALM');
self::$REALM[$this->realm]['accs'] = new realm_accounts($this->realm, $accs);
unset($accs);
}
$this->accounts = &self::$REALM[$this->realm]['accs'];
return;
}
private function load_challenge($chal){
if ( ! isset(self::$REALM[$this->realm]['chals'][$chal]) ){
$chals = configs::get('realm', 'realm', Array($this->realm, 'chals'));
if ( FALSE === array_search($chal, $chals) ){ errors::raise("Challenge '$chal' is invalid for realm '$this->realm' !", CORE_LOG_ALERT, 'REALM'); }
unset($chals);
$type = configs::get('realm', 'chal', Array($chal, 'type'));
if ( FALSE === $type ){ errors::raise("Challenge '$chal' used in realm '$this->realm' is missing 'type' config !", CORE_LOG_ALERT, 'REALM'); }
$class = 'chal_'.$type;
self::$REALM[$this->realm]['chals'][$chal] = new $class($this->realm, $chal);
}
$this->challenges = &self::$REALM[$this->realm]['chals'][$chal];
return;
}
private function load_acl(){
if ( ! isset(self::$REALM[$this->realm]['acl']) ){
$engine = configs::get('realm', 'realm', Array($this->realm, 'acl_engine'));
$c = "acl_$engine";
self::$REALM[$this->realm]['acl'] = new $c($this->realm);
}
$this->acl = &self::$REALM[$this->realm]['acl'];
return;
}
//-------------------------------------------------------------------Object load
//-------------------------------------------------------------------REMEMBER AUTHENTIFICATION
//tell if client got a active ticket on this realm
public function isloged(){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
if ( isset(self::$AUTH[$this->realm]['ruid']) ){ return TRUE; }
}
//return the active ruid for this client on this realm
public function logedas(){
if ( !$this->isloged() ){ return FALSE; }
return self::$AUTH[$this->realm]['ruid'];
}
public function logedpassive(){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
$tickets = $this->get_passive_ticket();
if ( FALSE === $tickets ){ return FALSE; }
foreach($tickets as $k => &$ticket){ $o[] = $this->ticket_uid($ticket); }
if ( isset($o) ){
errors::raise('Loged passive as :'.print_r($o, TRUE), CORE_LOG_NOTICE, 'realm');
return $o;
}
return FALSE;
}
//return the membership of the gived ruid (of client if non gived)
public function ismemberof($ruid=NULL){
if ( FALSE === @constant('CORE_AUTH') ){ return Array(0=>Array(),1=>Array()); }
if ( NULL !== $ruid ){
$this->load_accounts();
return self::$REALM[$this->realm]['accs']->ismemberof($ruid);
} elseif ( TRUE === $this->isloged() ){
return self::$AUTH[$this->realm]['mbr'];
}
return Array(0=>Array(),1=>Array());
}
public function switchto($ruid){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
$err = $this->activate_ticket($ruid);
if ( TRUE !== $err ){
errors::raise("Switching To account ".$this->loginbyuid($ruid)." failed : $err", CORE_LOG_ERROR, 'REALM');
return FALSE;
}
errors::raise("Switching To account $ruid on realm $this->realm", CORE_LOG_INFO, 'REALM');
pagegen::add_event("Account ".$this->loginbyuid($ruid)." is now active on realm $this->realm");
self::$AUTH[$this->realm]['ruid'] = $ruid;
self::$AUTH[$this->realm]['mbr'] = $this->ismemberof($ruid);
sessions::start('profile');
sessions::clear('profile');
return TRUE;
}
public function logout(){
if ( FALSE === @constant('CORE_AUTH') ){ return; }
unset(self::$AUTH[$this->realm]['ruid']);
unset(self::$AUTH[$this->realm]['mbr']);
sessions::start('profile');
sessions::clear('profile');
pagegen::add_event("Active account has been loged out from realm $this->realm");
$this->del_active_ticket();
$ptickets = $this->get_passive_ticket();
if ( !is_array($ptickets) ){ return; }
$k = array_keys($ptickets);
$ruid = $this->ticket_uid($ptickets[$k[0]]);
$this->switchto($ruid);
}
//-------------------------------------------------------------------REMEMBER AUTHENTIFICATION
//-------------------------------------------------------------------CHALLENGE
public function authenticate(){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
if ( $this->isloged() ){ return TRUE; }
$ticket = $this->get_active_ticket();
$ptickets = $this->get_passive_ticket();
if ( FALSE !== $ticket ){
//first we try to restore the client_active_ticket,
$ruid = $this->ticket_uid($ticket);
errors::raise("Client @ ".client::host()." has been authenticated as account $ruid on realm $this->realm", CORE_LOG_INFO, 'REALM');
self::$AUTH[$this->realm]['ruid'] = $ruid;
self::$AUTH[$this->realm]['mbr'] = $this->ismemberof($ruid);
$this->ticket_update($ticket);
$ptickets = $this->get_passive_ticket();
if ( !is_array($ptickets) ){ return; }
foreach($ptickets as $k => $pticket){ $this->ticket_update($pticket); }
} elseif ( is_array($ptickets) ){
//then we try to restore the first client_passive_ticket,
$k = array_keys($ptickets);
$ruid = $this->ticket_uid($ptickets[$k[0]]);
$this->switchto($ruid);
foreach($ptickets as $k => $pticket){ $this->ticket_update($pticket); }
}
//no active, passive ticket, let's attempt an auto_login
return $this->auto_login();
}
public function login($chal=NULL, $err=NULL, $forceform=FALSE){
if ( FALSE === @constant('CORE_AUTH') ){ return; }
if ( $chal === NULL ){
errors::raise("Using first challenge defined in realm $this->realm", CORE_LOG_NOTICE, 'REALM');
$chal = configs::get('realm', 'realm', Array($this->realm, 'chals', 0));
}
$this->load_challenge($chal);
$trying = $this->challenges->istrying();
if ( FALSE === $trying OR $err !== NULL ){
$chaldata = $this->challenges->get_chaldata($chal);
$ticket = CORE::hash();
$msg = $this->newticket($ticket, $chaldata);
if ( TRUE !== $msg){
$b = $msg;
if ( NULL !== $err ){ $b = Array($msg, $err); }
$this->challenges->sendform($b);return;
}
$this->challenges->seticket($ticket);
$this->challenges->sendform($err);
}
//errors::raise("Checking posted challenge", CORE_LOG_NOTICE, 'REALM');
//so client has sent back his challenge, let's see that
$ticket = $this->challenges->geticket();
//ticket could not be restored from challenge
if ( FALSE === $ticket ){ $this->login($chal, 'realmsg_nochalticket');return; }
//challenge returned a ticket, is it valid?
if ( FALSE === $this->isticket($ticket) ){ $this->login($chal, 'realmsg_noticket');return; }
//some challenges require some more data, let's get back those to do the preCheck
$chaldata = $this->ticket_chaldata($ticket);
if ( FALSE === $chaldata ){ $this->login($chal, 'realmsg_nochaldata');return; }
//some specific challenge pre check,
$err = $this->challenges->preCheck($chaldata);
//errors::raise("PreCheck posted challenge => ".$err, CORE_LOG_NOTICE, 'REALM');
if ( TRUE !== $err ){ $this->login($chal, $err);return; }
if ( TRUE === $this->ischallenged($ticket) ){ $this->login($chal, 'realmsg_alreadychallenged');return; }
$this->mark_challenged($ticket);
$this->load_accounts();
$ruid = $this->challenges->authenticate();
//auth check failed, we sendform
if ( FALSE === $ruid ){ $this->login($chal, 'realmsg_authko');return; }
//auth ok we grant ticket
/*
$err = $this->apply_login($ruid, $ticket);
if ( TRUE !== $err ){ $this->login($chal, $err);return; }
*/
$err = $this->grant_ticket($ruid, $ticket);
if ( TRUE !== $err){ $this->login($chal, $err);return; }
//errors::raise('Granting "'.$ruid.'" to '.client::host(), CORE_LOG_INFO, 'REALM');
self::$AUTH[$this->realm]['ruid'] = $ruid;
self::$AUTH[$this->realm]['mbr'] = $this->ismemberof($ruid);
errors::raise("Challenge success, ".client::host()." is now authenticated as $ruid", CORE_LOG_INFO, 'REALM');
pagegen::add_event("Challenge '$chal' success, you are now authenticated as ".$this->loginbyuid($ruid) );
sessions::clear('profile');
$this->accounts->update_lastlogin($ruid);
if ( FALSE ){ //so user asked for autologin FIXME, new challenge method
errors::raise("Storing autologin cooky key", CORE_LOG_NOTICE, 'REALM');
$key = CORE::hash();
if ( FALSE === $this->set_client_autokey($ruid, $key) AND $forceform ){ $this->login($chal, 'realmsg_autocooknoset');return; }
if ( FALSE === $this->accounts->set_cookey($ruid, $key) AND $forceform ){ $this->login($chal, 'realmsg_autoaccnoset');return; }
} else {
errors::raise("Removing autologin cooky key", CORE_LOG_NOTICE, 'REALM');
$this->del_client_autokey();
}
if ( $forceform === TRUE ){ $this->login($chal, 'realmsg_authok');return; }
return;
}
//-------------------------------------------------------------------CHALLENGE
//-------------------------------------------------------------------AUTO LOGIN
public function clientuseautologin(){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
if ( FALSE === configs::get('realm', 'realm', Array($this->realm, 'auto_login')) ){ return FALSE; }
$COOKRef = configs::get('realm', 'realm', Array($this->realm, 'cookname'));
if ( !cookies::isdefined('autolog', $COOKRef) ){ return FALSE; }
$autolog = cookies::read('autolog', $COOKRef);
return isset($autolog[$this->realm]);
}
private function auto_login(){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
if ( FALSE === $this->clientuseautologin() ){ return FALSE; }
$COOKRef = configs::get('realm', 'realm', Array($this->realm, 'cookname'));
$autolog = cookies::read('autolog', $COOKRef);
$ruid = $autolog[$this->realm][0];
$key = $autolog[$this->realm][1];
unset($autolog, $COOKRef);
$this->load_accounts();
if ( FALSE === $this->accounts->cookcheck($ruid, $key) ){ return FALSE; }
$ticket = $this->newticket();
if ( FALSE === $ticket ){ return FALSE; }
if ( TRUE !== $this->apply_login($ruid, $ticket) ){ return FALSE; }
$this->accounts->update_lastlogin($ruid);
$key = CORE::hash();
$this->set_client_autokey($ruid, $key);
$this->account->set_cookey($ruid, $key);
return TRUE;
}
private function set_client_autokey($who, $key){
if ( FALSE === configs::get('realm', 'realm', Array($this->realm, 'auto_login')) ){ return FALSE; }
$COOKRef = configs::get('realm', 'realm', Array($this->realm, 'cookname'));
$autolog = cookies::read('autolog', $COOKRef);
$autolog[$this->realm] = Array($who, $key);
return cookies::set('autolog', $autolog, $COOKRef);
}
private function del_client_autokey(){
$COOKRef = configs::get('realm', 'realm', Array($this->realm, 'cookname'));
$autolog = cookies::read('autolog', $COOKRef);
unset($autolog[$this->realm]);
return cookies::set('autolog', $autolog, $COOKRef);
}
//-------------------------------------------------------------------AUTO LOGIN
//-------------------------------------------------------------------ACL ACCESS RIGTHS
public function ispublic($obj, $action, $objid){
if ( FALSE === @constant('CORE_AUTH') ){ return TRUE; }
$this->load_acl();
//is everybody allowed to do $action on object $obj with id $objid ?
$r = $this->acl->ispublic($obj, $action, $objid);
//errors::raise("realm::ispublic($obj, $action, $objid) => $r", CORE_LOG_NOTICE, 'REALM');
return $r;
}
public function checks($obj, $action, $objid){
if ( FALSE === @constant('CORE_AUTH') ){ return TRUE; }
$this->load_acl();
$ruid = $this->logedas();
$mbrship = $this->ismemberof();
//echo "auth check on $obj<=>$action as $uid <=> ".print_r($mbrship, TRUE)."<br/>\n";
$r = $this->acl->checks($obj, $action, $ruid, $mbrship, $objid);
//errors::raise("realm::checks($obj, $action, $objid) => $r", CORE_LOG_NOTICE, 'REALM');
return $r;
}
public function chmod_checks($obj, $ac=NULL){
if ( FALSE === @constant('CORE_AUTH') ){ return TRUE; }
$this->load_acl();
$uid = $this->logedas();
$mbrship = $this->ismemberof();
$action = 'chmod';
if ( $ac !== NULL ){ $action = "chmod_$ac"; }
$r = $this->_RIGHT->checks('objects', $action, $uid, $mbrship, $obj);
//echo "cchmod check => obj:$obj, action: $action => $r<br>";
//echo "<pre>";print_r($mbrship);echo "</pre>";
return $r;
}
//-------------------------------------------------------------------ACL ACCESS RIGTHS
//-------------------------------------------------------------------ACCOUNTS INFOS
public function loginbyuid($ruid){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
$this->load_accounts();
return $this->accounts->infobyuid('login', $ruid);
}
protected function getpass($ruid){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
$this->load_accounts();
return $this->accounts->getpass($ruid);
}
protected function authcheck($login, $pass){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
$this->load_accounts();
return $this->accounts->authcheck($login, $pass);
}
protected function uidbylogin($login){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
$this->load_accounts();return $this->accounts->uidbylogin($login);
}
public function groupbygid($rgid){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
$this->load_accounts();return $this->accounts->groupbygid($rgid);
}
//-------------------------------------------------------------------ACCOUNTS INFOS
public function get_profiledata(){
if ( FALSE === @constant('CORE_AUTH') ){ return FALSE; }
$o['user'] = $this->loginbyuid($this->logedas());
$ticket = $this->get_active_ticket();
$tmp['granted'] = time() - $this->grant_time($ticket);
$tmp['exp'] = configs::get('realm', 'realm', Array($this->realm, 'lifetime')) - (time() - $this->create_time($ticket) );
$tmp['to'] = configs::get('realm', 'realm', Array($this->realm, 'timeout'));
foreach($tmp as $k => &$int){
if ( $int <= 60 ){
$o[$k] = $int.' secondes';
} elseif( $int <= 3600 ){
$o[$k] = floor($int/60).' '.langs::translate('minutes', NULL, 'base');
} elseif( $int <= 86400 ){
$o[$k] = floor($int/(60*60)).' '.langs::translate('hours', NULL, 'base');
} else {
$o[$k] = floor($int/(24*60*60)).' '.langs::translate('days', NULL, 'base');
}
}
unset($tmp);
$buff = $this->ismemberof();
foreach($buff as $lvl => &$mbrship ){
foreach($mbrship as $k => $grp ){
$gname = $this->groupbygid($grp);
if ( $lvl == 1 ){ $gname = '<b>'.$gname.'</b>'; }
$o['mbrship'][] = $gname;
}
}
unset($buff);
$o['passchange'] = FALSE;
$o['mailchange'] = FALSE;
return $o;
}
public function list_users(){ return FALSE; }
}