parent
2a9d7aa187
commit
aab69a41e8
|
@ -0,0 +1,376 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
use Icinga\Application\Platform;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Logger as Log;
|
||||
|
||||
/**
|
||||
* Connection class
|
||||
*
|
||||
* @package Icinga\Protocol\Ldap
|
||||
*/
|
||||
/**
|
||||
* Backend class managing all the LDAP stuff for you.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* <code>
|
||||
* $lconf = new Connection((object) array(
|
||||
* 'hostname' => 'localhost',
|
||||
* 'root_dn' => 'dc=monitoring,dc=...',
|
||||
* 'bind_dn' => 'cn=Mangager,dc=monitoring,dc=...',
|
||||
* 'bind_pw' => '***'
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
protected $ds;
|
||||
protected $hostname;
|
||||
protected $bind_dn;
|
||||
protected $bind_pw;
|
||||
protected $root_dn;
|
||||
protected $count;
|
||||
protected $ldap_extension = array(
|
||||
'1.3.6.1.4.1.1466.20037' => 'STARTTLS', // notes?
|
||||
// '1.3.6.1.4.1.4203.1.11.1' => '11.1', // PASSWORD_MODIFY
|
||||
// '1.3.6.1.4.1.4203.1.11.3' => '11.3', // Whoami
|
||||
// '1.3.6.1.1.8' => '8', // Cancel Extended Request
|
||||
);
|
||||
|
||||
protected $ms_capability = array(
|
||||
// Prefix LDAP_CAP_
|
||||
// Source: http://msdn.microsoft.com/en-us/library/cc223359.aspx
|
||||
|
||||
// Running Active Directory as AD DS:
|
||||
'1.2.840.113556.1.4.800' => 'ACTIVE_DIRECTORY_OID',
|
||||
|
||||
// Capable of signing and sealing on an NTLM authenticated connection
|
||||
// and of performing subsequent binds on a signed or sealed connection.
|
||||
'1.2.840.113556.1.4.1791' => 'ACTIVE_DIRECTORY_LDAP_INTEG_OID',
|
||||
|
||||
// If AD DS: running at least W2K3, if AD LDS running at least W2K8
|
||||
'1.2.840.113556.1.4.1670' => 'ACTIVE_DIRECTORY_V51_OID',
|
||||
|
||||
// If AD LDS: accepts DIGEST-MD5 binds for AD LDSsecurity principals
|
||||
'1.2.840.113556.1.4.1880' => 'ACTIVE_DIRECTORY_ADAM_DIGEST',
|
||||
|
||||
// Running Active Directory as AD LDS
|
||||
'1.2.840.113556.1.4.1851' => 'ACTIVE_DIRECTORY_ADAM_OID',
|
||||
|
||||
// If AD DS: it's a Read Only DC (RODC)
|
||||
'1.2.840.113556.1.4.1920' => 'ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID',
|
||||
|
||||
// Running at least W2K8
|
||||
'1.2.840.113556.1.4.1935' => 'ACTIVE_DIRECTORY_V60_OID',
|
||||
|
||||
// Running at least W2K8r2
|
||||
'1.2.840.113556.1.4.2080' => 'ACTIVE_DIRECTORY_V61_R2_OID',
|
||||
|
||||
// Running at least W2K12
|
||||
'1.2.840.113556.1.4.2237' => 'ACTIVE_DIRECTORY_W8_OID',
|
||||
|
||||
);
|
||||
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* TODO: Allow to pass port and SSL options
|
||||
*
|
||||
* @param array LDAP connection credentials
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->hostname = $config->hostname;
|
||||
$this->bind_dn = $config->bind_dn;
|
||||
$this->bind_pw = $config->bind_pw;
|
||||
$this->root_dn = $config->root_dn;
|
||||
|
||||
}
|
||||
|
||||
public function getDN()
|
||||
{
|
||||
return $this->root_dn;
|
||||
}
|
||||
|
||||
public function root()
|
||||
{
|
||||
if ($this->root === null) {
|
||||
$this->root = Root::forConnection($this);
|
||||
}
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
public function select()
|
||||
{
|
||||
return new Query($this);
|
||||
}
|
||||
|
||||
public function fetchOne($query, $fields = array())
|
||||
{
|
||||
$row = (array) $this->fetchRow($query, $fields);
|
||||
return array_shift($row);
|
||||
}
|
||||
|
||||
public function fetchDN($query, $fields = array())
|
||||
{
|
||||
$rows = $this->fetchAll($query, $fields);
|
||||
if (count($rows) !== 1) {
|
||||
throw new Exception(sprintf(
|
||||
'Cannot fetch single DN for %s',
|
||||
$query
|
||||
));
|
||||
}
|
||||
return key($rows);
|
||||
}
|
||||
|
||||
|
||||
public function fetchRow($query, $fields = array())
|
||||
{
|
||||
// TODO: This is ugly, make it better!
|
||||
$results = $this->fetchAll($query, $fields);
|
||||
return array_shift($results);
|
||||
}
|
||||
|
||||
public function count(Query $query)
|
||||
{
|
||||
$results = $this->runQuery($query, '+');
|
||||
return ldap_count_entries($this->ds, $results);
|
||||
}
|
||||
|
||||
public function fetchAll($query, $fields = array())
|
||||
{
|
||||
$offset = null;
|
||||
$limit = null;
|
||||
if ($query->hasLimit()) {
|
||||
$offset = $query->getOffset();
|
||||
$limit = $query->getLimit();
|
||||
}
|
||||
$entries = array();
|
||||
$results = $this->runQuery($query, $fields);
|
||||
$entry = ldap_first_entry($this->ds, $results);
|
||||
$count = 0;
|
||||
while ($entry) {
|
||||
if (($offset === null || $offset <= $count)
|
||||
&& ($limit === null || ($offset + $limit) >= $count)
|
||||
) {
|
||||
$attrs = ldap_get_attributes($this->ds, $entry);
|
||||
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes($attrs);
|
||||
}
|
||||
$count++;
|
||||
$entry = ldap_next_entry($this->ds, $entry);
|
||||
}
|
||||
ldap_free_result($results);
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function cleanupAttributes(& $attrs)
|
||||
{
|
||||
$clean = (object) array();
|
||||
for ($i = 0; $i < $attrs['count']; $i++) {
|
||||
$attr_name = $attrs[$i];
|
||||
if ($attrs[$attr_name]['count'] === 1) {
|
||||
$clean->$attr_name = $attrs[$attr_name][0];
|
||||
} else {
|
||||
for ($j = 0; $j < $attrs[$attr_name]['count']; $j++) {
|
||||
$clean->{$attr_name}[] = $attrs[$attr_name][$j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
protected function runQuery($query, $fields)
|
||||
{
|
||||
$this->connect();
|
||||
if ($query instanceof Query) {
|
||||
$fields = $query->listFields();
|
||||
}
|
||||
// WARNING:
|
||||
// We do not support pagination right now, and there is no chance to
|
||||
// do so for PHP < 5.4. Warnings about "Sizelimit exceeded" will
|
||||
// therefore not be hidden right now.
|
||||
$results = ldap_search(
|
||||
$this->ds,
|
||||
$this->root_dn,
|
||||
(string) $query,
|
||||
$fields,
|
||||
0, // Attributes and values
|
||||
0 // No limit - at least where possible
|
||||
);
|
||||
if (! $results) {
|
||||
throw new Exception(sprintf(
|
||||
'LDAP query "%s" (root %s) failed: %s',
|
||||
$query,
|
||||
$this->root_dn,
|
||||
ldap_error($this->ds)
|
||||
));
|
||||
die('Query failed');
|
||||
}
|
||||
$list = array();
|
||||
if ($query instanceof Query) {
|
||||
foreach ($query->getSortColumns() as $col) {
|
||||
ldap_sort($this->ds, $results, $col[0]) ;
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function testCredentials($username, $password)
|
||||
{
|
||||
Log::debug("Trying to connect to %s", $this->hostname);
|
||||
$ds = ldap_connect($this->hostname);
|
||||
Log::debug("ldap_bind (%s)", $username);
|
||||
$r = @ldap_bind($ds, $username, $password);
|
||||
if ($r) {
|
||||
return true;
|
||||
} else {
|
||||
log::fatal('LDAP connection (%s / %s) failed: %s',
|
||||
$username,
|
||||
'***',
|
||||
ldap_error($ds));
|
||||
return false;
|
||||
/* TODO: Log failure
|
||||
throw new Exception(sprintf(
|
||||
'LDAP connection (%s / %s) failed: %s',
|
||||
$username,
|
||||
'***',
|
||||
ldap_error($ds)
|
||||
));
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
protected function getConfigDir()
|
||||
{
|
||||
return Config::getInstance()->getConfigDir() . '/ldap';
|
||||
}
|
||||
|
||||
protected function discoverServerlistForDomain($domain)
|
||||
{
|
||||
$ldaps_records = dns_get_record('_ldaps._tcp.' . $domain, DNS_SRV);
|
||||
$ldap_records = dns_get_record('_ldap._tcp.' . $domain, DNS_SRV);
|
||||
}
|
||||
|
||||
protected function prepareTlsEnvironment()
|
||||
{
|
||||
$strict_tls = true;
|
||||
$use_local_ca = true;
|
||||
if (Platform::isWindows()) {
|
||||
} else {
|
||||
$cfg_dir = $this->getConfigDir();
|
||||
if ($strict_tls) {
|
||||
putenv(sprintf('LDAPRC=%s/%s', $cfg_dir, 'ldap_ca.conf'));
|
||||
} else {
|
||||
putenv(sprintf('LDAPRC=%s/%s', $cfg_dir, 'ldap_nocert.conf'));
|
||||
}
|
||||
}
|
||||
// file_put_contents('/tmp/tom_LDAP.conf', "TLS_REQCERT never\n");
|
||||
}
|
||||
|
||||
protected function fetchRootDseDetails()
|
||||
{
|
||||
$query = $this->select()->from('*', array('+'))
|
||||
/*, array(
|
||||
'defaultNamingContext',
|
||||
'namingContexts',
|
||||
'supportedSaslMechanisms',
|
||||
'dnsHostName',
|
||||
'schemaNamingContext',
|
||||
'supportedLDAPVersion', // => array(3, 2)
|
||||
'supportedCapabilities'
|
||||
))*/
|
||||
;
|
||||
$fields = $query->listFields();
|
||||
|
||||
$result = ldap_read(
|
||||
$this->ds,
|
||||
'',
|
||||
(string) $query,
|
||||
$query->listFields(),
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
$entry = ldap_first_entry($this->ds, $result);
|
||||
$result = $this->cleanupAttributes(ldap_get_attributes($this->ds, $entry));
|
||||
|
||||
|
||||
if (isset($result->supportedCapabilities)) {
|
||||
foreach ($result->supportedCapabilities as $oid) {
|
||||
if (array_key_exists($oid, $this->ms_capability)) {
|
||||
echo $this->ms_capability[$oid] . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($result->supportedExtension)) {
|
||||
foreach ($result->supportedExtension as $oid) {
|
||||
if (array_key_exists($oid, $this->ldap_extension)) {
|
||||
echo $this->ldap_extension[$oid] . "\n";
|
||||
// STARTTLS -> läuft mit OpenLDAP
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function discoverCapabilities()
|
||||
{
|
||||
$this->fetchRootDseDetails();
|
||||
}
|
||||
|
||||
public function connect()
|
||||
{
|
||||
if ($this->ds !== null) return;
|
||||
$use_tls = true;
|
||||
$force_tls = true;
|
||||
|
||||
if ($use_tls) {
|
||||
$this->prepareTlsEnvironment();
|
||||
}
|
||||
Log::debug("Trying to connect to %s", $this->hostname);
|
||||
$this->ds = ldap_connect($this->hostname, 389);
|
||||
$this->discoverCapabilities();
|
||||
Log::debug("Trying ldap_start_tls()");
|
||||
if (ldap_start_tls($this->ds)) {
|
||||
Log::debug("Trying ldap_start_tls() succeeded");
|
||||
} else {
|
||||
Log::warn("ldap_start_tls() failed: %s. Does your ldap_ca.conf point to the certificate? ",ldap_error($this->ds));
|
||||
}
|
||||
|
||||
|
||||
// ldap_rename requires LDAPv3:
|
||||
if (! ldap_set_option($this->ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
||||
throw new Exception('LDAPv3 is required');
|
||||
}
|
||||
//
|
||||
// Not setting this results in "Operations error" on AD when using the
|
||||
// whole domain as search base:
|
||||
ldap_set_option($this->ds, LDAP_OPT_REFERRALS, 0);
|
||||
// ldap_set_option($this->ds, LDAP_OPT_DEREF, LDAP_DEREF_NEVER);
|
||||
Log::debug("Trying ldap_bind(%s)",$this->bind_dn);
|
||||
$r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw);
|
||||
|
||||
if (! $r) {
|
||||
log::fatal('LDAP connection (%s / %s) failed: %s',
|
||||
$this->bind_dn,
|
||||
'***',
|
||||
ldap_error($this->ds));
|
||||
throw new Exception(sprintf(
|
||||
'LDAP connection (%s / %s) failed: %s',
|
||||
$this->bind_dn,
|
||||
'***' /* $this->bind_pw */,
|
||||
ldap_error($this->ds)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
/**
|
||||
* LdapUtils class
|
||||
*
|
||||
* @package Icinga\Protocol\Ldap
|
||||
*/
|
||||
/**
|
||||
* This class provides useful LDAP-related functions
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class LdapUtils
|
||||
{
|
||||
/**
|
||||
* Extends PHPs ldap_explode_dn() function
|
||||
*
|
||||
* UTF-8 chars like German umlauts would otherwise be escaped and shown
|
||||
* as backslash-prefixed hexcode-sequenzes.
|
||||
*
|
||||
* @param string DN
|
||||
* @param boolean Returns 'type=value' when true and 'value' when false
|
||||
* @return string
|
||||
*/
|
||||
public static function explodeDN($dn, $with_type = true)
|
||||
{
|
||||
$res = ldap_explode_dn($dn, $with_type ? 0 : 1);
|
||||
foreach ($res as $k => $v) {
|
||||
$res[$k] = preg_replace(
|
||||
'/\\\([0-9a-f]{2})/ei',
|
||||
"chr(hexdec('\\1'))",
|
||||
$v
|
||||
);
|
||||
}
|
||||
unset($res['count']);
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implode unquoted RDNs to a DN
|
||||
*
|
||||
* TODO: throw away, this is not how it shall be done
|
||||
*
|
||||
* @param string DN-component
|
||||
* @return string
|
||||
*/
|
||||
public static function implodeDN($parts)
|
||||
{
|
||||
$str = '';
|
||||
foreach ($parts as $part) {
|
||||
if ($str !== '') { $str .= ','; }
|
||||
list($key, $val) = preg_split('~=~', $part, 2);
|
||||
$str .= $key . '=' . self::quoteForDN($val);
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote a string that should be used in a DN
|
||||
*
|
||||
* Special characters will be escaped
|
||||
*
|
||||
* @param string DN-component
|
||||
* @return string
|
||||
*/
|
||||
public static function quoteForDN($str)
|
||||
{
|
||||
return self::quoteChars($str, array(
|
||||
',', '=', '+', '<', '>', ';', '\\', '"', '#'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote a string that should be used in an LDAP search
|
||||
*
|
||||
* Special characters will be escaped
|
||||
*
|
||||
* @param string String to be escaped
|
||||
* @return string
|
||||
*/
|
||||
public static function quoteForSearch($str, $allow_wildcard = false)
|
||||
{
|
||||
if ($allow_wildcard) {
|
||||
return self::quoteChars($str, array('(', ')', '\\', chr(0)));
|
||||
}
|
||||
return self::quoteChars($str, array('*', '(', ')', '\\', chr(0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape given characters in the given string
|
||||
*
|
||||
* Special characters will be escaped
|
||||
*
|
||||
* @param string String to be escaped
|
||||
* @return string
|
||||
*/
|
||||
protected static function quoteChars($str, $chars)
|
||||
{
|
||||
$quotedChars = array();
|
||||
foreach ($chars as $k => $v) {
|
||||
// Temporarily prefixing with illegal '('
|
||||
$quotedChars[$k] = '(' . str_pad(dechex(ord($v)), 2, '0');
|
||||
}
|
||||
$str = str_replace($chars, $quotedChars, $str);
|
||||
// Replacing temporary '(' with '\\'. This is a workaround, as
|
||||
// str_replace behaves pretty strange with leading a backslash:
|
||||
$str = preg_replace('~\(~', '\\', $str);
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
/**
|
||||
* Node class
|
||||
*
|
||||
* @package Icinga\Protocol\Ldap
|
||||
*/
|
||||
/**
|
||||
* This class represents an LDAP node object
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class Node extends Root
|
||||
{
|
||||
protected $connection;
|
||||
protected $rdn;
|
||||
protected $parent;
|
||||
|
||||
protected function __construct(Root $parent)
|
||||
{
|
||||
$this->connection = $parent->getConnection();
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
public static function createWithRDN($parent, $rdn, $props = array())
|
||||
{
|
||||
$node = new Node($parent);
|
||||
$node->rdn = $rdn;
|
||||
$node->props = $props;
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function getRDN()
|
||||
{
|
||||
return $this->rdn;
|
||||
}
|
||||
|
||||
public function getDN()
|
||||
{
|
||||
return $this->parent->getDN() . '.' . $this->getRDN();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
/**
|
||||
* Search class
|
||||
*
|
||||
* @package Icinga\Protocol\Ldap
|
||||
*/
|
||||
/**
|
||||
* Search abstraction class
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* <code>
|
||||
* $connection->select()->from('user')->where('sAMAccountName = ?', 'icinga');
|
||||
* </code>
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class Query
|
||||
{
|
||||
protected $connection;
|
||||
protected $filters = array();
|
||||
protected $fields = array();
|
||||
protected $limit_count;
|
||||
protected $limit_offset;
|
||||
protected $sort_columns = array();
|
||||
protected $count;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Connection LDAP Connection object
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count result set, ignoring limits
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
if ($this->count === null) {
|
||||
$this->count = $this->connection->count($this);
|
||||
}
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count result set, ignoring limits
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function limit($count = null, $offset = null)
|
||||
{
|
||||
if (! preg_match('~^\d+~', $count . $offset)) {
|
||||
throw new Exception(sprintf(
|
||||
'Got invalid limit: %s, %s',
|
||||
$count,
|
||||
$offset
|
||||
));
|
||||
}
|
||||
$this->limit_count = (int) $count;
|
||||
$this->limit_offset = (int) $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a limit has been set
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasLimit()
|
||||
{
|
||||
return $this->limit_count !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an offset (limit) has been set
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasOffset()
|
||||
{
|
||||
return $this->limit_offset > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve result limit
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLimit()
|
||||
{
|
||||
return $this->limit_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve result offset
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOffset()
|
||||
{
|
||||
return $this->limit_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch result as tree
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public function fetchTree()
|
||||
{
|
||||
$result = $this->fetchAll();
|
||||
$sorted = array();
|
||||
foreach ($result as $key => & $item)
|
||||
{
|
||||
$new_key = LdapUtils::implodeDN(array_reverse(LdapUtils::explodeDN(
|
||||
preg_replace(
|
||||
'/,' . preg_quote($this->connection->getDN(), '/') . '$/',
|
||||
'',
|
||||
$key
|
||||
)
|
||||
)));
|
||||
$sorted[$new_key] = $key;
|
||||
}
|
||||
unset($groups);
|
||||
ksort($sorted);
|
||||
|
||||
$tree = Root::forConnection($this->connection);
|
||||
foreach ($sorted as $sort_key => & $key) {
|
||||
$tree->createChildByDN($key, $result[$key]);
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch result as an array of objects
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll()
|
||||
{
|
||||
return $this->connection->fetchAll($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first result row
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function fetchRow()
|
||||
{
|
||||
return $this->connection->fetchRow($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first column value from first result row
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetchOne()
|
||||
{
|
||||
return $this->connection->fetchOne($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a key/value list, first column is key, second is value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchPairs()
|
||||
{
|
||||
// STILL TODO!!
|
||||
return $this->connection->fetchPairs($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Where to select (which fields) from
|
||||
*
|
||||
* This creates an objectClass filter
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function from($objectClass, $fields = array())
|
||||
{
|
||||
$this->filters['objectClass'] = $objectClass;
|
||||
$this->fields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new filter to the query
|
||||
*
|
||||
* @param string Column to search in
|
||||
* @param string Filter text (asterisks are allowed)
|
||||
* @return Query
|
||||
*/
|
||||
public function where($key, $val)
|
||||
{
|
||||
$this->filters[$key] = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort by given column
|
||||
*
|
||||
* TODO: Sort direction is not implemented yet
|
||||
*
|
||||
* @param string Order column
|
||||
* @param string Order direction
|
||||
* @return Query
|
||||
*/
|
||||
public function order($column, $direction = 'ASC')
|
||||
{
|
||||
$this->sort_columns[] = array($column, $direction);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of the desired fields
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listFields()
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list containing current sort columns
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSortColumns()
|
||||
{
|
||||
return $this->sort_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a pagination adapter for the current query
|
||||
*
|
||||
* @return \Zend_Paginator
|
||||
*/
|
||||
public function paginate($limit = null, $page = null)
|
||||
{
|
||||
if ($page === null || $limit === null) {
|
||||
$request = \Zend_Controller_Front::getInstance()->getRequest();
|
||||
if ($page === null) {
|
||||
$page = $request->getParam('page', 0);
|
||||
}
|
||||
if ($limit === null) {
|
||||
$limit = $request->getParam('limit', 20);
|
||||
}
|
||||
}
|
||||
$paginator = new \Zend_Paginator(
|
||||
// TODO: Adapter doesn't fit yet:
|
||||
new \Icinga\Web\Paginator\Adapter\QueryAdapter($this)
|
||||
);
|
||||
$paginator->setItemCountPerPage($limit);
|
||||
$paginator->setCurrentPageNumber($page);
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LDAP filter that will be applied
|
||||
*
|
||||
* @string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LDAP filter that will be applied
|
||||
*
|
||||
* @string
|
||||
*/
|
||||
protected function render()
|
||||
{
|
||||
$parts = array();
|
||||
if ($this->filters['objectClass'] === null) {
|
||||
throw new Exception('Object class is mandatory');
|
||||
}
|
||||
foreach ($this->filters as $key => $value) {
|
||||
$parts[] = sprintf(
|
||||
'%s=%s',
|
||||
LdapUtils::quoteForSearch($key),
|
||||
LdapUtils::quoteForSearch($value, true)
|
||||
);
|
||||
}
|
||||
return '(&(' . implode(')(', $parts) . '))';
|
||||
}
|
||||
|
||||
/**
|
||||
* Descructor
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// To be on the safe side:
|
||||
unset($this->connection);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
/**
|
||||
* Root class
|
||||
*
|
||||
* @package Icinga\Protocol\Ldap
|
||||
*/
|
||||
/**
|
||||
* This class is a special node object, representing your connections root node
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class Root
|
||||
{
|
||||
protected $rdn;
|
||||
protected $connection;
|
||||
protected $children = array();
|
||||
protected $props = array();
|
||||
|
||||
protected function __construct(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function hasParent()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function forConnection(Connection $connection)
|
||||
{
|
||||
$root = new Root($connection);
|
||||
return $root;
|
||||
}
|
||||
|
||||
public function createChildByDN($dn, $props = array())
|
||||
{
|
||||
$dn = $this->stripMyDN($dn);
|
||||
$parts = array_reverse(LdapUtils::explodeDN($dn));
|
||||
$parent = $this;
|
||||
while($rdn = array_shift($parts)) {
|
||||
if ($parent->hasChildRDN($rdn)) {
|
||||
$child = $parent->getChildByRDN($rdn);
|
||||
} else {
|
||||
$child = Node::createWithRDN($parent, $rdn, (array) $props);
|
||||
$parent->addChild($child);
|
||||
}
|
||||
$parent = $child;
|
||||
}
|
||||
return $child;
|
||||
}
|
||||
|
||||
public function hasChildRDN($rdn)
|
||||
{
|
||||
return array_key_exists(strtolower($rdn), $this->children);
|
||||
}
|
||||
|
||||
public function getChildByRDN($rdn)
|
||||
{
|
||||
if (! $this->hasChildRDN($rdn)) {
|
||||
throw new Exception(sprintf(
|
||||
'The child RDN "%s" is not available',
|
||||
$rdn
|
||||
));
|
||||
}
|
||||
return $this->children[strtolower($rdn)];
|
||||
}
|
||||
|
||||
public function children()
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function hasChildren()
|
||||
{
|
||||
return ! empty($this->children);
|
||||
}
|
||||
|
||||
public function addChild(Node $child)
|
||||
{
|
||||
$this->children[strtolower($child->getRDN())] = $child;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function stripMyDN($dn)
|
||||
{
|
||||
$this->assertSubDN($dn);
|
||||
return substr($dn, 0, strlen($dn) - strlen($this->getDN()) - 1);
|
||||
}
|
||||
|
||||
protected function assertSubDN($dn)
|
||||
{
|
||||
$mydn = $this->getDN();
|
||||
$end = substr($dn, -1 * strlen($mydn));
|
||||
if (strtolower($end) !== strtolower($mydn)) {
|
||||
throw new Exception(sprintf(
|
||||
'"%s" is not a child of "%s"',
|
||||
$dn,
|
||||
$mydn
|
||||
));
|
||||
}
|
||||
if (strlen($dn) === strlen($mydn)) {
|
||||
throw new Exception(sprintf(
|
||||
'"%s" is not a child of "%s", they are equal',
|
||||
$dn,
|
||||
$mydn
|
||||
));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setConnection(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
public function hasBeenChanged()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getRDN()
|
||||
{
|
||||
return $this->getDN();
|
||||
}
|
||||
|
||||
public function getDN()
|
||||
{
|
||||
return $this->connection->getDN();
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
if (! array_key_exists($key, $this->props)) {
|
||||
return null;
|
||||
}
|
||||
return $this->props[$key];
|
||||
}
|
||||
|
||||
public function __isset($key)
|
||||
{
|
||||
return array_key_exists($key, $this->props);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Action controller
|
||||
*/
|
||||
namespace Icinga\Web;
|
||||
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\Application\Benchmark;
|
||||
use Icinga\Exception;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Web\Notification;
|
||||
use Zend_Layout as ZfLayout;
|
||||
use Zend_Controller_Action as ZfController;
|
||||
use Zend_Controller_Request_Abstract as ZfRequest;
|
||||
use Zend_Controller_Response_Abstract as ZfResponse;
|
||||
use Zend_Controller_Action_HelperBroker as ZfActionHelper;
|
||||
|
||||
/**
|
||||
* Base class for all core action controllers
|
||||
*
|
||||
* All Icinga Web core controllers should extend this class
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class ActionController extends ZfController
|
||||
{
|
||||
/**
|
||||
* The Icinga Config object is available in all controllers. This is the
|
||||
* modules config for module action controllers.
|
||||
*
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
protected $replaceLayout = false;
|
||||
|
||||
/**
|
||||
* The current module name. TODO: Find out whether this shall be null for
|
||||
* non-module actions
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $module_name;
|
||||
|
||||
/**
|
||||
* The current controller name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $controller_name;
|
||||
|
||||
/**
|
||||
* The current action name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $action_name;
|
||||
|
||||
protected $handlesAuthentication = false;
|
||||
|
||||
protected $modifiesSession = false;
|
||||
|
||||
protected $allowAccess = false;
|
||||
|
||||
/**
|
||||
* The constructor starts benchmarking, loads the configuration and sets
|
||||
* other useful controller properties
|
||||
*
|
||||
* @param ZfRequest $request
|
||||
* @param ZfResponse $response
|
||||
* @param array $invokeArgs Any additional invocation arguments
|
||||
*/
|
||||
public function __construct(
|
||||
ZfRequest $request,
|
||||
ZfResponse $response,
|
||||
array $invokeArgs = array()
|
||||
) {
|
||||
Benchmark::measure('Action::__construct()');
|
||||
if (Auth::getInstance()->isAuthenticated()
|
||||
&& ! $this->modifiesSession
|
||||
&& ! Notification::getInstance()->hasMessages()
|
||||
) {
|
||||
session_write_close();
|
||||
}
|
||||
$this->module_name = $request->getModuleName();
|
||||
$this->controller_name = $request->getControllerName();
|
||||
$this->action_name = $request->getActionName();
|
||||
|
||||
$this->loadConfig();
|
||||
$this->setRequest($request)
|
||||
->setResponse($response)
|
||||
->_setInvokeArgs($invokeArgs);
|
||||
$this->_helper = new ZfActionHelper($this);
|
||||
|
||||
if ($this->handlesAuthentication()
|
||||
|| Auth::getInstance()->isAuthenticated()) {
|
||||
$this->allowAccess = true;
|
||||
$this->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is where the configuration is going to be loaded
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function loadConfig()
|
||||
{
|
||||
$this->config = Config::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given string with the global translation catalog
|
||||
*
|
||||
* @param string $string The string that should be translated
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function translate($string)
|
||||
{
|
||||
return t($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function creating a new widget
|
||||
*
|
||||
* @param string $name The widget name
|
||||
* @param string $properties Optional widget properties
|
||||
*
|
||||
* @return Widget\AbstractWidget
|
||||
*/
|
||||
public function widget($name, $properties = array())
|
||||
{
|
||||
return Widget::create($name, $properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current user has the given permission
|
||||
*
|
||||
* TODO: This has not been implemented yet
|
||||
*
|
||||
* @param string $permission Permission name
|
||||
* @param string $object No idea what this should have been :-)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final protected function hasPermission($uri, $permission = 'read')
|
||||
{
|
||||
return Auth::getInstance()->hasPermission($uri, $permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the current user has the given permission
|
||||
*
|
||||
* TODO: This has not been implemented yet
|
||||
*
|
||||
* @param string $permission Permission name
|
||||
* @param string $object No idea what this should have been :-)
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
final protected function assertPermission($permission, $object = null)
|
||||
{
|
||||
if (! $this->hasPermission($permission, $object)) {
|
||||
// TODO: Log violation, create dedicated Exception class
|
||||
throw new \Exception('Permission denied');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our benchmark wants to know when we started our dispatch loop
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function preDispatch()
|
||||
{
|
||||
Benchmark::measure('Action::preDispatch()');
|
||||
if (! $this->allowAccess) {
|
||||
$this->_request->setModuleName('default')
|
||||
->setControllerName('authentication')
|
||||
->setActionName('login')
|
||||
->setDispatched(false);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->view->action_name = $this->action_name;
|
||||
$this->view->controller_name = $this->controller_name;
|
||||
$this->view->module_name = $this->module_name;
|
||||
|
||||
//$this->quickRedirect('/authentication/login?a=e');
|
||||
}
|
||||
|
||||
public function redirectNow($url, array $params = array())
|
||||
{
|
||||
$this->_helper->Redirector->gotoUrlAndExit($url);
|
||||
}
|
||||
|
||||
public function handlesAuthentication()
|
||||
{
|
||||
return $this->handlesAuthentication;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render our benchmark
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderBenchmark()
|
||||
{
|
||||
return '<pre class="benchmark">'
|
||||
. Benchmark::renderToHtml()
|
||||
. '</pre>';
|
||||
}
|
||||
|
||||
/**
|
||||
* After dispatch happend we are going to do some automagic stuff
|
||||
*
|
||||
* - Benchmark is completed and rendered
|
||||
* - Notifications will be collected here
|
||||
* - Layout is disabled for XHR requests
|
||||
* - TODO: Headers with required JS and other things will be created
|
||||
* for XHR requests
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function postDispatch()
|
||||
{
|
||||
Benchmark::measure('Action::postDispatch()');
|
||||
|
||||
|
||||
// TODO: Move this elsewhere, this is just an ugly test:
|
||||
if ($this->_request->getParam('filetype') === 'pdf') {
|
||||
|
||||
// Snippet stolen from less compiler in public/css.php:
|
||||
|
||||
require_once 'vendor/lessphp/lessc.inc.php';
|
||||
$less = new \lessc;
|
||||
$cssdir = dirname(ICINGA_LIBDIR) . '/public/css';
|
||||
// TODO: We need a way to retrieve public dir, even if located elsewhere
|
||||
|
||||
$css = $less->compileFile($cssdir . '/pdfprint.less');
|
||||
/*
|
||||
foreach ($app->moduleManager()->getLoadedModules() as $name => $module) {
|
||||
if ($module->hasCss()) {
|
||||
$css .= $less->compile(
|
||||
'.icinga-module.module-'
|
||||
. $name
|
||||
. " {\n"
|
||||
. file_get_contents($module->getCssFilename())
|
||||
. "}\n\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// END of CSS test
|
||||
|
||||
$this->render(
|
||||
null,
|
||||
$this->_helper->viewRenderer->getResponseSegment(),
|
||||
$this->_helper->viewRenderer->getNoController()
|
||||
);
|
||||
$html = '<style>' . $css . '</style>' . (string) $this->getResponse();
|
||||
|
||||
$pdf = new \Icinga\Pdf\File();
|
||||
$pdf->AddPage();
|
||||
$pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true);
|
||||
$pdf->Output('docs.pdf', 'I');
|
||||
exit;
|
||||
}
|
||||
// END of PDF test
|
||||
|
||||
|
||||
if ($this->_request->isXmlHttpRequest()) {
|
||||
if ($this->replaceLayout || $this->_getParam('_render') === 'body') {
|
||||
$this->_helper->layout()->setLayout('just-the-body');
|
||||
header('X-Icinga-Target: body');
|
||||
} else {
|
||||
$this->_helper->layout()->setLayout('inline');
|
||||
}
|
||||
}
|
||||
$notification = Notification::getInstance();
|
||||
if ($notification->hasMessages()) {
|
||||
$nhtml = '<ul class="notification">';
|
||||
foreach ($notification->getMessages() as $msg) {
|
||||
$nhtml .= '<li>['
|
||||
. $msg->type
|
||||
. '] '
|
||||
. htmlspecialchars($msg->message);
|
||||
}
|
||||
$nhtml .= '</ul>';
|
||||
$this->getResponse()->append('notification', $nhtml);
|
||||
}
|
||||
|
||||
if (Session::getInstance()->show_benchmark) {
|
||||
Benchmark::measure('Response ready');
|
||||
$this->getResponse()->append('benchmark', $this->renderBenchmark());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the token parameter is valid
|
||||
*
|
||||
* TODO: Could this make use of Icinga\Web\Session once done?
|
||||
*
|
||||
* @param int $maxAge Max allowed token age
|
||||
* @param string $sessionId A specific session id (useful for tests?)
|
||||
*
|
||||
* return bool
|
||||
*/
|
||||
public function hasValidToken($maxAge = 600, $sessionId = null)
|
||||
{
|
||||
$sessionId = $sessionId ? $sessionId : session_id();
|
||||
$seed = $this->_getParam('seed');
|
||||
if (! is_numeric($seed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove quantitized timestamp portion so maxAge applies
|
||||
$seed -= (intval(time() / $maxAge) * $maxAge);
|
||||
$token = $this->_getParam('token');
|
||||
return $token === hash('sha256', $sessionId . $seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new seed/token pair
|
||||
*
|
||||
* TODO: Could this make use of Icinga\Web\Session once done?
|
||||
*
|
||||
* @param int $maxAge Max allowed token age
|
||||
* @param string $sessionId A specific session id (useful for tests?)
|
||||
*
|
||||
* return array
|
||||
*/
|
||||
public function getSeedTokenPair($maxAge = 600, $sessionId = null)
|
||||
{
|
||||
$sessionId = $sessionId ? $sessionId : session_id();
|
||||
$seed = mt_rand();
|
||||
$hash = hash('sha256', $sessionId . $seed);
|
||||
|
||||
// Add quantitized timestamp portion to apply maxAge
|
||||
$seed += (intval(time() / $maxAge) * $maxAge);
|
||||
return array($seed, $hash);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Web;
|
||||
use Icinga\Application\Logger as Log;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
|
||||
class Hook
|
||||
{
|
||||
protected static $hooks = array();
|
||||
protected static $instances = array();
|
||||
public static $BASE_NS = 'Icinga\\Web\\Hook\\';
|
||||
|
||||
public static function clean()
|
||||
{
|
||||
self::$hooks = array();
|
||||
self::$instances = array();
|
||||
self::$BASE_NS = 'Icinga\\Web\\Hook\\';
|
||||
}
|
||||
|
||||
public static function has($name,$key=null)
|
||||
{
|
||||
if ($key !== null) {
|
||||
return isset(self::$hooks[$name][$key]);
|
||||
} else {
|
||||
return isset(self::$hooks[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function createInstance($name,$key)
|
||||
{
|
||||
if (!self::has($name,$key)) {
|
||||
return null;
|
||||
}
|
||||
if (isset(self::$instances[$name][$key])) {
|
||||
return self::$instances[$name][$key];
|
||||
}
|
||||
$class = self::$hooks[$name][$key];
|
||||
try {
|
||||
$instance = new $class();
|
||||
} catch (\Exception $e) {
|
||||
Log::debug(
|
||||
'Hook "%s" (%s) (%s) failed, will be unloaded: %s',
|
||||
$name,
|
||||
$key,
|
||||
$class,
|
||||
$e->getMessage()
|
||||
);
|
||||
unset(self::$hooks[$name][$key]);
|
||||
return null;
|
||||
}
|
||||
self::assertValidHook($instance,$name);
|
||||
self::$instances[$name][$key] = $instance;
|
||||
return $instance;
|
||||
}
|
||||
|
||||
private static function assertValidHook(&$instance, $name)
|
||||
{
|
||||
$base_class = self::$BASE_NS.ucfirst($name);
|
||||
if (! $instance instanceof $base_class) {
|
||||
throw new ProgrammingError(sprintf(
|
||||
'%s is not an instance of %s',
|
||||
get_class($instance),
|
||||
$base_class
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public static function all($name)
|
||||
{
|
||||
if (!self::has($name)) {
|
||||
return array();
|
||||
}
|
||||
foreach (self::$hooks[$name] as $key=>$hook) {
|
||||
if(self::createInstance($name,$key) === null)
|
||||
return array();
|
||||
}
|
||||
return self::$instances[$name];
|
||||
}
|
||||
|
||||
public static function first($name)
|
||||
{
|
||||
return self::createInstance($name,key(self::$hooks[$name]));
|
||||
}
|
||||
|
||||
public static function register($name, $key, $class)
|
||||
{
|
||||
self::$hooks[$name][$key] = $class;
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
|
@ -4,7 +4,7 @@ namespace Tests\Icinga\Web\ActionController;
|
|||
use Icinga\Web\ActionController as Action;
|
||||
|
||||
require_once('Zend/Controller/Action.php');
|
||||
require_once('../library/Icinga/Web/ActionController.php');
|
||||
require_once('../../library/Icinga/Web/ActionController.php');
|
||||
|
||||
/**
|
||||
* This is not a nice hack, but doesn't affect the behaviour of
|
||||
|
|
|
@ -7,8 +7,8 @@ namespace Tests\Icinga\Web;
|
|||
* Created Fri, 22 Mar 2013 09:44:40 +0000
|
||||
*
|
||||
**/
|
||||
require_once("../library/Icinga/Exception/ProgrammingError.php");
|
||||
require_once("../library/Icinga/Web/Hook.php");
|
||||
require_once("../../library/Icinga/Exception/ProgrammingError.php");
|
||||
require_once("../../library/Icinga/Web/Hook.php");
|
||||
|
||||
use Icinga\Web\Hook as Hook;
|
||||
class Base
|
||||
|
|
Loading…
Reference in New Issue