Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 63 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
LdapAuthenticationHooks | |
0.00% |
0 / 63 |
|
0.00% |
0 / 5 |
650 | |
0.00% |
0 / 1 |
getLDAP | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
setLdapLockStatus | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
72 | |||
onBlockIpComplete | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
42 | |||
onUnblockUserComplete | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
onRegistration | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | */ |
18 | |
19 | use MediaWiki\Block\DatabaseBlock; |
20 | use MediaWiki\Hook\BlockIpCompleteHook; |
21 | use MediaWiki\Hook\UnblockUserCompleteHook; |
22 | use MediaWiki\User\User; |
23 | use MediaWiki\User\UserIdentity; |
24 | |
25 | class LdapAuthenticationHooks implements |
26 | BlockIpCompleteHook, |
27 | UnblockUserCompleteHook |
28 | { |
29 | |
30 | /** |
31 | * Get an LdapAuthenticationPlugin instance that is connected to the LDAP |
32 | * directory and bound as the wgLDAPWriterDN user. |
33 | * |
34 | * @return LdapAuthenticationPlugin|false LDAP connection or false if initialization fails |
35 | */ |
36 | private static function getLDAP() { |
37 | $ldap = LdapAuthenticationPlugin::getInstance(); |
38 | if ( $ldap->ldapconn === null ) { |
39 | if ( !$ldap->connect() ) { |
40 | $ldap->printDebug( 'Failed to connect to LDAP directory', NONSENSITIVE ); |
41 | return false; |
42 | } |
43 | } |
44 | $writer = $ldap->getConf( 'WriterDN' ); |
45 | if ( !$writer ) { |
46 | $ldap->printDebug( "Wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE ); |
47 | return false; |
48 | |
49 | } |
50 | $bind = $ldap->bindAs( $writer, $ldap->getConf( 'WriterPassword' ) ); |
51 | if ( !$bind ) { |
52 | $ldap->printDebug( 'Failed to bind to directory as wgLDAPWriterDN', NONSENSITIVE ); |
53 | return false; |
54 | } |
55 | return $ldap; |
56 | } |
57 | |
58 | /** |
59 | * Lock/unlock an LDAP account via a 'pwdAccountLockedTime' attribute. |
60 | * Optionally set a password policy. This should help cover some cases |
61 | * pwdAccountLockedTime doesn't cover well like out-of-band (e.g. by an |
62 | * admin) password resets. |
63 | * |
64 | * @param UserIdentity $user UserIdentity to lock/unlock |
65 | * @param bool $lock True to lock, False to unlock |
66 | * @return bool True if successful, false otherwise |
67 | */ |
68 | private static function setLdapLockStatus( UserIdentity $user, $lock ) { |
69 | $ldap = static::getLDAP(); |
70 | if ( !$ldap ) { |
71 | wfDebugLog( 'ldap', 'Failed to initialize LDAP connection' ); |
72 | return false; |
73 | } |
74 | |
75 | $ppolicy = $ldap->getConf( 'LDAPLockPasswordPolicy' ); |
76 | |
77 | $actionStr = $lock ? 'lock' : 'unlock'; |
78 | // * '000001010000Z' means that the account has been locked |
79 | // permanently, and that only a password administrator can unlock the |
80 | // account. |
81 | // * If a password policy has been configured, apply that as well |
82 | // * empty array means delete the attribute |
83 | $lockData = []; |
84 | $lockData['pwdAccountLockedTime'] = $lock ? '000001010000Z' : []; |
85 | if ( $ppolicy ) { |
86 | $lockData['pwdPolicySubentry'] = $lock ? $ppolicy : []; |
87 | } |
88 | |
89 | $userDN = $ldap->getUserDN( $user->getName() ); |
90 | if ( !$userDN ) { |
91 | $ldap->printDebug( |
92 | "Failed to lookup DN for user {$user->getName()}", |
93 | NONSENSITIVE |
94 | ); |
95 | return false; |
96 | } |
97 | $ldap->printDebug( |
98 | "Attempting to {$actionStr} {$userDN}", NONSENSITIVE ); |
99 | $success = LdapAuthenticationPlugin::ldap_modify( |
100 | $ldap->ldapconn, |
101 | $userDN, |
102 | $lockData ); |
103 | if ( !$success ) { |
104 | $msg = "Failed to {$actionStr} LDAP account {$userDN}"; |
105 | $errno = LdapAuthenticationPlugin::ldap_errno( $ldap->ldapconn ); |
106 | $ldap->printDebug( $msg . ": LDAP errno {$errno}", NONSENSITIVE ); |
107 | return false; |
108 | } |
109 | return true; |
110 | } |
111 | |
112 | /** |
113 | * Inspect new blocks and lock the backing LDAP account when an indefinite |
114 | * block is made against a specific user. Alternately, unlock the account |
115 | * if a new block is placed replacing a prior indefinite block. |
116 | * |
117 | * @param DatabaseBlock $block The block object that was saved |
118 | * @param User $user The user who performed the unblock |
119 | * @param DatabaseBlock|null $prior Previous block that was replaced |
120 | * @return bool True if successful, false otherwise |
121 | */ |
122 | public function onBlockIpComplete( $block, $user, $prior ) { |
123 | global $wgLDAPLockOnBlock; |
124 | if ( $wgLDAPLockOnBlock ) { |
125 | if ( $block->getType() === DatabaseBlock::TYPE_USER |
126 | && $block->getExpiry() === 'infinity' |
127 | && $block->isSitewide() |
128 | ) { |
129 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullable $user can't be null |
130 | return static::setLdapLockStatus( $block->getTargetUserIdentity(), true ); |
131 | } elseif ( $prior ) { |
132 | // New block replaced a prior block. Process the prior block |
133 | // as though it was explicitly removed. |
134 | return $this->onUnblockUserComplete( $prior, $user ); |
135 | } |
136 | } |
137 | } |
138 | |
139 | /** |
140 | * Inspect removed blocks and unlock the backing LDAP account when an |
141 | * indefinite block is lifted against a specific user. |
142 | * |
143 | * @param DatabaseBlock $block the block object that was saved |
144 | * @param User $user The user who performed the unblock |
145 | * @return bool True if successful, false otherwise |
146 | */ |
147 | public function onUnblockUserComplete( $block, $user ) { |
148 | global $wgLDAPLockOnBlock; |
149 | if ( $wgLDAPLockOnBlock |
150 | && $block->getType() === DatabaseBlock::TYPE_USER |
151 | && $block->getExpiry() === 'infinity' |
152 | && $block->isSitewide() |
153 | ) { |
154 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullable $user can't be null |
155 | return static::setLdapLockStatus( $block->getTargetUserIdentity(), false ); |
156 | } |
157 | } |
158 | |
159 | public static function onRegistration() { |
160 | // constants for search base |
161 | define( "GROUPDN", 0 ); |
162 | define( "USERDN", 1 ); |
163 | define( "DEFAULTDN", 2 ); |
164 | |
165 | // constants for error reporting |
166 | define( "NONSENSITIVE", 1 ); |
167 | define( "SENSITIVE", 2 ); |
168 | define( "HIGHLYSENSITIVE", 3 ); |
169 | } |
170 | |
171 | } |