Passed
Pull Request — master (#28)
by Tim
02:50
created

BaseFilter::getLdap()   A

Complexity

Conditions 5
Paths 1

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 20
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 29
rs 9.2888
1
<?php
2
3
/**
4
 * This base LDAP filter class can be extended to enable real filter classes direct access
5
 * access to the authsource ldap config and connects to the ldap server.
6
 *
7
 * @package simplesamlphp/simplesamlphp-module-ldap
8
 */
9
10
declare(strict_types=1);
11
12
namespace SimpleSAML\Module\ldap\Auth\Process;
13
14
use SimpleSAML\Configuration;
15
use SimpleSAML\Error;
16
use SimpleSAML\Logger;
17
use SimpleSAML\Module\ldap\Auth\Ldap;
18
use SimpleSAML\Module\ldap\Utils;
19
use Symfony\Component\Ldap\Ldap as LdapObject;
20
21
abstract class BaseFilter extends \SimpleSAML\Auth\ProcessingFilter
22
{
23
    // TODO: Support ldap:LDAPMulti, if possible
24
    protected static array $ldapsources = ['ldap:Ldap', 'authX509:X509userCert'];
25
26
    /**
27
     * List of attribute "alias's" linked to the real attribute
28
     * name. Used for abstraction / configuration of the LDAP
29
     * attribute names, which may change between dir service.
30
     *
31
     * @var array
32
     */
33
    protected array $attribute_map;
34
35
    /**
36
     * The base DN of the LDAP connection. Used when searching the LDAP server.
37
     *
38
     * @var array
39
     */
40
    protected array $searchBase;
41
42
    /**
43
     * The construct method will change the filter config into
44
     * a \SimpleSAML\Configuration object and store it here for
45
     * later use, if needed.
46
     *
47
     * @var \SimpleSAML\Configuration
48
     */
49
    protected Configuration $config;
50
51
    /**
52
     * Array of LDAP connection objects. Stored here to be accessed later during processing.
53
     *
54
     * @var \Symfony\Component\Ldap\Ldap
55
     */
56
    protected LdapObject $ldapObject;
57
58
    /**
59
     * The class "title" used in logging and exception messages.
60
     * This should be prepended to the beginning of the message.
61
     *
62
     * @var string
63
     */
64
    protected string $title = 'ldap:BaseFilter';
65
66
    /**
67
     * List of LDAP object types, used to determine the type of
68
     * object that a DN references.
69
     *
70
     * @var array
71
     */
72
    protected array $type_map;
73
74
75
    /**
76
     * Checks the authsource, if defined, for configuration values
77
     * to the LDAP server. Then sets up the LDAP connection for the
78
     * instance/object and stores everything in class members.
79
     *
80
     * @throws \SimpleSAML\Error\Exception
81
     * @param array &$config
82
     * @param mixed $reserved
83
     */
84
    public function __construct(array &$config, $reserved)
85
    {
86
        parent::__construct($config, $reserved);
87
88
        // Change the class $title to match it's true name
89
        // This way if the class is extended the proper name is used
90
        $classname = get_class($this);
91
        $classname = explode('_', $classname);
92
        $this->title = 'ldap:' . end($classname);
93
94
        // Log the construction
95
        Logger::debug(sprintf('%s : Creating and configuring the filter.', $this->title));
96
97
        // If an authsource was defined (an not empty string)...
98
        if (isset($config['authsource']) && $config['authsource'] !== '') {
99
            $authconfig = $this->parseAuthSourceConfig($config['authsource']);
100
101
            // Merge the authsource config with the filter config,
102
            // but have the filter config override the authsource config
103
            $config = array_merge($authconfig, $config);
104
105
            // Authsource complete
106
            Logger::debug(sprintf(
107
                '%s : Retrieved authsource [%s] configuration values: %s',
108
                $this->title,
109
                $config['authsource'],
110
                $this->varExport($authconfig)
111
            ));
112
        }
113
114
        // Convert the config array to a config class,
115
        // that way we can verify type and define defaults.
116
        // Store in the instance in-case needed later, by a child class.
117
        $this->config = Configuration::loadFromArray($config, 'ldap:AuthProcess');
118
119
        // Initialize the Ldap-object
120
        $this->ldapObject = $this->initializeLdap();
121
122
        // Set all the filter values, setting defaults if needed
123
        $this->searchBase = $this->config->getArrayizeString('search.base', '');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->config->getArrayi...ring('search.base', '') can also be of type string. However, the property $searchBase is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
124
125
        // Log the member values retrieved above
126
        Logger::debug(sprintf(
127
            '%s : Configuration values retrieved; BaseDN: %s',
128
            $this->title,
129
            $this->varExport($this->searchBase)
130
        ));
131
132
        // Setup the attribute map which will be used to search LDAP
133
        $this->attribute_map = [
134
            'dn'       => $this->config->getString('attribute.dn', 'distinguishedName'),
135
            'groups'   => $this->config->getString('attribute.groups', 'groups'),
136
            'member'   => $this->config->getString('attribute.member', 'member'),
137
            'memberof' => $this->config->getString('attribute.memberof', 'memberOf'),
138
            'name'     => $this->config->getString('attribute.groupname', 'name'),
139
            'return'   => $this->config->getString('attribute.return', 'distinguishedName'),
140
            'type'     => $this->config->getString('attribute.type', 'objectClass'),
141
            'username' => $this->config->getString('attribute.username', 'sAMAccountName')
142
        ];
143
144
        // Log the attribute map
145
        Logger::debug(sprintf(
146
            '%s : Attribute map created: $s',
147
            $this->title,
148
            $this->varExport($this->attribute_map)
149
        ));
150
151
        // Setup the object type map which is used to determine a DNs' type
152
        $this->type_map = [
153
            'group' => $this->config->getString('type.group', 'group'),
154
            'user'  => $this->config->getString('type.user', 'user')
155
        ];
156
157
        // Log the type map
158
        Logger::debug(sprintf(
159
            '%s : Type map created: %s',
160
            $this->title,
161
            $this->varExport($this->type_map)
162
        ));
163
    }
164
165
166
    /**
167
     * Parse authsource config
168
     *
169
     * @param string $as The name of the authsource
170
     */
171
    private function parseAuthSourceConfig(string $as): array
172
    {
173
        // Log the authsource request
174
        Logger::debug(sprintf(
175
            '%s : Attempting to get configuration values from authsource [%s]',
176
            $this->title,
177
            $as
178
        ));
179
180
        // Get the authsources file, which should contain the config
181
        $authsources = Configuration::getConfig('authsources.php');
182
183
        // Verify that the authsource config exists
184
        if (!$authsources->hasValue($as)) {
185
            throw new Error\Exception(sprintf(
186
                '%s : Authsource [%s] defined in filter parameters not found in authsources.php',
187
                $this->title,
188
                $as
189
            ));
190
        }
191
192
        // Get just the specified authsource config values
193
        $authsource = $authsources->getArray($as);
194
195
        // Make sure it is an ldap source
196
        if (isset($authsource[0]) && !in_array($authsource[0], self::$ldapsources)) {
197
            throw new Error\Exception(sprintf(
198
                '%s : Authsource [%s] specified in filter parameters is not an ldap:LDAP type',
199
                $this->title,
200
                $as
201
            ));
202
        }
203
204
        // Build the authsource config
205
        $authconfig = [];
206
        if (isset($authsource['connection_string'])) {
207
            $authconfig['connection_string'] = $authsource['connection_string'];
208
        }
209
        if (isset($authsource['encryption'])) {
210
            $authconfig['encryption'] = $authsource['encryption'];
211
        }
212
        if (isset($authsource['version'])) {
213
            $authconfig['version'] = $authsource['version'];
214
        }
215
        if (isset($authsource['timeout'])) {
216
            $authconfig['timeout'] = $authsource['timeout'];
217
        }
218
        if (isset($authsource['debug'])) {
219
            $authconfig['debug']      = $authsource['debug'];
220
        }
221
        if (isset($authsource['referrals'])) {
222
            $authconfig['referrals']  = $authsource['referrals'];
223
        }
224
225
        // only set when search.enabled = true
226
        if (isset($authsource['search.enable']) && ($authsource['search.enable'] === true)) {
227
            if (isset($authsource['search.base'])) {
228
                $authconfig['search.base'] = $authsource['search.base'];
229
            }
230
            if (isset($authsource['search.scope'])) {
231
                $authconfig['search.scope'] = $authsource['search.scope'];
232
            }
233
            if (isset($authsource['search.username'])) {
234
                $authconfig['search.username']   = $authsource['search.username'];
235
            }
236
            if (isset($authsource['search.password'])) {
237
                $authconfig['search.password']   = $authsource['search.password'];
238
            }
239
240
            // Only set the username attribute if the authsource specifies one attribute
241
            if (
242
                isset($authsource['search.attributes'])
243
                && is_array($authsource['search.attributes'])
244
                && count($authsource['search.attributes']) == 1
245
            ) {
246
                $authconfig['attribute.username'] = reset($authsource['search.attributes']);
247
            }
248
        }
249
250
        // only set when priv.read = true
251
        if (isset($authsource['priv.read']) && $authsource['priv.read']) {
252
            if (isset($authsource['priv.username'])) {
253
                $authconfig['priv.username'] = $authsource['priv.username'];
254
            }
255
            if (isset($authsource['priv.password'])) {
256
                $authconfig['priv.password'] = $authsource['priv.password'];
257
            }
258
        }
259
260
        return $authconfig;
261
    }
262
263
264
    /**
265
     * Initialize the Ldap-object
266
     *
267
     * @return \Symfony\Component\Ldap\Ldap
268
     */
269
    private function initializeLdap(): LdapObject
270
    {
271
        $ldapUtils = new Utils\Ldap();
272
273
        return $ldapUtils->create(
274
            $this->config->getString('connection_string'),
275
            $this->config->getString('encryption', 'ssl'),
276
            $this->config->getInteger('version', 3),
277
            $this->config->getString('extension', 'ext_ldap'),
278
            $this->config->getBoolean('debug', false),
279
            [
280
                'network_timeout' => $this->config->getInteger('timeout', 3),
281
                'referrals' => $this->config->getBoolean('referrals', false),
282
            ]
283
        );
284
    }
285
286
287
    /**
288
     * Local utility function to get details about a variable,
289
     * basically converting it to a string to be used in a log
290
     * message. The var_export() function returns several lines
291
     * so this will remove the new lines and trim each line.
292
     *
293
     * @param mixed $value
294
     * @return string
295
     */
296
    protected function varExport($value): string
297
    {
298
        if (is_array($value)) {
299
            // remove sensitive data
300
            foreach ($value as $key => &$val) {
301
                if ($key === 'search.password') {
302
                    $val = empty($val) ? '' : '********';
303
                }
304
            }
305
            unset($val);
306
        }
307
308
        $export = var_export($value, true);
309
        $lines = explode("\n", $export);
310
        foreach ($lines as &$line) {
311
            $line = trim($line);
312
        }
313
        return implode(' ', $lines);
314
    }
315
}
316