Subversion Repositories oidplus

Rev

Rev 1116 | Rev 1131 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
635 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
1086 daniel-mar 5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
635 daniel-mar 6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
 
1050 daniel-mar 20
namespace ViaThinkSoft\OIDplus;
635 daniel-mar 21
 
1086 daniel-mar 22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
25
 
635 daniel-mar 26
class OIDplusPagePublicLoginLdap extends OIDplusPagePluginPublic {
27
 
1116 daniel-mar 28
        /**
1130 daniel-mar 29
         * @param OIDplusRA $ra
30
         * @param array $ldap_userinfo
1116 daniel-mar 31
         * @return void
32
         * @throws OIDplusException
33
         */
1130 daniel-mar 34
        private function registerRA(OIDplusRA $ra, array $ldap_userinfo) {
635 daniel-mar 35
                $email = $ra->raEmail();
36
 
37
                $ra->register_ra(null); // create a user account without password
38
 
39
                /*
40
                OIDplus DB Field          ActiveDirectory field
41
                ------------------------------------------------
42
                ra_name                   cn
43
                personal_name             displayname (or: givenname + " " + sn)
44
                organization              company
45
                office                    physicaldeliveryofficename or department
46
                street                    streetaddress
47
                zip_town                  postalcode + " " + l
48
                country                   co (human-readable) or c (ISO country code)
49
                phone                     telephonenumber or homephone
50
                mobile                    mobile
51
                fax                       facsimiletelephonenumber
52
                (none)                    wwwhomepage
53
                */
54
 
55
                $opuserdata = array();
1050 daniel-mar 56
                $opuserdata['ra_name'] = \VtsLDAPUtils::getString($ldap_userinfo,'cn');
57
                if (!empty(\VtsLDAPUtils::getString($ldap_userinfo,'displayname'))) {
58
                        $opuserdata['personal_name'] = \VtsLDAPUtils::getString($ldap_userinfo,'displayname');
635 daniel-mar 59
                } else {
1050 daniel-mar 60
                        $opuserdata['personal_name'] = trim(\VtsLDAPUtils::getString($ldap_userinfo,'givenname').' '.\VtsLDAPUtils::getString($ldap_userinfo,'sn'));
635 daniel-mar 61
                }
1050 daniel-mar 62
                $opuserdata['organization'] = \VtsLDAPUtils::getString($ldap_userinfo,'company');
63
                if (!empty(\VtsLDAPUtils::getString($ldap_userinfo,'physicaldeliveryofficename'))) {
64
                        $opuserdata['office'] = \VtsLDAPUtils::getString($ldap_userinfo,'physicaldeliveryofficename');
635 daniel-mar 65
                } else {
1050 daniel-mar 66
                        $opuserdata['office'] = \VtsLDAPUtils::getString($ldap_userinfo,'department');
635 daniel-mar 67
                }
1050 daniel-mar 68
                $opuserdata['street'] = \VtsLDAPUtils::getString($ldap_userinfo,'streetaddress');
69
                $opuserdata['zip_town'] = trim(\VtsLDAPUtils::getString($ldap_userinfo,'postalcode').' '.\VtsLDAPUtils::getString($ldap_userinfo,'l'));
70
                $opuserdata['country'] = \VtsLDAPUtils::getString($ldap_userinfo,'co'); // ISO country code: \VtsLDAPUtils::getString($ldap_userinfo,'c')
71
                $opuserdata['phone'] = \VtsLDAPUtils::getString($ldap_userinfo,'telephonenumber'); // homephone for private phone number
72
                $opuserdata['mobile'] = \VtsLDAPUtils::getString($ldap_userinfo,'mobile');
73
                $opuserdata['fax'] = \VtsLDAPUtils::getString($ldap_userinfo,'facsimiletelephonenumber');
635 daniel-mar 74
 
75
                foreach ($opuserdata as $dbfield => $val) {
76
                        if (!empty($val)) {
77
                                OIDplus::db()->query("update ###ra set ".$dbfield." = ? where email = ?", array($val, $email));
78
                        }
79
                }
80
        }
81
 
1116 daniel-mar 82
        /**
1130 daniel-mar 83
         * @param bool $remember_me
84
         * @param string $email
85
         * @param array $ldap_userinfo
1116 daniel-mar 86
         * @return void
87
         * @throws OIDplusException
88
         */
1130 daniel-mar 89
        private function doLoginRA(bool $remember_me, string $email, array $ldap_userinfo) {
635 daniel-mar 90
                $ra = new OIDplusRA($email);
91
                if (!$ra->existing()) {
92
                        $this->registerRA($ra, $ldap_userinfo);
93
                        OIDplus::logger()->log("[INFO]RA($email)!", "RA '$email' was created because of successful LDAP login");
94
                }
95
 
96
                OIDplus::authUtils()->raLoginEx($email, $remember_me, 'LDAP');
97
 
98
                OIDplus::db()->query("UPDATE ###ra set last_login = ".OIDplus::db()->sqlDate()." where email = ?", array($email));
99
        }
100
 
1116 daniel-mar 101
        /**
1130 daniel-mar 102
         * @param string $upn
1116 daniel-mar 103
         * @return int
104
         * @throws OIDplusException
105
         */
1130 daniel-mar 106
        private function getDomainNumber(string $upn): int {
635 daniel-mar 107
                $numDomains = OIDplus::baseConfig()->getValue('LDAP_NUM_DOMAINS', 1);
108
                for ($i=1; $i<=$numDomains; $i++) {
109
                        $cfgSuffix = $i == 1 ? '' : "__$i";
110
                        $upnSuffix = OIDplus::baseConfig()->getValue('LDAP_UPN_SUFFIX'.$cfgSuffix, '');
111
                        if (str_ends_with($upn, $upnSuffix)) return $i;
112
                }
113
                return -1;
114
        }
115
 
1116 daniel-mar 116
        /**
117
         * @param string $actionID
118
         * @param array $params
119
         * @return int[]
120
         * @throws OIDplusConfigInitializationException
121
         * @throws OIDplusException
122
         */
123
        public function action(string $actionID, array $params): array {
635 daniel-mar 124
                if ($actionID == 'ra_login_ldap') {
125
                        if (!OIDplus::baseConfig()->getValue('LDAP_ENABLED', false)) {
126
                                throw new OIDplusException(_L('LDAP authentication is disabled on this system.'));
127
                        }
128
 
129
                        if (!function_exists('ldap_connect')) throw new OIDplusConfigInitializationException(_L('PHP extension "%1" not installed','LDAP'));
130
 
702 daniel-mar 131
                        OIDplus::getActiveCaptchaPlugin()->captchaVerify($params, 'captcha');
635 daniel-mar 132
 
133
                        _CheckParamExists($params, 'email');
134
                        _CheckParamExists($params, 'password');
135
 
136
                        $upn = $params['email'];
137
                        $password = $params['password'];
138
 
139
                        $domainNumber = $this->getDomainNumber($upn);
140
                        if ($domainNumber <= 0) {
141
                                throw new OIDplusException(_L('The server is not configured to handle this domain (the part behind the at-sign)'));
142
                        }
143
                        $cfgSuffix = $domainNumber == 1 ? '' : "__$domainNumber";
144
 
145
                        if (empty($upn)) {
146
                                throw new OIDplusException(_L('Please enter a valid username'));
147
                        }
148
 
1050 daniel-mar 149
                        $ldap = new \VtsLDAPUtils();
635 daniel-mar 150
 
151
                        try {
152
 
153
                                $cfg_ldap_server      = OIDplus::baseConfig()->getValue('LDAP_SERVER'.$cfgSuffix);
154
                                $cfg_ldap_port        = OIDplus::baseConfig()->getValue('LDAP_PORT'.$cfgSuffix, 389);
155
                                $cfg_ldap_base_dn     = OIDplus::baseConfig()->getValue('LDAP_BASE_DN'.$cfgSuffix);
156
 
157
                                // Note: Will throw an Exception if connect fails
158
                                $ldap->connect($cfg_ldap_server, $cfg_ldap_port);
159
 
160
                                if (!$ldap->login($upn, $password)) {
161
                                        if (OIDplus::config()->getValue('log_failed_ra_logins', false)) {
162
                                                OIDplus::logger()->log("[WARN]A!", "Failed login to RA account '$upn' using LDAP");
163
                                        }
164
                                        throw new OIDplusException(_L('Wrong password or user not registered'));
165
                                }
166
 
167
                                $ldap_userinfo = $ldap->getUserInfo($upn, $cfg_ldap_base_dn);
168
 
169
                                if (!$ldap_userinfo) {
170
                                        throw new OIDplusException(_L('The LDAP login was successful, but the own user %1 cannot be found. Please check the base configuration setting %2 and %3', $upn, "LDAP_BASE_DN$cfgSuffix", "LDAP_UPN_SUFFIX$cfgSuffix"));
171
                                }
172
 
173
                                $foundSomething = false;
174
 
175
                                // ---
176
 
177
                                $cfgAdminGroup = OIDplus::baseConfig()->getValue('LDAP_ADMIN_GROUP'.$cfgSuffix,'');
178
                                if (!empty($cfgAdminGroup)) {
179
                                        $isAdmin = $ldap->isMemberOfRec($ldap_userinfo, $cfgAdminGroup);
180
                                } else {
181
                                        $isAdmin = false;
182
                                }
183
                                if ($isAdmin) {
184
                                        $foundSomething = true;
185
                                        $remember_me = isset($params['remember_me']) && ($params['remember_me']);
186
                                        OIDplus::authUtils()->adminLoginEx($remember_me, 'LDAP login');
187
                                }
188
 
189
                                // ---
190
 
191
                                $cfgRaGroup = OIDplus::baseConfig()->getValue('LDAP_RA_GROUP'.$cfgSuffix,'');
192
                                if (!empty($cfgRaGroup)) {
193
                                        $isRA = $ldap->isMemberOfRec($ldap_userinfo, $cfgRaGroup);
194
                                } else {
195
                                        $isRA = true;
196
                                }
197
                                if ($isRA) {
198
                                        if (OIDplus::baseConfig()->getValue('LDAP_AUTHENTICATE_UPN'.$cfgSuffix,true)) {
1050 daniel-mar 199
                                                $mail = \VtsLDAPUtils::getString($ldap_userinfo, 'userprincipalname');
635 daniel-mar 200
                                                $foundSomething = true;
201
                                                $remember_me = isset($params['remember_me']) && ($params['remember_me']);
202
                                                $this->doLoginRA($remember_me, $mail, $ldap_userinfo);
203
                                        }
204
                                        if (OIDplus::baseConfig()->getValue('LDAP_AUTHENTICATE_EMAIL'.$cfgSuffix,false)) {
1050 daniel-mar 205
                                                $mails = \VtsLDAPUtils::getArray($ldap_userinfo, 'mail');
635 daniel-mar 206
                                                foreach ($mails as $mail) {
207
                                                        $foundSomething = true;
208
                                                        $remember_me = isset($params['remember_me']) && ($params['remember_me']);
209
                                                        $this->doLoginRA($remember_me, $mail, $ldap_userinfo);
210
                                                }
211
                                        }
212
                                }
213
 
214
                        } finally {
215
                                $ldap->disconnect();
216
                                $ldap = null;
217
                        }
218
 
219
                        if (!$foundSomething) {
220
                                throw new OIDplusException(_L("Error: These credentials cannot be used with OIDplus. Please check the base configuration."));
221
                        }
222
 
223
                        return array("status" => 0);
224
                } else {
1116 daniel-mar 225
                        return parent::action($actionID, $params);
635 daniel-mar 226
                }
227
        }
228
 
1116 daniel-mar 229
        /**
230
         * @param bool $html
231
         * @return void
232
         */
233
        public function init(bool $html=true) {
635 daniel-mar 234
                // Nothing
235
        }
236
 
1116 daniel-mar 237
        /**
238
         * @param string $id
239
         * @param array $out
240
         * @param bool $handled
241
         * @return void
242
         * @throws OIDplusException
243
         */
244
        public function gui(string $id, array &$out, bool &$handled) {
635 daniel-mar 245
                if ($id === 'oidplus:login_ldap') {
246
                        $handled = true;
247
                        $out['title'] = _L('Login using LDAP / ActiveDirectory');
801 daniel-mar 248
                        $out['icon']  = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png';
635 daniel-mar 249
 
250
                        if (!OIDplus::baseConfig()->getValue('LDAP_ENABLED', false)) {
800 daniel-mar 251
                                $out['icon'] = 'img/error.png';
635 daniel-mar 252
                                $out['text'] = _L('LDAP authentication is disabled on this system.');
253
                                return;
254
                        }
255
 
256
                        if (!function_exists('ldap_connect')) {
800 daniel-mar 257
                                $out['icon'] = 'img/error.png';
635 daniel-mar 258
                                $out['text'] = _L('PHP extension "%1" not installed','LDAP');
259
                                return;
260
                        }
261
 
1116 daniel-mar 262
                        $out['text']  = '<noscript>';
635 daniel-mar 263
                        $out['text'] .= '<p>'._L('You need to enable JavaScript to use the login area.').'</p>';
264
                        $out['text'] .= '</noscript>';
265
 
266
                        $out['text'] .= '<div id="loginLdapArea" style="visibility: hidden">';
702 daniel-mar 267
 
268
                        $out['text'] .= OIDplus::getActiveCaptchaPlugin()->captchaGenerate(_L('Before logging in, please solve the following CAPTCHA'));
635 daniel-mar 269
                        $out['text'] .= '<br>';
270
 
271
                        $out['text'] .= '<p><a '.OIDplus::gui()->link('oidplus:login').'><img src="img/arrow_back.png" width="16" alt="'._L('Go back').'"> '._L('Regular login method').'</a></p>';
272
 
273
                        $out['text'] .= '<h2>'._L('Login as RA').'</h2>';
274
 
275
                        $login_list = OIDplus::authUtils()->loggedInRaList();
276
                        if (count($login_list) > 0) {
277
                                foreach ($login_list as $x) {
278
                                        $out['text'] .= '<p>'._L('You are logged in as %1','<b>'.$x->raEmail().'</b>').' (<a href="#" onclick="return OIDplusPagePublicLogin.raLogout('.js_escape($x->raEmail()).');">'._L('Logout').'</a>)</p>';
279
                                }
280
                                $out['text'] .= '<p>'._L('If you have more accounts, you can log in with another account here.').'</p>';
281
                        } else {
282
                                $out['text'] .= '<p>'._L('Enter your domain username and your password to log in as Registration Authority.').'</p>';
283
                        }
284
                        $out['text'] .= '<form onsubmit="return OIDplusPagePublicLoginLDAP.raLoginLdapOnSubmit(this);">';
285
                        $out['text'] .= '<div><label class="padding_label">'._L('Username').':</label><input type="text" name="username" value="" id="raLoginLdapUsername">';
286
                        $out['text'] .= '&nbsp;&nbsp;';
287
                        $out['text'] .= '<select id="ldapUpnSuffix" name="upnSuffix">';
288
 
289
                        $numDomains = OIDplus::baseConfig()->getValue('LDAP_NUM_DOMAINS', 1);
290
                        for ($i=1; $i<=$numDomains; $i++) {
291
                                $cfgSuffix = $i == 1 ? '' : "__$i";
292
                                $upnSuffix = OIDplus::baseConfig()->getValue('LDAP_UPN_SUFFIX'.$cfgSuffix, '');
293
                                if ($upnSuffix == '') throw new OIDplusException(_L('Invalid base configuration setting: %1 is missing or empty', 'LDAP_UPN_SUFFIX'.$cfgSuffix));
294
                                $out['text'] .= '<option value="'.htmlentities($upnSuffix).'">'.htmlentities($upnSuffix).'</option>';
295
                        }
296
 
297
                        $out['text'] .= '</select>';
298
                        $out['text'] .= '</div>';
299
                        $out['text'] .= '<div><label class="padding_label">'._L('Password').':</label><input type="password" name="password" value="" id="raLoginLdapPassword"></div>';
300
                        if (OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_USER', true)) {
301
                                if ((OIDplus::authUtils()->getAuthMethod() === OIDplusAuthContentStoreJWT::class)) {
302
                                        if (OIDplus::authUtils()->getExtendedAttribute('oidplus_generator',-1) === OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN) {
303
                                                $att = 'disabled checked';
304
                                        } else {
305
                                                $att = 'disabled';
306
                                        }
307
                                } else if ((OIDplus::authUtils()->getAuthMethod() === OIDplusAuthContentStoreSession::class)) {
308
                                        $att = 'disabled';
309
                                } else {
310
                                        $att = '';
311
                                }
312
                                $out['text'] .= '<div><input '.$att.' type="checkbox" value="1" id="remember_me_ldap" name="remember_me_ldap"> <label for="remember_me_ldap">'._L('Remember me').'</label></div>';
313
                        }
314
                        $out['text'] .= '<br><input type="submit" value="'._L('Login').'"><br><br>';
315
                        $out['text'] .= '</form>';
316
 
317
                        $invitePlugin = OIDplus::getPluginByOid('1.3.6.1.4.1.37476.2.5.2.4.2.92'); // OIDplusPageRaInvite
318
                        $out['text'] .= '<p><abbr title="'._L('You don\'t need to register. Just enter your Windows/Company credentials.').'">'._L('How to register?').'</abbr></p>';
319
 
320
                        $mins = ceil(OIDplus::baseConfig()->getValue('SESSION_LIFETIME', 30*60)/60);
321
                        $out['text'] .= '<p><font size="-1">'._L('<i>Privacy information</i>: By using the login functionality, you are accepting that a "session cookie" is temporarily stored in your browser. The session cookie is a small text file that is sent to this website every time you visit it, to identify you as an already logged in user. It does not track any of your online activities outside OIDplus. The cookie will be destroyed when you log out or after an inactivity of %1 minutes (except if the "Remember me" option is used).', $mins);
322
                        $privacy_document_file = 'OIDplus/privacy_documentation.html';
323
                        $resourcePlugin = OIDplus::getPluginByOid('1.3.6.1.4.1.37476.2.5.2.4.1.500'); // OIDplusPagePublicResources
324
                        if (!is_null($resourcePlugin) && file_exists(OIDplus::localpath().'res/'.$privacy_document_file)) {
325
                                $out['text'] .= ' <a '.OIDplus::gui()->link('oidplus:resources$'.$privacy_document_file.'#cookies').'>'._L('More information about the cookies used').'</a>';
326
                        }
327
                        $out['text'] .= '</font></p></div>';
328
 
329
                        $out['text'] .= '<script>$("#loginLdapArea")[0].style.visibility = "visible";</script>';
330
                }
331
        }
332
 
1116 daniel-mar 333
        /**
334
         * @param array $out
335
         * @return void
336
         */
337
        public function publicSitemap(array &$out) {
635 daniel-mar 338
                $out[] = 'oidplus:login_ldap';
339
        }
340
 
1116 daniel-mar 341
        /**
342
         * @param array $json
343
         * @param string|null $ra_email
344
         * @param bool $nonjs
345
         * @param string $req_goto
346
         * @return bool
347
         */
348
        public function tree(array &$json, string $ra_email=null, bool $nonjs=false, string $req_goto=''): bool {
635 daniel-mar 349
                return true;
350
        }
351
 
1116 daniel-mar 352
        /**
353
         * @param string $request
354
         * @return array|false
355
         */
356
        public function tree_search(string $request) {
635 daniel-mar 357
                return false;
358
        }
359
 
1116 daniel-mar 360
        /**
361
         * @param string $id
362
         * @return bool
363
         */
364
        public function implementsFeature(string $id): bool {
1000 daniel-mar 365
                if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.5') return true; // alternativeLoginMethods()
366
                if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.8') return true; // getNotifications()
635 daniel-mar 367
                return false;
368
        }
369
 
1116 daniel-mar 370
        /**
371
         * Implements interface 1.3.6.1.4.1.37476.2.5.2.3.5
372
         * @return array
373
         * @throws OIDplusException
374
         */
1130 daniel-mar 375
        public function alternativeLoginMethods(): array {
635 daniel-mar 376
                $logins = array();
377
                if (OIDplus::baseConfig()->getValue('LDAP_ENABLED', false)) {
378
                        $logins[] = array(
379
                                'oidplus:login_ldap',
380
                                _L('Login using LDAP / ActiveDirectory'),
801 daniel-mar 381
                                OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon16.png'
635 daniel-mar 382
                        );
383
                }
384
                return $logins;
385
        }
1000 daniel-mar 386
 
1116 daniel-mar 387
        /**
388
         * Implements interface 1.3.6.1.4.1.37476.2.5.2.3.8
1130 daniel-mar 389
         * @param string|null $user
1116 daniel-mar 390
         * @return array
391
         * @throws OIDplusException
392
         */
1130 daniel-mar 393
        public function getNotifications(string $user=null): array {
1000 daniel-mar 394
                $notifications = array();
395
                if ((!$user || ($user == 'admin')) && OIDplus::authUtils()->isAdminLoggedIn()) {
396
                        if (OIDplus::baseConfig()->getValue('LDAP_ENABLED', false)) {
397
                                if (!function_exists('ldap_connect')) {
398
                                        $title = _L('LDAP Login');
1008 daniel-mar 399
                                        $notifications[] = array('ERR', _L('OIDplus plugin "%1" is enabled, but the required PHP extension "%2" is not installed.', htmlentities($title), 'php_ldap'));
1000 daniel-mar 400
                                }
401
                        }
402
                }
403
                return $notifications;
404
        }
405
 
635 daniel-mar 406
}