Merge pull request #39 from LDAPAccountManager/expireStatus

Expire status
This commit is contained in:
gruberroland 2017-10-19 19:46:10 +02:00 committed by GitHub
commit 54bf006d5c
9 changed files with 606 additions and 304 deletions

View File

@ -1,5 +1,6 @@
December 2017
- PHP 5.6 and Internet Explorer 11 or later required
- Account status also shows expired accounts
19.09.2017 6.1

View File

@ -271,9 +271,9 @@
<para><emphasis>Show account status:</emphasis> If you activate this
option then there will be an additional column displayed that shows if the
account is locked. You can see more details when moving the mouse cursor
over the lock icon. This function supports Unix, Samba, PPolicy, Windows
and 389ds locking+deactivation.</para>
account is locked or expired. You can see more details when moving the
mouse cursor over the lock icon. This function supports Unix, Samba,
PPolicy, Windows and 389ds locking+deactivation.</para>
<screenshot>
<mediaobject>
@ -702,7 +702,7 @@
</mediaobject>
</screenshot>
<para></para>
<para/>
<screenshot>
<mediaobject>
@ -888,7 +888,7 @@
</mediaobject>
</screenshot>
<para></para>
<para/>
<screenshot>
<mediaobject>
@ -1335,7 +1335,7 @@
</mediaobject>
</screenshot>
<para></para>
<para/>
<screenshot>
<mediaobject>
@ -1974,13 +1974,13 @@
service (e.g. sshd) by reading the LDAP attribute "authorizedService".
This way you can manage all allowed services via LAM.</para>
<para></para>
<para/>
<para>To activate this PAM feature please setup your <emphasis
role="bold">/etc/libnss-ldap.conf</emphasis> and set
"pam_check_service_attr" to "yes".</para>
<para></para>
<para/>
<para>Inside LAM you can now set the allowed services. You may also
setup default services in your account profiles.</para>
@ -2145,7 +2145,7 @@
<section>
<title>Groups</title>
<para></para>
<para/>
<section>
<title>Unix</title>
@ -2849,7 +2849,7 @@
</mediaobject>
</screenshot>
<para></para>
<para/>
<screenshot>
<mediaobject>
@ -3166,7 +3166,7 @@
</mediaobject>
</screenshot>
<para></para>
<para/>
<screenshot>
<mediaobject>
@ -3176,7 +3176,7 @@
</mediaobject>
</screenshot>
<para></para>
<para/>
<screenshot>
<mediaobject>
@ -3312,7 +3312,7 @@
</mediaobject>
</screenshot>
<para></para>
<para/>
<screenshot>
<mediaobject>
@ -3372,7 +3372,7 @@
</mediaobject>
</screenshot>
<para></para>
<para/>
<screenshot>
<mediaobject>
@ -3772,8 +3772,7 @@ Attention: If the Active Directory schema is used then LAM will always use dn an
</mediaobject>
</screenshot>
<para><emphasis role="bold">Example server
entry:</emphasis><code></code></para>
<para><emphasis role="bold">Example server entry:</emphasis><code/></para>
<para><code>dn:
cn=server,ou=dhcp,dc=ldap-account-manager,dc=org</code></para>
@ -3842,7 +3841,7 @@ Attention: If the Active Directory schema is used then LAM will always use dn an
<para><code>ldap-debug-file
"/var/log/dhcp-ldap-startup.log";</code></para>
<para><code></code></para>
<para><code/></para>
<literallayout>
</literallayout>
@ -4190,7 +4189,7 @@ Run slapindex to rebuild the index.
</row>
<row>
<entry></entry>
<entry/>
<entry>IN</entry>
@ -4200,7 +4199,7 @@ Run slapindex to rebuild the index.
</row>
<row>
<entry></entry>
<entry/>
<entry>IN</entry>
@ -4210,7 +4209,7 @@ Run slapindex to rebuild the index.
</row>
<row>
<entry></entry>
<entry/>
<entry>IN</entry>
@ -4220,7 +4219,7 @@ Run slapindex to rebuild the index.
</row>
<row>
<entry></entry>
<entry/>
<entry>IN</entry>
@ -4260,7 +4259,7 @@ Run slapindex to rebuild the index.
</row>
<row>
<entry></entry>
<entry/>
<entry>IN</entry>
@ -5187,7 +5186,7 @@ OK (10 msec)</programlisting>
value):</para>
<table border="1">
<caption></caption>
<caption/>
<tr>
<th>Constant value</th>
@ -5220,7 +5219,7 @@ OK (10 msec)</programlisting>
</tr>
</table>
<para></para>
<para/>
<para>Presentation:</para>
@ -5497,7 +5496,7 @@ OK (10 msec)</programlisting>
activating this option will only show the command output but not the
command itself.</para>
<para></para>
<para/>
<para>You can see a preview of the commands which will be automatically
executed on the "Custom scripts" tab. Here you can also run the manual

