Merge branch 'master' into feature/doc-search-6630

This commit is contained in:
Eric Lippmann 2015-02-11 13:28:06 +01:00
commit 54292eed20
12 changed files with 270 additions and 194 deletions

View File

@ -0,0 +1,36 @@
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Accessibility: Text cue for required form control labels</title>
<meta name="description" content="Text cue for required form control labels">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
label.required span.required-indicator:after {
content: " *";
}
.sr-only {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
</style>
</head>
<body>
<form>
<label class="required">
Enter some text
<span class="required-indicator" aria-hidden="true"></span>
<span class="sr-only"> (required)</span>
</label>
<input type="text" name="some_text" value="" aria-required="true" required>
<input type="submit" name="btn_submit" value="Submit">
</form>
</body>
</html>

View File

@ -55,6 +55,14 @@ nginx:
./bin/icingacli setup config webserver nginx --document-root /usr/share/icingaweb2/public
````
Save the output as new file in your webserver's configuration directory.
Example for Apache on RHEL/CentOS:
````
./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb2/public > /etc/httpd/conf.d/icingaweb2.conf
````
**Step 4: Preparing Web Setup**
Because both web and CLI must have access to configuration and logs, permissions will be managed using a special

View File

@ -189,7 +189,7 @@ rm -rf %{buildroot}
mkdir -p %{buildroot}/{%{basedir}/{modules,library,public},%{bindir},%{configdir}/modules/setup,%{logdir},%{phpdir},%{wwwconfigdir},%{_sysconfdir}/bash_completion.d,%{docsdir}}
cp -prv application doc %{buildroot}/%{basedir}
cp -pv etc/bash_completion.d/icingacli %{buildroot}/%{_sysconfdir}/bash_completion.d/icingacli
cp -prv modules/{monitoring,setup} %{buildroot}/%{basedir}/modules
cp -prv modules/{monitoring,setup,doc,translation} %{buildroot}/%{basedir}/modules
cp -prv library/Icinga %{buildroot}/%{phpdir}
cp -prv library/vendor %{buildroot}/%{basedir}/library
cp -prv public/{css,img,js,error_norewrite.html} %{buildroot}/%{basedir}/public

View File

@ -5,6 +5,7 @@ namespace Icinga\Authentication\Backend;
use Icinga\User;
use Icinga\Authentication\UserBackend;
use Icinga\Protocol\Ldap\Query;
use Icinga\Protocol\Ldap\Connection;
use Icinga\Exception\AuthenticationException;
use Icinga\Protocol\Ldap\Exception as LdapException;
@ -15,7 +16,7 @@ class LdapUserBackend extends UserBackend
* Connection to the LDAP server
*
* @var Connection
**/
*/
protected $conn;
protected $baseDn;
@ -36,7 +37,9 @@ class LdapUserBackend extends UserBackend
}
/**
* @return \Icinga\Protocol\Ldap\Query
* Create a query to select all usernames
*
* @return Query
*/
protected function selectUsers()
{
@ -49,15 +52,15 @@ class LdapUserBackend extends UserBackend
}
/**
* Create query
* Create a query filtered by the given username
*
* @param string $username
*
* @return \Icinga\Protocol\Ldap\Query
**/
* @return Query
*/
protected function selectUser($username)
{
return $this->selectUsers()->where(
return $this->selectUsers()->setUsePagedResults(false)->where(
$this->userNameAttribute,
str_replace('*', '', $username)
);
@ -68,7 +71,7 @@ class LdapUserBackend extends UserBackend
*
* Try to bind to the backend and query all available users to check if:
* <ul>
* <li>User connection credentials are correct and the bind is possible</li>
* <li>Connection credentials are correct and the bind is possible</li>
* <li>At least one user exists</li>
* <li>The specified userClass has the property specified by userNameAttribute</li>
* </ul>
@ -78,13 +81,12 @@ class LdapUserBackend extends UserBackend
public function assertAuthenticationPossible()
{
try {
$q = $this->conn->select()->setBase($this->baseDn)->from($this->userClass);
$result = $q->fetchRow();
$result = $this->selectUsers()->fetchRow();
} catch (LdapException $e) {
throw new AuthenticationException('Connection not possible.', $e);
}
if (! isset($result)) {
if ($result === null) {
throw new AuthenticationException(
'No objects with objectClass="%s" in DN="%s" found.',
$this->userClass,
@ -139,17 +141,16 @@ class LdapUserBackend extends UserBackend
}
/**
* Test whether the given user exists
* Return whether the given user exists
*
* @param User $user
*
* @return bool
* @throws AuthenticationException
*/
public function hasUser(User $user)
{
$username = $user->getUsername();
$entry = $this->conn->fetchOne($this->selectUser($username));
$entry = $this->selectUser($username)->fetchOne();
if (is_array($entry)) {
return in_array(strtolower($username), array_map('strtolower', $entry));
@ -159,24 +160,22 @@ class LdapUserBackend extends UserBackend
}
/**
* Authenticate the given user and return true on success, false on failure and null on error
* Return whether the given user credentials are valid
*
* @param User $user
* @param string $password
* @param boolean $healthCheck Perform additional health checks to generate more useful exceptions in case
* of a configuration or backend error
* @param boolean $healthCheck Assert that authentication is possible at all
*
* @return bool True when the authentication was successful, false when the username
* or password was invalid
* @throws AuthenticationException When an error occurred during authentication and authentication is not possible
* @return bool
*
* @throws AuthenticationException In case an error occured or the health check has failed
*/
public function authenticate(User $user, $password, $healthCheck = true)
public function authenticate(User $user, $password, $healthCheck = false)
{
if ($healthCheck) {
try {
$this->assertAuthenticationPossible();
} catch (AuthenticationException $e) {
// Authentication not possible
throw new AuthenticationException(
'Authentication against backend "%s" not possible.',
$this->getName(),
@ -184,24 +183,27 @@ class LdapUserBackend extends UserBackend
);
}
}
if (! $this->hasUser($user)) {
return false;
}
try {
$userDn = $this->conn->fetchDN($this->selectUser($user->getUsername()));
$authenticated = $this->conn->testCredentials(
$userDn,
$password
);
if ($authenticated) {
$groups = $this->getGroups($userDn);
if ($groups !== null) {
$user->setGroups($groups);
}
}
return $authenticated;
} catch (LdapException $e) {
// Error during authentication of this specific user
throw new AuthenticationException(
'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',
$user->getUsername(),
@ -238,6 +240,7 @@ class LdapUserBackend extends UserBackend
$users[] = $row->{$this->userNameAttribute};
}
}
return $users;
}
}

View File

@ -84,7 +84,7 @@ class Manager
$preferences = new Preferences();
}
$user->setPreferences($preferences);
$groups = array();
$groups = $user->getGroups();
foreach (Config::app('groups') as $name => $config) {
try {
$groupBackend = UserGroupBackend::create($name, $config);

View File

@ -93,10 +93,10 @@ abstract class UserBackend implements Countable
break;
case 'msldap':
$groupOptions = array(
'group_base_dn' => $backendConfig->group_base_dn,
'group_attribute' => $backendConfig->group_attribute,
'group_member_attribute' => $backendConfig->group_member_attribute,
'group_class' => $backendConfig->group_class
'group_base_dn' => $backendConfig->get('group_base_dn', $resource->getDN()),
'group_attribute' => $backendConfig->get('group_attribute', 'sAMAccountName'),
'group_member_attribute' => $backendConfig->get('group_member_attribute', 'member'),
'group_class' => $backendConfig->get('group_class', 'group')
);
$backend = new LdapUserBackend(
$resource,

View File

@ -32,6 +32,8 @@ class Connection
{
const LDAP_NO_SUCH_OBJECT = 32;
const LDAP_SIZELIMIT_EXCEEDED = 4;
const LDAP_ADMINLIMIT_EXCEEDED = 11;
const PAGE_SIZE = 1000;
protected $ds;
protected $hostname;
@ -98,9 +100,6 @@ class Connection
protected $namingContexts;
protected $discoverySuccess = false;
protected $lastResult;
protected $pageCookie;
/**
* Constructor
*
@ -171,11 +170,9 @@ class Connection
return false;
}
throw new LdapException(
sprintf(
'LDAP list for "%s" failed: %s',
$dn,
ldap_error($this->ds)
)
);
}
$children = ldap_get_entries($this->ds, $result);
@ -183,7 +180,7 @@ class Connection
$result = $this->deleteRecursively($children[$i]['dn']);
if (!$result) {
//return result code, if delete fails
throw new LdapException(sprintf('Recursively deleting "%s" failed', $dn));
throw new LdapException('Recursively deleting "%s" failed', $dn);
}
}
return $this->deleteDN($dn);
@ -200,11 +197,9 @@ class Connection
return false;
}
throw new LdapException(
sprintf(
'LDAP delete for "%s" failed: %s',
$dn,
ldap_error($this->ds)
)
);
}
@ -225,10 +220,8 @@ class Connection
$rows = $this->fetchAll($query, $fields);
if (count($rows) !== 1) {
throw new LdapException(
sprintf(
'Cannot fetch single DN for %s',
$query->create()
)
);
}
return key($rows);
@ -244,6 +237,7 @@ class Connection
{
$query = clone $query;
$query->limit(1);
$query->setUsePagedResults(false);
$results = $this->fetchAll($query, $fields);
return array_shift($results);
}
@ -273,37 +267,146 @@ class Connection
$this->connect();
$this->bind();
$offset = $limit = null;
if ($query->hasLimit()) {
$offset = $query->getOffset();
if ($query->getUsePagedResults() && version_compare(PHP_VERSION, '5.4.0') >= 0) {
return $this->runPagedQuery($query, $fields);
} else {
return $this->runQuery($query, $fields);
}
}
protected function runQuery(Query $query, $fields = array())
{
$limit = $query->getLimit();
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
$results = @ldap_search(
$this->ds,
$query->hasBase() ? $query->getBase() : $this->root_dn,
$query->create(),
empty($fields) ? $query->listFields() : $fields,
0, // Attributes and values
$limit ? $offset + $limit : 0
);
if ($results === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
return array();
}
throw new LdapException(
'LDAP query "%s" (base %s) failed. Error: %s',
$query->create(),
$query->hasBase() ? $query->getBase() : $this->root_dn,
ldap_error($this->ds)
);
} elseif (ldap_count_entries($this->ds, $results) === 0) {
return array();
}
foreach ($query->getSortColumns() as $col) {
ldap_sort($this->ds, $results, $col[0]);
}
$count = 0;
$entries = array();
$results = $this->runQuery($query, $fields);
while (! empty($results)) {
$entry = ldap_first_entry($this->ds, $results);
while ($entry) {
$count++;
if (
($offset === null || $offset <= $count)
&& ($limit === null || $limit > count($entries))
) {
do {
$count += 1;
if ($offset === 0 || $offset < $count) {
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($this->ds, $entry)
);
}
} while (($limit === 0 || $limit !== count($entries)) && ($entry = ldap_next_entry($this->ds, $entry)));
$entry = ldap_next_entry($this->ds, $entry);
}
$results = $this->runQuery($query, $fields);
}
ldap_free_result($results);
return $entries;
}
protected function runPagedQuery(Query $query, $fields = array())
{
$limit = $query->getLimit();
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
$queryString = $query->create();
$base = $query->hasBase() ? $query->getBase() : $this->root_dn;
if (empty($fields)) {
$fields = $query->listFields();
}
$count = 0;
$cookie = '';
$entries = array();
do {
ldap_control_paged_result($this->ds, static::PAGE_SIZE, true, $cookie);
$results = @ldap_search($this->ds, $base, $queryString, $fields, 0, $limit ? $offset + $limit : 0);
if ($results === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
break;
}
throw new LdapException(
'LDAP query "%s" (base %s) failed. Error: %s',
$queryString,
$base,
ldap_error($this->ds)
);
} elseif (ldap_count_entries($this->ds, $results) === 0) {
if (in_array(
ldap_errno($this->ds),
array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED)
)) {
Logger::warning(
'Unable to request more than %u results. Does the server allow paged search requests? (%s)',
$count,
ldap_error($this->ds)
);
}
break;
}
$entry = ldap_first_entry($this->ds, $results);
do {
$count += 1;
if ($offset === 0 || $offset < $count) {
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($this->ds, $entry)
);
}
} while (($limit === 0 || $limit !== count($entries)) && ($entry = ldap_next_entry($this->ds, $entry)));
try {
ldap_control_paged_result_response($this->ds, $results, $cookie);
} catch (Exception $e) {
// If the page size is greater than or equal to the sizeLimit value, the server should ignore the
// control as the request can be satisfied in a single page: https://www.ietf.org/rfc/rfc2696.txt
// This applies no matter whether paged search requests are permitted or not. You're done once you
// got everything you were out for.
if (count($entries) !== $limit) {
Logger::warning(
'Unable to request paged LDAP results. Does the server allow paged search requests? (%s)',
$e->getMessage()
);
}
}
ldap_free_result($results);
} while ($cookie && ($limit === 0 || count($entries) < $limit));
if ($cookie) {
// A sequence of paged search requests is abandoned by the client sending a search request containing a
// pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by
// the server: https://www.ietf.org/rfc/rfc2696.txt
ldap_control_paged_result($this->ds, 0, false, $cookie);
ldap_search($this->ds, $base, $queryString, $fields); // Returns no entries, due to the page size
} else {
// Reset the paged search request so that subsequent requests succeed
ldap_control_paged_result($this->ds, 0);
}
return $entries; // TODO(7693): Sort entries post-processed
}
protected function cleanupAttributes($attrs)
{
$clean = (object) array();
@ -320,79 +423,6 @@ class Connection
return $clean;
}
protected function runQuery(Query $query, $fields = array())
{
if ($query->getUsePagedResults() && version_compare(PHP_VERSION, '5.4.0') >= 0) {
if ($this->pageCookie === null) {
$this->pageCookie = '';
} else {
try {
ldap_control_paged_result_response($this->ds, $this->lastResult, $this->pageCookie);
} catch (Exception $e) {
$this->pageCookie = '';
if (! $query->hasLimit() || ldap_errno($this->ds) !== static::LDAP_SIZELIMIT_EXCEEDED) {
Logger::error(
'Unable to request paged LDAP results. Does the server allow paged search requests? (%s)',
$e->getMessage()
);
}
}
ldap_free_result($this->lastResult);
if (! $this->pageCookie) {
$this->pageCookie = $this->lastResult = null;
// Abandon the paged search request so that subsequent requests succeed
ldap_control_paged_result($this->ds, 0);
return false;
}
}
// Does not matter whether we'll use a valid page size here,
// as the server applies its hard limit in case its too high
ldap_control_paged_result(
$this->ds,
$query->hasLimit() ? $query->getLimit() : 500,
true,
$this->pageCookie
);
} elseif ($this->lastResult !== null) {
ldap_free_result($this->lastResult);
$this->lastResult = null;
return false;
}
$base = $query->hasBase() ? $query->getBase() : $this->root_dn;
$results = @ldap_search(
$this->ds,
$base,
$query->create(),
empty($fields) ? $query->listFields() : $fields,
0, // Attributes and values
$query->hasLimit() ? $query->getOffset() + $query->getLimit() : 0 // No limit - at least where possible
);
if ($results === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
return false;
}
throw new LdapException(
sprintf(
'LDAP query "%s" (root %s) failed: %s',
$query->create(),
$this->root_dn,
ldap_error($this->ds)
)
);
}
foreach ($query->getSortColumns() as $col) {
ldap_sort($this->ds, $results, $col[0]);
}
$this->lastResult = $results;
return $results;
}
public function testCredentials($username, $password)
{
$this->connect();
@ -471,18 +501,14 @@ class Connection
} else {
Logger::debug('LDAP STARTTLS failed: %s', ldap_error($ds));
throw new LdapException(
sprintf(
'LDAP STARTTLS failed: %s',
ldap_error($ds)
)
);
}
} elseif ($force_tls) {
throw new LdapException(
sprintf(
'TLS is required but not announced by %s',
$this->hostname
)
);
} else {
// TODO: Log noticy -> TLS enabled but not announced
@ -708,24 +734,20 @@ class Connection
if (! $result) {
throw new LdapException(
sprintf(
'Capability query failed (%s:%d): %s. Check if hostname and port of the ldap resource are correct '
. ' and if anonymous access is permitted.',
'Capability query failed (%s:%d): %s. Check if hostname and port of the'
. ' ldap resource are correct and if anonymous access is permitted.',
$this->hostname,
$this->port,
ldap_error($ds)
)
);
}
$entry = ldap_first_entry($ds, $result);
if ($entry === false) {
throw new LdapException(
sprintf(
'Capabilities not available (%s:%d): %s. Discovery of root DSE probably not permitted.',
$this->hostname,
$this->port,
ldap_error($ds)
)
);
}
@ -771,14 +793,12 @@ class Connection
$r = @ldap_bind($this->ds, $this->bind_dn, $this->bind_pw);
if (! $r) {
throw new LdapException(
sprintf(
'LDAP connection to %s:%s (%s / %s) failed: %s',
$this->hostname,
$this->port,
$this->bind_dn,
'***' /* $this->bind_pw */,
ldap_error($this->ds)
)
);
}
$this->bound = true;

View File

@ -3,10 +3,12 @@
namespace Icinga\Protocol\Ldap;
use Icinga\Exception\IcingaException;
/**
* Class Exception
* @package Icinga\Protocol\Ldap
*/
class Exception extends \Exception
class Exception extends IcingaException
{
}

View File

@ -27,8 +27,8 @@ class Query
protected $connection;
protected $filters = array();
protected $fields = array();
protected $limit_count;
protected $limit_offset;
protected $limit_count = 0;
protected $limit_offset = 0;
protected $sort_columns = array();
protected $count;
protected $base;
@ -111,7 +111,7 @@ class Query
*/
public function hasLimit()
{
return $this->limit_count !== null;
return $this->limit_count > 0;
}
/**

View File

@ -5,34 +5,44 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
{
protected static $purifier;
protected static $txtPatterns = array(
'~\\\n~',
'~\\\t~',
'~\\\n\\\n~',
'~(\[|\()OK(\]|\))~',
'~(\[|\()WARNING(\]|\))~',
'~(\[|\()CRITICAL(\]|\))~',
'~(\[|\()UNKNOWN(\]|\))~',
'~\@{6,}~'
);
protected static $txtReplacements = array(
"\n",
"\t",
"\n",
'<span class="state ok">$1OK$2</span>',
'<span class="state warning">$1WARNING$2</span>',
'<span class="state critical">$1CRITICAL$2</span>',
'<span class="state error">$1UNKNOWN$2</span>',
'@@@@@@',
);
public function pluginOutput($output)
{
if (empty($output)) {
return '';
}
$output = preg_replace('~<br[^>]+>~', "\n", $output);
if (preg_match('~<\w+[^>]*>~', $output)) {
if (preg_match('~<\w+[^>^\\\]{,60}>~', $output)) {
// HTML
$output = preg_replace('~<table~', '<table style="font-size: 0.75em"',
$this->getPurifier()->purify($output)
);
} elseif (preg_match('~\\\n~', $output)) {
// Plaintext
$output = '<pre class="pluginoutput">'
. preg_replace(
'~\\\n~', "\n", preg_replace(
'~\\\n\\\n~', "\n",
preg_replace('~\[OK\]~', '<span class="ok">[OK]</span>',
preg_replace('~\[WARNING\]~', '<span class="warning">[WARNING]</span>',
preg_replace('~\[CRITICAL\]~', '<span class="error">[CRITICAL]</span>',
preg_replace('~\@{6,}~', '@@@@@@',
$this->view->escape($output)
))))
)
) . '</pre>';
} else {
$output = '<pre class="pluginoutput">'
. preg_replace('~\@{6,}~', '@@@@@@',
// Plaintext
$output = '<pre class="pluginoutput">' . preg_replace(
self::$txtPatterns,
self::$txtReplacements,
$this->view->escape($output)
) . '</pre>';
}
@ -55,7 +65,7 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
parse_str($m[1], $params);
if (isset($params['host'])) {
$tag->setAttribute('href', $this->view->baseUrl(
'/monitoring/detail/show?host=' . urlencode($params['host']
'/monitoring/host/show?host=' . urlencode($params['host']
)));
}
} else {

View File

@ -737,7 +737,6 @@
}
this.icinga.ui.assignUniqueContainerIds();
console.log(origFocus);
if (origFocus.length == origFocus[0] !== '') {
setTimeout(function() {
$(self.icinga.utils.getElementByDomPath(origFocus)).focus();

View File

@ -284,10 +284,8 @@
if (! $element) {
$element = $(selector);
} else {
console.log(selector);
$element = $element.children(selector).first();
if (! $element[0]) {
console.log("element not existing stopping...");
return false;
}
}