BIN
lam/graphics/expired.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1090,7 +1090,6 @@ class LAMConfig {
*/
public function set_Passwd($value) {
if (is_string($value)) {
mt_srand((microtime() * 1000000));
$rand = getRandomNumber();
$salt0 = substr(pack("h*", md5($rand)), 0, 8);
$salt = substr(pack("H*", sha1($salt0 . $value)), 0, 4);
@ -2410,7 +2409,6 @@ class LAMCfgMain {
* @param String $password new password
*/
public function setPassword($password) {
mt_srand((microtime() * 1000000));
$rand = getRandomNumber();
$salt0 = substr(pack("h*", md5($rand)), 0, 8);
$salt = substr(pack("H*", sha1($salt0 . $password)), 0, 4);

View File

@ -4,7 +4,7 @@ $Id$
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2003 - 2006 Tilo Lutz
Copyright (C) 2007 - 2016 Roland Gruber
Copyright (C) 2007 - 2017 Roland Gruber
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
@ -771,6 +771,45 @@ class shadowAccount extends baseModule implements passwordService {
);
}
/**
* Returns if the given account is expired.
*
* @param array $attrs LDAP attributes
* @return bool expired
*/
public static function isAccountExpired($attrs) {
$attrs = array_change_key_case($attrs, CASE_LOWER);
if (empty($attrs['shadowexpire'][0])) {
return false;
}
$time = new DateTime('@' . $attrs['shadowexpire'][0] * 24 * 3600, new DateTimeZone('UTC'));
$now = new DateTime(null, getTimeZone());
return ($time < $now);
}
/**
* Returns if the given password is expired.
*
* @param array $attrs LDAP attributes
* @return bool expired
*/
public static function isPasswordExpired($attrs) {
$attrs = array_change_key_case($attrs, CASE_LOWER);
if (empty($attrs['shadowlastchange'][0]) || empty($attrs['shadowmax'][0])) {
return false;
}
if (($attrs['shadowlastchange'][0] < 1) || ($attrs['shadowmax'][0] < 1)) {
return;
}
$time = new DateTime('@' . $attrs['shadowlastchange'][0] * 24 * 3600, new DateTimeZone('UTC'));
$time = $time->add(new DateInterval('P' . $attrs['shadowmax'][0] . 'D'));
if (!empty($attrs['shadowinactive'][0]) && ($attrs['shadowinactive'][0] > 0)) {
$time = $time->add(new DateInterval('P' . $attrs['shadowinactive'][0] . 'D'));
}
$now = new DateTime(null, getTimeZone());
return ($time < $now);
}
}
if (interface_exists('\LAM\JOB\Job', false)) {

View File

@ -3373,6 +3373,28 @@ class windowsUser extends baseModule implements passwordService {
return $replacements;
}
/**
* Returns if the given account is expired.
*
* @param array $attrs LDAP attributes
* @return bool expired
*/
public static function isAccountExpired($attrs) {
$attrs = array_change_key_case($attrs, CASE_LOWER);
if (empty($attrs['accountexpires'][0])) {
return false;
}
$value = $attrs['accountexpires'][0];
if ($value < 1) {
return false;
}
$seconds = substr($value, 0, -7);
$time = new DateTime('1601-01-01', new DateTimeZone('UTC'));
$time->add(new DateInterval('PT' . $seconds . 'S'));
$now = new DateTime(null, getTimeZone());
return ($time < $now);
}
}
if (interface_exists('\LAM\JOB\Job', false)) {

View File

@ -346,7 +346,35 @@ class user extends baseType {
if ($isEditable) {
$onClick = 'onclick="showConfirmationDialog(\'' . _('Change account status') . '\', \'' . _('Ok') . '\', \'' . _('Cancel') . '\', \'lam_accountStatusDialog\', \'inputForm\', \'lam_accountStatusResult\');"';
}
return $dialogDiv . '<a href="#"><img id="lam_accountStatus" alt="status" ' . $onClick . ' helptitle="' . _('Account status') . '" helpdata="' . $tipContent . '" height=16 width=16 src="../../graphics/' . $icon . '"></a>&nbsp;&nbsp;&nbsp;';
$dialogDiv .= '<a href="#"><img id="lam_accountStatus" alt="status" ' . $onClick . ' helptitle="' . _('Account status') . '" helpdata="' . $tipContent . '" height=16 width=16 src="../../graphics/' . $icon . '"></a>&nbsp;&nbsp;&nbsp;';
// expiration status
$expiredLabels = array();
$shadowModule = $container->getAccountModule('shadowAccount');
if ($shadowModule != null) {
$shadowAttrs = $shadowModule->getAttributes();
if (shadowAccount::isAccountExpired($shadowAttrs)) {
$expiredLabels[] = _('Shadow') . ': ' . _('Account expiration');
}
elseif (shadowAccount::isPasswordExpired($shadowAttrs)) {
$expiredLabels[] = _('Shadow') . ': ' . _('Password expiration');
}
}
$windowsModule = $container->getAccountModule('windowsUser');
if ($windowsModule != null) {
$windowsAttrs = $windowsModule->getAttributes();
if (windowsUser::isAccountExpired($windowsAttrs)) {
$expiredLabels[] = _('Windows') . ': ' . _('Account expiration');
}
}
if (!empty($expiredLabels)) {
$expiredTip = '<table border=0>';
foreach ($expiredLabels as $label) {
$expiredTip .= '<tr><td>' . $label . '</td><td><img src=&quot;../../graphics/expired.png&quot;/></td></tr>';
}
$expiredTip .= '</table>';
$dialogDiv .= '<img alt="expired" helptitle="' . _('Expired') . '" helpdata="' . $expiredTip . '" height=16 width=16 src="../../graphics/expired.png">&nbsp;&nbsp;&nbsp;';
}
return $dialogDiv;
}
/**
@ -606,6 +634,8 @@ class lamUserList extends lamList {
/** virtual attribute name for account status column */
const ATTR_ACCOUNT_STATUS = 'lam_virtual_account_status';
/** filter value for expired accounts */
const FILTER_EXPIRED = 1;
/** filter value for locked accounts */
const FILTER_LOCKED = 2;
/** filter value for partially locked accounts */
@ -854,7 +884,8 @@ class lamUserList extends lamList {
'' => '',
_('Unlocked') => self::FILTER_UNLOCKED,
_('Partially locked') => self::FILTER_SEMILOCKED,
_('Locked') => self::FILTER_LOCKED
_('Locked') => self::FILTER_LOCKED,
_('Expired') => self::FILTER_EXPIRED,
);
$filterInput = new htmlSelect('filter' . strtolower($attrName), $filterOptions, array($value));
$filterInput->setCSSClasses(array($this->type->getScope() . '-dark'));
@ -899,6 +930,11 @@ class lamUserList extends lamList {
$attrs[] = 'lockoutTime';
$attrs[] = 'nsAccountLock';
$attrs[] = 'accountUnlockTime';
$attrs[] = 'shadowExpire';
$attrs[] = 'shadowLastChange';
$attrs[] = 'shadowMax';
$attrs[] = 'shadowInactive';
$attrs[] = 'accountExpires';
$attrs[] = 'objectClass';
}
return $attrs;
@ -931,8 +967,15 @@ class lamUserList extends lamList {
|| ($sambaAvailable && !$sambaLocked)
|| ($ppolicyAvailable && !$ppolicyLocked)
|| ($windowsAvailable && !$windowsLocked);
$shadowExpired = shadowAccount::isAccountExpired($this->entries[$i]);
$shadowPasswordExpired = shadowAccount::isPasswordExpired($this->entries[$i]);
$windowsExpired = windowsUser::isAccountExpired($this->entries[$i]);
$expired = $shadowExpired || $shadowPasswordExpired || $windowsExpired;
$status = self::FILTER_UNLOCKED;
if ($hasLocked && $hasUnlocked) {
if ($expired) {
$status = self::FILTER_EXPIRED;
}
elseif ($hasLocked && $hasUnlocked) {
$status = self::FILTER_SEMILOCKED;
}
elseif (!$hasUnlocked && $hasLocked) {
@ -978,16 +1021,30 @@ class lamUserList extends lamList {
&& (!$sambaAvailable || $sambaLocked)
&& (!$ppolicyAvailable || $ppolicyLocked)
&& (!$windowsAvailable || $windowsLocked);
$shadowExpired = shadowAccount::isAccountExpired($attrs);
$shadowPasswordExpired = shadowAccount::isPasswordExpired($attrs);
$windowsExpired = windowsUser::isAccountExpired($attrs);
$expired = $shadowExpired || $shadowPasswordExpired || $windowsExpired;
$icon = 'unlocked.png';
if ($fullyLocked) {
if ($expired) {
$icon = 'expired.png';
}
elseif ($fullyLocked) {
$icon = 'lock.png';
}
elseif ($partiallyLocked) {
$icon = 'partiallyLocked.png';
}
// print icon and detail tooltips
if ($unixAvailable || $sambaAvailable || $ppolicyAvailable || $windowsAvailable || $is389dsDeactivated) {
if ($unixAvailable || $sambaAvailable || $ppolicyAvailable || $windowsAvailable || $is389dsDeactivated || $expired) {
$tipContent = '<table border=0>';
// Shadow expired
if ($shadowExpired) {
$tipContent .= '<tr><td>' . _('Shadow') . ': ' . _('Account expiration') . '&nbsp;&nbsp;</td><td><img height=16 width=16 src=&quot;../../graphics/expired.png&quot;></td></tr>';
}
elseif ($shadowPasswordExpired) {
$tipContent .= '<tr><td>' . _('Shadow') . ': ' . _('Password expiration') . '&nbsp;&nbsp;</td><td><img height=16 width=16 src=&quot;../../graphics/expired.png&quot;></td></tr>';
}
// Unix
if ($unixAvailable) {
$unixIcon = 'unlocked.png';
@ -1019,6 +1076,9 @@ class lamUserList extends lamList {
$windowsIcon = 'lock.png';
}
$tipContent .= '<tr><td>' . _('Windows') . '&nbsp;&nbsp;</td><td><img height=16 width=16 src=&quot;../../graphics/' . $windowsIcon . '&quot;></td></tr>';
if ($windowsExpired) {
$tipContent .= '<tr><td>' . _('Windows') . ': ' . _('Account expiration') . '&nbsp;&nbsp;</td><td><img height=16 width=16 src=&quot;../../graphics/expired.png&quot;></td></tr>';
}
}
if ($windowsAvailable && $windowsPasswordLocked) {
$tipContent .= '<tr><td>' . _('Locked till') . '&nbsp;&nbsp;</td><td>' . $windowsPasswordLockedTime->format('Y-m-d H:i:s') . '</td></tr>';
@ -1049,6 +1109,16 @@ class lamUserList extends lamList {
return (isset($attrs['objectclass']) && in_array_ignore_case('posixAccount', $attrs['objectclass']) && isset($attrs['userpassword'][0]));
}
/**
* Returns if the Shadow part exists.
*
* @param array $attrs LDAP attributes
* @return boolean Shadow part exists
*/
public static function isShadowAvailable(&$attrs) {
return (isset($attrs['objectclass']) && in_array_ignore_case('shadowAccount', $attrs['objectclass']));
}
/**
* Returns if the Unix part is locked.
*

View File

@ -3,7 +3,7 @@
$Id$
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2016 Roland Gruber
Copyright (C) 2016 - 2017 Roland Gruber
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
@ -21,13 +21,102 @@
*/
if (is_readable('lam/lib/passwordExpirationJob.inc')) {
include_once 'lam/lib/baseModule.inc';
include_once 'lam/lib/modules.inc';
if (is_readable('lam/lib/passwordExpirationJob.inc')) {
include_once 'lam/lib/passwordExpirationJob.inc';
}
include_once 'lam/lib/modules/shadowAccount.inc';
/**
* Checks the shadowAccount class.
*
* @author Roland Gruber
*/
class ShadowAccountTest extends PHPUnit_Framework_TestCase {
public function test_isAccountExpired_noAttr() {
$attrs = array('objectClass' => array('shadowAccount'));
$this->assertFalse(shadowAccount::isAccountExpired($attrs));
}
public function test_isAccountExpired_notExpired() {
$expire = intval(time() / (24*3600)) + 10000;
$attrs = array(
'objectClass' => array('shadowAccount'),
'sHadoweXpirE' => array(0 => $expire)
);
$this->assertFalse(shadowAccount::isAccountExpired($attrs));
}
public function test_isAccountExpired_expired() {
$expire = intval(time() / (24*3600)) - 10000;
$attrs = array(
'objectClass' => array('shadowAccount'),
'sHadoweXpirE' => array(0 => $expire)
);
$this->assertTrue(shadowAccount::isAccountExpired($attrs));
}
public function test_isPasswordExpired_noAttr() {
$attrs = array('objectClass' => array('shadowAccount'));
$this->assertFalse(shadowAccount::isPasswordExpired($attrs));
}
public function test_isPasswordExpired_notExpired() {
$change = intval(time() / (24*3600)) - 10;
$attrs = array(
'objectClass' => array('shadowAccount'),
'shadoWlastCHange' => array(0 => $change),
'shadowmax' => array(0 => '14'),
);
$this->assertFalse(shadowAccount::isPasswordExpired($attrs));
}
public function test_isPasswordExpired_expired() {
$change = intval(time() / (24*3600)) - 10;
$attrs = array(
'objectClass' => array('shadowAccount'),
'shadoWlastCHange' => array(0 => $change),
'shadowmax' => array(0 => '7'),
);
$this->assertTrue(shadowAccount::isPasswordExpired($attrs));
}
public function test_isPasswordExpired_notExpiredInactiveSet() {
$change = intval(time() / (24*3600)) - 10;
$attrs = array(
'objectClass' => array('shadowAccount'),
'shadoWlastCHange' => array(0 => $change),
'shadowmax' => array(0 => '7'),
'shaDowinactIVe' => array(0 => '14'),
);
$this->assertFalse(shadowAccount::isPasswordExpired($attrs));
}
public function test_isPasswordExpired_expiredInactiveSet() {
$change = intval(time() / (24*3600)) - 10;
$attrs = array(
'objectClass' => array('shadowAccount'),
'shadoWlastCHange' => array(0 => $change),
'shadowmax' => array(0 => '7'),
'shaDowinactIVe' => array(0 => '2'),
);
$this->assertTrue(shadowAccount::isPasswordExpired($attrs));
}
}
if (is_readable('lam/lib/passwordExpirationJob.inc')) {
/**
* Checks the shadow expire job.
*

View File

@ -0,0 +1,84 @@
<?php
/*
$Id$
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2017 Roland Gruber
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
include_once 'lam/lib/baseModule.inc';
include_once 'lam/lib/modules.inc';
include_once 'lam/lib/modules/windowsUser.inc';
/**
* Checks the windowsUser class.
*
* @author Roland Gruber
*/
class WindowsUserTest extends PHPUnit_Framework_TestCase {
public function test_isAccountExpired_noAttr() {
$attrs = array('objectClass' => array('user'));
$this->assertFalse(windowsUser::isAccountExpired($attrs));
}
public function test_isAccountExpired_notExpired() {
$expire = $this->getTimeStamp(14);
$attrs = array(
'objectClass' => array('user'),
'accounTExpIRes' => array(0 => $expire)
);
$this->assertFalse(windowsUser::isAccountExpired($attrs));
}
public function test_isAccountExpired_expired() {
$expire = $this->getTimeStamp(-14);
$attrs = array(
'objectClass' => array('user'),
'accounTExpIRes' => array(0 => $expire)
);
$this->assertTrue(windowsUser::isAccountExpired($attrs));
}
/**
* Returns the timestamp from now with given time difference.
*
* @param int $diff time difference in days
*/
private function getTimeStamp($diff) {
$timeBase = new DateTime('1601-01-01', getTimeZone());
$time = new DateTime(null, getTimeZone());
if ($diff > 0) {
$time->add(new DateInterval('P' . $diff . 'D'));
}
else {
$time->sub(new DateInterval('P' . abs($diff) . 'D'));
}
$timeDiff = $time->diff($timeBase);
$days = $timeDiff->format('%a');
$seconds = $days * 24 * 3600 - ($time->getOffset());
echo $seconds . ' ';
return $seconds . '0000000';
}
}
?>